CVE-2013-2551漏洞分析
2016-12-26 14:17
609 查看
CVE-2013-2551漏洞成因与利用分析
1. 简介
VUPEN在Pwn2Own2013上利用此漏洞攻破了Win8+IE10,5月22日VUPEN在其博客上公布了漏洞的细节。它是一个ORG数组整数溢出漏洞,由于此漏洞的特殊性,使得攻击者可以通过整数溢出修改数组的长度,获取任意内存读写的能力,最终绕过ASLR并获取控制权限实现远程代码执行。在漏洞公布不久,就有人写出了对应的metasploit利用代码,不过其代码只支持Win7+IE8环境。通过分析,想要在Win7+IE10下成功利用此漏洞需要解决几个关键问题,并运用几个技巧。本文首先参考已有的分析文章,根据自己的理解对漏洞成因再次进行综合分析,记录分析过程及结果,方便小组成员学习。然后针对IE8、IE10两个环境,对漏洞利用所使用的技术及技巧进行总结。
2. 实验环境
操作系统:Win7 SP1 (6.1.7601.17514)浏览器:IE10.0.9200.16540
漏洞编号:CVE-2013-2551
3. 漏洞分析
这个漏洞是由于负责VML解析的模块VGX.DLL,在处理<v:stroke>标签的dashstyle.array.length属性时,没有对传入的参数进行完备验证而导致的整数溢出。攻击者利用这个漏洞能够对任意地址进行读写操作——通过读取敏感内存信息、改写对象虚函数表,就能够完美绕过重重内存防护机制实现任意代码执行。
3.1. VML
VML(Vector Markup Language)矢量标记语言是SVG的前身,它仍然被IE10所支持,通过它可以在页面中表现二维矢量图形。值得说明的是IE10在默认情况下是不支持VML的,但通过在HTML中加入有关对文档模式的声明,可以使IE10正常解析VML标签。VML被vgx.dll(C:\Program Files\Common Files\Microsoft Shared\VGX)所实现,可以通过附件中的VML.html来简单了解VML的使用。
3.2. 漏洞成因
VML为shape元素实现了一个Stroke子元素,Stroke子元素有一个DashStyle属性。DashStyle可以是一个常数也可以是一个自定义的类型。例如下面的使用方法:当通过JS读取dashstyle属性时,其内部会调用"vgx!COAStroke::get_dashstyle()",随后"vgx!COAStroke::get_dashstyle()"调用子函数"vgx!COAShapeProg::GetOALineDashStyle()"并最终返回一个COALineDashStyle对象。
dashstyle属性的功能是被COALineDashStyle对象实现的,COALineDashStyle对象的方法如下,其中get_value/put_value是针对dashstyle为常数的情况。
如果dashstyle属性是自定义类型,调用COALineDashStyle::get_array()方法,将会在其子函数COAShapeProg::GetOALineDashStyleArray()中返回一个COALineDashStyleArray对象。
COALineDashStyleArray对象用来管理自定义类型的dashstyle属性,其方法如下:
设置或者读取dashstyle.array.length属性时,对应的put_length或get_length函数就会被调用;利用数组下标访问dash.array属性时,对应的put_item或get_item函数就会被调用。
使用JS语句对dashstyle属性赋值时,例如:stroke.dashstyle = "1 2 3 4",函数vgx!ParseDashStyle会被调用,将控制流转向_MsoFCreateArray函数来创建一个ORG数组。在_MsoFCreateArray中调用_MsoFInitPx函数根据数组成员个数来、进行内存分配,每个数组成员占四字节内存,所以ORG数组的缓冲区大小为数组成员个数×4(byte)。在对dashstyle.array.length属性赋值时,数组成员的个数会改变,在put_length函数中会根据新设置的值来重新为ORG数组成分配内存。然而在put_length中存在一个整数溢出漏洞,漏洞相关代码如下:
可以看出漏洞成因就在于put_length函数判断当前长度current_length和desired_length长度时,使用跳转指令是用于判断有符号数的jge指令,而数组长度是一个unsiged int类型的变量。因此当传入desired_length的值为有符号数0xFFFFFFFF时,会导致跳转至loc_1008B844处,进而调用ORG::DeleteRange。执行流程为:ORG::DeleteRange-->MsoDeletePx-->MsoFRemovePx。
进入MsoFRemovePx函数时,arg_4=current_length-desired_length,arg_8=desired_length。
在MsoFRemovePx函数内部,最终将desired_length设置成了current_length,使得在没有重新分配内存的情况下将ORG数组长度current_length改写成0xFFFF。因此,可以通过此时的ORG数组获得越界访问。
要想成功触发漏洞,须使用JS将dashstyle.array.length的值设置为0xFFFFFFFF,但是如果直接使用语句dashstyle.array.length = 0xFFFFFFFF。会提示错误有"溢出"发生,将0xFFFFFFFF改为(0-1)就能够成功触发漏洞了。
“漏洞成因”结合“信息泄露”的源代码在附录poc (CVE-2013-2551).html中给出,请参见附件。
4. 漏洞利用
4.1. Bypass ASLR
4.1.1. 信息泄露
VML shape的_vgRuntimeStyle属性由COARuntimeStyle对象负责处理,它包含很多方法,如下使用JS语句设置或者读取_vgRuntimeStyle.marginLeft属性时,对应的COARuntimeStyle::put_marginLeft()或者COARuntimeStyle::get_marginLeft()函数就会被调用。
如果是第一次访问marginLeft/rotation属性,那么在put_marginLeft/put_rotation函数中会调用CVMLShape::GetRTSInfoàCParserTag::GetRTSInfo来创建一个COARuntimeStyle对象,该对象大小为0xAC(实际分配0xB0)。
而marginLeft属性的值对应的字符串指针就保存在该COARuntimeStyle对象的0x58偏移处。同样get_marginLeft函数在读取marginLeft属性时,也是通过访问这个对象偏移0x58位置上的字符串指针来完成的。
COARuntimeStyle::put_marginLeft()
因此,我们可以设置堆内存,使得ORG数组在一个COARuntimeStyle对象前面。然后通过ORG数组用任意值(这里使用0x7ffe0300)越界重写marginLeft属性的字符串指针,再使用COARuntimeStyle::get_marginLeft()将任意内存地址(0x7ffe0300)处的内容读取出来。0x7ffe0300处保存着ntdll!KiFastSystemCall的地址,通过此地址减去相应偏移即可得到当前ntdll.dll的内存基址。
也可以通过ORG数组越界读随后COARuntimeStyle对象的前4字节(虚函数表指针),最终减去相应偏移即可得到当前vgx.dll的内存基址。
信息泄露部分的源代码见附件poc (CVE-2013-2551).html
4.1.2. 定位shellcode
定位shellcode有两种方法可控选择,使用的最多的方法就是堆喷射,但是它并不是一种完美的利用方法。完美的利用方法是通过漏洞读取出shellcode的地址。我使用两种方法分别实现了IE10下的漏洞利用。
4.1.2.1. 通过堆喷射布局shellcode
使用堆喷射可以方便的布局我们的shellcode在指定的内存处。在IE8下,堆喷射使用常见的heapLib技术。IE10下,heapLib不再奏效,我们可以通过DEPS技术实现堆喷射。具体技术资料及源码很多,此处不再赘述。
4.1.2.2. 通过漏洞定位shellcode
由于此漏洞的特殊性,使其具有任意地址读的能力。参见4.1.1信息泄露部分,我们可以将shellcode作为COARuntimeStyle的marginLeft属性值,再通过触发漏洞使用ORG数组越界读取出shellcode的地址。其原理与“信息泄露”一致,主要在于代码实现,请参见“CVE-2013-2551_MS13-037(IE10noHeapSpray).rb”。
4.2. 获取控制权
当通过JS读取_anchorRect属性时,其内部会调用"vgx!COAShape::get__anchorRect()",最终创建并返回一个0x10大小的COAReturnedPointsForAnchor对象。因此有这样一种利用场景:创建大量的COAReturnedPointsForAnchor对象,在其中为Dashstyle属性创建具有4个元素的ORG数组使其内存布局刚好位于大量COAReturnedPointsForAnchor对象之间。然后利用漏洞通过ORG数组越界改写其后COAReturnedPointsForAnchor对象的虚函数表指针,从而获取控制权。具体步骤如下:
1) 创建大量的COAShape元素(v:shape)
2) 遍历每个COAShape的_anchorRect属性,这样就可以创建COAReturnedPointsForAnchor元素,在遍历的过程中,某个地方给dashstyle属性赋值,这样会创建ORG对 象,为保证ORG对象和COAReturnedPointsForAnchor对象在同一个堆块并且地址连续,ORG的元素数目为4个,正好 4×4 = 0x10。
3) Dashstyle.array.length = -1,触发漏洞。由于漏洞被触发,Dashstyle.array的长度被修改为0xffff,可以越界写内存
4) dashstyle.array.item(6) = 0x0c0c0c0c,修改ORG后面的COAReturnedPointsForAnchor虚表指针。Item(6)是为了跳过堆首部的8个字节。
5) 释放_anchorRec元素,当被修改虚表指针的元素被删除时,会通过虚表调用Release函数,这样就可以被攻击者获得控制权。
4.3. Bypass DEP
4.3.1. IE8
首先来看当被修改虚函数表指针的COAReturnedPointsForAnchor对象将要被释放时,索引其Release虚函数的过程。如上图所示,(98行)ecx保存着对象UserPtr地址,并将其首4字节(虚函数表指针)传递给eax,然后(100行)调用虚函数表的第三个函数。而在“获取控制权”阶段我们已经将对象的首4字节改写成了0x0c0c0c0c,因此它将在0x0c0c0c0c处的伪造虚函数表中索引虚函数表并调用。
在IE8下通过常规的heapLib技术进行堆喷射,将shellcode布局在0x0c0c0c0c处。并在0x0c0c0c0c处伪造虚函数表,构造ROP chain。
如上图所示,0x0c0c0c0c处就是根据之后虚函数被调用顺序,构造的ROP chain,将切换堆栈的指令(xchg eax,esp)的地址放在了0x0c0c0c0c + 0x08处,这样当程序接下来调用虚基表中的第3个虚函数Release时,就在伪造的虚函数表0x0c0c0c0c处索引第三个函数地址,并执行xchg eax,esp; retn指令
注意,当调用“第三个虚函数”时,eax保存着虚函数表指针(此时为0x0c0c0c0c),ecx保存着对象UserPtr指针。其随后的执行流程如下:
1) 通过xchg eax,esp指令可将堆栈切换到0x0c0c0c0c上,使得esp指向0x0c0c0c0c处。随后执行retn指令时,将会在新的堆栈(0x0c0c0c0c)上索引返回地址并
跳转执行。
2) 执行0x0c0c0c0c处第一个4字节地址所指向的指令——retn。继续在堆栈上索引返回地址。
3) 执行0x0c0c0c0c处第二个4字节地址所指向的指令——pop ebx; retn。意在跳过0x0c0c0c0c处的第三个4字节,并将第四个4字节的值作为返回地址并执行。
4) 第四个4字节为实际为ntdll!ZwProtectVirtualMemory函数地址,其返回地址、调用参数已经硬编码到随后的shellcode中。其目的在于修改0x0c0c0c40处0x400字
节大小的内存空间的属性,将其修改成可执行页面。
5) ntdll!ZwProtectVirtualMemory执行完毕后从堆栈上索引返回地址,跳转到0x0c0c0c40处继续执行,最终绕过了DEP,实现了任意代码执行。
4.3.2. IE10
在IE10下,我们将面对两个关键问题。首先是以前常用的堆喷射技术(heapLib)不再奏效;其次是编译器做了调整,在索引虚函数时eax不再保存虚函数表指针,转而保存对象UserPtr指针,并由ecx来保存虚函数表指针。其造成的结果是指令串xchg ecx,esp; retn;对应的字节码变长(变为87E1C3,而xchg eax,esp; retn为94C3),使得在内存中很难搜索到能够满足类似要求的指令串,从而无法轻易控制esp。(IE10下相关代码如下图所示)针对IE10,我找到了可替代的堆喷射方法——DEPS,关于DEPS的技术细节及实现请参见参考资料链接。
而当在ntdll.dll中尝试搜索实现类似功能的指令串(xchg ecx,esp; retn;)无果后,需要重新理清思路。想明白我们能控制什么,尽最大可能利用它们仍然可以绕过阻碍。
4.3.2.1. 堆喷射下ROP chain构造
在IE10下使用DEPS技术仍然可以进行堆喷射,并把我们的shellcode部署在指定内存空间,demo中将shellcode部署在0x0c0c2228处。此时我们重点关注如何在call dword ptr [ecx+8]时控制堆栈,使得esp切换到指定的内存空间处(0x0c0c2228)在IE8下,我们通过漏洞使用ORG数组越界改写了对象的虚函数表指针,最终对象在释放过程中将索引我们伪造的虚函数,并在一个ROP gadget中实现对堆栈的控制。但IE10下保存虚函数表指针的寄存器变成了ecx,保存对象指针的寄存器变成了eax,此时无法在一个ROP gadget中实现类似xchg ecx,esp; retn的功能。
此时,我们面对的情况为:保存对象指针的eax、保存对象虚函数表指针的ecx、一个具有越界读写对象能力的ORG数组。
因此,可以使用ORG数组进一步越界改写对象头部,利用容易找到的xchg eax,esp; retn指令串先将堆栈切换到对象内存空间上。然后再通过其他的ROP gadget将堆栈切换到shellcode处(0x0c0c2228)。实现对堆栈esp的完全控制。
首先通过mona.py搜索ntdll.dll中所有可能的gadget后。在其搜索结果中寻找可用的ROP gadget。最终效果如下:
由于我们使用了堆喷射技术对shellcode进行布局,因此我们能够确切的知道其内存位置(0x0c0c2228),在调用ntdll!ZwProtectVirtualMemory修改shellcode内存空间可执行权限时基本不存在问题,可以通过硬编码设置当前堆栈上的返回地址、参数值等。如下图所示:
4.3.2.2. 无堆喷射下ROP chain构造
获取shellcode地址在4.1.1信息泄露部分,已经介绍了利用COARuntimeStyle对象及其marginLeft属性来读取任意地址处的值。如果将shellcode作为COARuntimeStyle的marginLeft属性,再通过触发漏洞,可以很容易获得shellcode的地址,这样就可以不使用heap spray,实现完美的利用。利用方法如下:
其中#{js_code}为shellcode,marginLeftAddress即为读取到的shellcode地址。
构造ROP chain
由于shellcode地址是动态获取的,因此在构造ROP chain(尤其是在堆栈上为ntdll!ZwProtectVirtualMemory构造返回地址及参数)时不能使用硬编码。构造ROP chain是一项考验并激发创造力的体力活,其中mona.py为我们创造了条件,提供了很多便利。
在为其构造ROP chain时运用了几个小技巧,下面一一讲解,首先来看函数的定义
其总体思想是将ntdll!ZwProtectVirtualMemory函数调用的函数地址、返回地址、参数依次传递给edi、esi、ebp、esp、ebx、edx、ecx。然后使用pushad; retn指令串将其布局在堆栈上并调用,调用pushad; retn指令串前的要求寄存器环境为:
因为ntdll!ZwProtectVirtualMemory函数的参数中有指针,参数NumberOfBytesToProtect即为一个指针,指向要修改保护属性的字节数。在ROP chain中可以使用push esp; pop; pop retn的指令串来动态获得当前的地址,例如下图122行代码所示,其ROP gadget将NumberOfBytesToProtect的地址传递给了esi,最终传递给了ebx
ntdll!ZwProtectVirtualMemory的参数BaseAddress也是一个指针,指向要修改内存属性的内存基址。调用pushad; retn前,要使esp指向想要修改内存属性的内存基址也需要一点小技巧,我的实现如下图。143行代码首先获取当前堆栈的地址,然后将其加0x40字节,并写入其值所指的内存中。即指针所指的内存空间保存着指针的地址。
最后,真正的返回地址(esi)要指向最终的payload,它将会被布局在ROP chain之后,即pushad; retn指令串之后的+4字节处(pushad; retn指令串之后的4字节保存BaseAddress)。因此,还需要将eax加4字节再传递给esi。
由于在shellcode中不能出现/u0000,因此在指定NewAccessProtection时,通过加法指令溢出寄存器,使其变为0x00000040。其实现过程如下:
至此,构造ROP chain时所运用的几个技巧已经讲解完毕。此处也可以将shellcode分开存放,即将payload作为COARuntimeStyle的marginLeft属性存放;将伪造的虚函数表及用于改写payload所在页面属性的ZwProtectVirtualMemory参数布局在ntdll.dll的.rsrc处,即可省去复杂的ROP构造;
构造ROP chain虽然是体力活,但也考验并激发创造力。有点像走一根有障碍的独木桥,想明白自己能控制什么,尽最大可能利用它们绕过阻碍,最终展现那奇妙可能性时的成就感妙不可言。
5. 总结
网上也已经有多篇文章对CVE-2013-2551漏洞成因与利用方法进行了分析说明。个人认为它是学习漏洞利用技术非常好的一个例子,通过这一个漏洞可以了解学习到漏洞利用过程中的多种技术与技巧。
6. 参考资料
[1] Advanced Exploitation of Internet Explorer 10 / Windows 8 Overflow (Pwn2Own 2013):http://www.vupen.com/blog/20130522.Advanced_Exploitation_of_IE10_Windows8_Pwn2Own_2013.php[2] CVE-2013-2551利用技巧分析:http://hi.baidu.com/4b5f5f4b/item/33d5c21e33ed12181994ec6c
[3] CVE-2013-2551利用分析:http://zenhumany.blog.163.com/blog/static/171806633201403114815739/
7. 附录
7.1. poc (CVE-2013-2551).html
<html> <head> <meta http-equiv="x-ua-compatible" content="IE=EmulateIE9" > </head> <title> POC by VUPEN </title> <style>v\: * { behavior:url(#default#VML); display:inline-block }</style> <xml:namespace ns="urn:schemas-microsoft-com:vml" prefix="v" /> <body onload="createRects(); exploit();"> <v:oval style="width:100pt;height:50pt" fillcolor="red"></v:oval> <v:oval> <v:stroke id="vml1"/> </v:oval> </body> <script> var rect_array = new Array() var a = new Array() function createRects(){ for(var i=0; i<0x400; i++){ rect_array[i] = document.createElement("v:shape") rect_array[i].id = "rect" + i.toString() document.body.appendChild(rect_array[i]) } } function exploit(){ var vml1 = document.getElementById("vml1") for (var i=0; i<0x400; i++){ a[i] = document.getElementById("rect" + i.toString())._vgRuntimeStyle; } for (var i=0; i<0x400; i++){ a[i].rotation; if (i == 0x300) { vml1.dashstyle = "1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44" } } var length_orig = vml1.dashstyle.array.length; vml1.dashstyle.array.length = 0 - 1; for (var i=0; i<0x400; i++) { a[i].marginLeft = "a"; marginLeftAddress = vml1.dashstyle.array.item(0x2E+0x16); if (marginLeftAddress > 0) { vml1.dashstyle.array.item(0x2E+0x16) = 0x7ffe0300; var leak = a[i].marginLeft; vml1.dashstyle.array.item(0x2E+0x16) = marginLeftAddress; vml1.dashstyle.array.length = length_orig; alert( parseInt( leak.charCodeAt(1).toString(16) + leak.charCodeAt(0).toString(16), 16 )); return; } } } </script> </html>
7.2. CVE-2013-2551_MS13-037(IE10).rb
View
Code
7.3. CVE-2013-2551_MS13-037(IE10noHeapSpray).rb
View
Code
相关文章推荐
- Windbg漏洞分析之CVE-2013-2551
- linux kernel 本地提权漏洞CVE-2013-1763 exploit 代码分析
- CVE-2013-3346Adobe Reader和Acrobat 内存损坏漏洞分析
- CVE 2013-3897 - UAF 漏洞分析学习学习
- CVE 2013-6272 Android phone提权打电话漏洞分析
- CVE-2013-3346&CVE-2013-5065-Adobe Reader释放重引用漏洞+NDProxy.sys数组越界漏洞联合利用恶意样本分析
- PCManFTP v2.0(CVE-2013-4730)漏洞分析报告
- linux kernel 本地提权漏洞CVE-2013-1763 exploit 代码分析
- 【转】cve-2013-2094 perf_event_open 漏洞分析
- Android电话拨打权限绕过漏洞(CVE-2013-6272)分析
- PCManFTP v2.0(CVE-2013-4730)漏洞分析报告
- linux kernel 本地提权漏洞CVE-2013-1763 exploit 代码分析
- PCManFTP v2.0(CVE-2013-4730)漏洞分析报告
- CVE 2013-3897 - UAF 漏洞分析学习学习
- Android电话拨打权限绕过漏洞(CVE-2013-6272)分析
- 转载CVE-2013-2551利用技巧分析
- Linux Kernel 'sctp_v6_xmit()'函数信息泄露漏洞(CVE-2013-4350)
- Microsoft Windows 远程权限提升漏洞(CVE-2013-3175)(MS13-062)
- CVE-2010-4258漏洞分析
- Linux Kernel空指针引用本地拒绝服务漏洞(CVE-2013-5634)