2012年,在Pwn2Own的黑客大赛上,来自法国的安全团队Vupen通过本篇的CVE-2012-1876漏洞,成功攻破windows7的IE9,他们因此获得数万美元的奖励。根据CVSS 2.0的评分,该漏洞的影响力达到最高分10分,利用难度极高,达到8.6分,其总体评分为9.3分。CVSS评分见下图:
针对这个漏洞我要说明的有以下几点: 1、本文并不做详细的漏洞成因分析,而是做详细的EXP编写分析; 2、本文只对核心漏洞代码、利用代码进行说明; 3、所以,阅读本文之前,你最好看看下面的网址,有很详细的基础说明:8f3K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7q4)9J5k6i4N6W2K9i4S2A6L8W2)9J5k6i4q4I4i4K6u0W2j5$3!0E0i4K6u0r3M7#2)9J5c8W2N6X3j5K6q4%4e0X3x3H3d9%4k6o6M7g2S2I4c8W2k6q4d9#2u0I4j5#2p5`. 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,可以写的最远范围为1000 1C-4,远远超出了0x70的范围。
内容的计算,不是在GetPixelWidth中,而是在GetFancyFormat函数中,也就是 mshtml!CTableLayout::CalculateMinMax+0x1952be处。 这个函数出来之后的eax+0x70,取内容,就是计算的内容。但有个问题,这个函数里面计算的数值很复杂,存在eax中,eax经过的赋值为:
然后eax+70取内容就是计算出来的值了。 现在,这个值非常不好确定,如果要自己去计算,就要跟进TlsGetValue函数。这是完全没有必要的,直接用实验法来确定。比如,width=1,width=2,看等于多少就可以确认要得到的内容了后面EXP的地址,也可以在这里提前确认。当然,实际的值是width100,或者width 100 << 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 所以让其等于10 32=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的技巧,参考下面网址:2bfK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4K6i4K6g2X3K9s2g2V1i4K6u0r3j5i4u0@1K9h3y4D9k6g2)9J5c8X3c8W2N6r3q4A6L8s2y4Q4x3V1j5&6z5o6t1I4y4K6x3#2i4K6y4r3N6i4c8E0i4K6g2X3L8h3g2V1K9i4g2E0i4K6y4p5k6r3W2K6N6s2u0A6j5Y4g2@1k6g2)9J5k6i4N6S2M7q4)9#2k6Y4u0W2L8r3g2$3j5h3&6@1i4K6u0W2L8X3!0F1k6g2)9J5k6s2c8S2M7$3E0Q4x3X3c8T1L8r3!0Y4i4K6u0V1x3W2)9%4c8h3c8W2k6X3q4#2L8s2c8Q4y4@1g2T1j5h3W2V1N6h3A6K6i4K6g2X3N6r3W2@1L8r3g2Q4y4@1g2V1k6h3k6S2N6h3I4@1i4K6u0V1z5g2)9J5k6i4N6S2M7q4)9#2k6X3u0D9L8$3N6Q4y4h3k6J5k6h3I4W2N6X3q4F1N6q4)9#2k6X3c8W2k6X3q4#2L8s2c8Q4x3U0k6S2L8i4m8Q4x3@1u0K6M7r3#2Q4x3@1b7I4x3o6l9I4i4K6u0W2x3U0p5H3x3g2)9J5k6e0x3H3x3o6q4Q4x3X3f1@1x3U0b7J5i4K6u0W2y4R3`.`. 正因为是这样,就把width定成了1178993 100,指向07070024。当然,也可以是其他的数据,反正就是要先看内存分布,再定width大小。 最后,通过mona.py生成ROP数据,就可以达到任意代码执行的目的了。
代码下载地址为:49dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6q4P5s2m8D9L8$3W2@1b7@1&6Q4x3V1k6o6g2V1g2Q4x3X3b7J5x3o6p5J5i4K6u0V1x3e0R3%4y4W2)9J5k6s2N6A6L8U0N6Q4y4h3k6^5z5o6k6Q4y4h3k6S2L8X3c8Q4y4h3k6%4K9h3^5%4P5o6j5@1 其中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
);
[注意]看雪招聘,专注安全领域的专业人才平台!
最后于 2022-2-28 20:31
被ExploitCN编辑
,原因: