2012年,在Pwn2Own的黑客大赛上,来自法国的安全团队Vupen通过本篇的CVE-2012-1876漏洞,成功攻破windows7的IE9,他们因此获得数万美元的奖励。根据CVSS 2.0的评分,该漏洞的影响力达到最高分10分,利用难度极高,达到8.6分,其总体评分为9.3分。CVSS评分见下图:
针对这个漏洞我要说明的有以下几点:
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、页面第一次的span等于1,申请的内存空间为0x70;
2、页面第二次的span等于1000,申请的内存空间依然为0x70;
在循环堆写入数据的时候,导致堆溢出。
可见,触发漏洞的代码是在:
mshtml!CTableColCalc::AdjustForCol+0x15。
mshtml!CTableColCalc::AdjustForCol+0x15打断点,逐步运行,可以得到图1,规律为:每一次循环,每两个位置之间相差0x1C,但是第一次是相差0x18。
下面从以下几个方面分析:
1、确认关键变量
2、确认堆循环
3、确认堆数值
在函数
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。
牢牢把握住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来写的呢?下面开始确认。
堆的写范围和什么相关,当然和循环次数相关了。循环次数是怎么定的?如下图,在mshtml!CTableLayout::CalculateMinMax函数里。而且可以推出第一次是0x18(因为第一次循环时v86等于0,adjustforcol里面偏移0x18),第二次开始才是每次0x1Cn+0x18。
如果span=1000,可以写的最远范围为10001C-4,远远超出了0x70的范围。
内容的计算,不是在GetPixelWidth中,而是在GetFancyFormat函数中,也就是
mshtml!CTableLayout::CalculateMinMax+0x1952be处。
这个函数出来之后的eax+0x70,取内容,就是计算的内容。但有个问题,这个函数里面计算的数值很复杂,存在eax中,eax经过的赋值为:
然后eax+70取内容就是计算出来的值了。
现在,这个值非常不好确定,如果要自己去计算,就要跟进TlsGetValue函数。这是完全没有必要的,直接用实验法来确定。比如,width=1,width=2,看等于多少就可以确认要得到的内容了后面EXP的地址,也可以在这里提前确认。当然,实际的值是width100,或者width100 << 8 + 8。
现在最好就取width*100。其实很容易确定。当width=41时候,值就是0x1004,十进制就是4100。也可以看出来就是乘以的100。
图2 堆溢出前堆布局
图3 溢出后堆布局
这两个图来源于《漏洞战争》,很重要。首先分配E、A、B、CButtonLayout的4个堆,然后释放其中的E,用vulheap替代,也就是我们之前的溢出堆来替代。
下面,我来详细说明,这里的编写、调试思路。
运行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
因为9*1C = FC ,刚好等于CButtonLayout的大小,而通过CButtonLayout我们知道,它是FC大小的堆,但实际size是0x108。
特别注意:这儿usersize是0xFC大小,但size是0x108,这和free的”EEEE”堆的大小是一样的。堆占用,要看size,而不是usersize。
在EXP代码CollectGarbage之后,再重新赋值ID=132的span之前,alert一下,然后进去看内存布局。因为取ID=132时,E和A之间有垃圾数据,所以选择ID=131的span。也就是日志vulheap倒数第二个,就是我们要的内存地址。但是因为断点是刚刚打在分配时,还没有赋值,所以还可以看到E的存在。内存如下:
之前其他EXP的span等于44,这个值可能也可以,但没必要覆盖这么大区域,直接等于29即可,内存如下:
未覆盖之前:
覆盖之后:
可见,系统现在调用的地址是:
call [0707002c]。
只要把堆喷射到这个地址,就可以了。
堆布局和x86平台一样,只是堆的大小要重新修改,一些参数要重新确认,这在后面分析。
上面红色是user size。所以,CButtonLayout的实际大小是0x148+0x8(8字节头)=0x150,但注意,可能有绿色的8字节头填充。那就是0x158字节了。下面的堆,是堆大小还等于0x100时的图。现在,x64要更改大小了。
所以,可以看到,Size = 0x12*0x10 = 0x120,刚好是UserSize + 0x10字节头长度。x64平台下,堆头是0x10字节。
其中,0x150是分配的总大小,-0x10是因为8字节头+8字节填充,-4-4是因为填充+BSTR长度,-2是因为最后NULL字符。
注意:蓝色是虚表指针。
不管大小是0x158,还是0x150,内存的堆分配都是根据Size来的,只要Size的数值一样,堆就会被分配在相邻的内存。
同样,在CalculateMinMax函数里,反汇编有:
所以堆指针在:
r14+0xd0+0x10
bu mshtml!CTableLayout::CalculateMinMax+0x1df ".echo vulheap;dd poi(r14+0xd0+0x10) l4;g"
这个断点的最后一个,也就是id=132的vulheap,就是要被覆盖的堆的地址,后面的截图,就是这样找的地址。
通过上面断点分析,发现分配的大小是span0x20
所以让其等于1032=320=0x140,
再加0x10字节头
让其等于0x150
那么需要span等于10。
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。
采用x86堆结构,无法覆盖虚表,需要修改
修改之后,加一个块C,为后面虚表指针被覆盖做准备。
注意:上面字符串B的长度被替换了。
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基址了。
这里的指针地址是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下面的问题,请一定要告诉我。
<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>
<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
);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2022-2-28 20:31
被ExploitCN编辑
,原因: