*本文中涉及到的相关漏洞已报送厂商并得到修复,本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担。
*本文原创作者:elli0tn0phacker,本文属FreeBuf原创奖励计划,未经许可禁止转载
0x00 背景
CVE-2015-2419是IE11中jscript9.dll的一个Double Free漏洞。虽然从发现在野攻击样本到今天已有三年多,但是目前依然被一些著名的漏洞利用工具包使用,可见其漏洞利用的稳定。通过分析其漏洞原理和利用技术,对于深入理解Double Free漏洞和jscript9.dll的exploit编写会有一定的帮助。
0x01 漏洞成因
触发漏洞的PoC如下:
PoC通过create_triggering_json() 创建了一个触发漏洞的Json对象:
通过JSON.stringify将这个Json对象转换为JSON字符串,最后通过手动GC触发漏洞,开启HPA UST观察漏洞现场:
IE Crash,edx指向了未分配的内存,分析edx的来源,查看 Js::TempArenaAllocatorWrapper<1>::Dispose 函数:
这里edx = [this+0x34]-0x8,同时观察绿框代码,可以发现这里存在一个双向链表的卸载操作:
(1) [[edx+4]]= [edx] (Prev pointer)
(2) [[edx]+4]= [edx+4] (Next pointer)
(3) Free[edx]
通过观察Call stack,可以发现这里是在脚本执行CollectGarbage()时调用:jscript9!Js::TempArenaAllocatorWrapper<1>::Dispose 释放已经被释放的内存触发的Double Free漏洞,观察edx保存的地址是在什么时候被释放的:
可以看到edx保存的地址是在执行JSON.Stringify操作时,调用了:ThreadContext::ReleaseTemporaryGuestAllocator 被释放的,观察这个函数的释放逻辑:
这里当[this+0x3c4]>= 5时就会进入free memory分支,可以看到free memory分支的代码和上面分析的:Js::TempArenaAllocatorWrapper<1>::Dispose 逻辑时一样的,然而两次free memory本身逻辑时没有问题的,这个漏洞的根本原因时free memory释放内存后,保存这块memory地址的指针(edx)没有被清零,而这个指针正是 Js::TempArenaAllocatorWrapper 的成员变量,保存在 Js::TempArenaAllocatorWrapper + 0x34处。
分析patch过的jscript9.dll可以发现这个漏洞是如何被修复的:
可以看到patch过的jscript9.dll,在free memory前会先检查edx是否为0,不为0则进入free memory分支,并且在free后将指针置0。
那么触发漏洞条件是什么呢,其实就是[this+0x3c4] >= 5,通过调试可以知道[this+0x3c4]保存的就是Json对象的嵌套深度,所以目前分析得到信息有:
(1) 当Json对象嵌套深度>=5时,GC后会触发Double Free漏洞
(2) 漏洞的根本原因是释放了类的成员变量保存的内存但是没有将该成员变量清零,GC时释放这个对象再次释放这块内存导致Double Free
(3) 利用这个漏洞不能直接RCE,但是可以触发一个双向链表的释放操作
现在我们开始尝试漏洞利用。
0x02 漏洞利用
通过前面的分析可以知道利用这个漏洞我们会获得一次双向链表卸载操作的机会,双向链表的卸载操作可以简化如下:
(1) [[edx+4]]= [edx] 第一个DWORD被复制到第二个DWORD指向的内存
(2) [[edx]+4]= [edx+4] 第二个DWORD被复制到第一个DWORD指向的地址+0x4的内存
如果edx可控的话,我们就可以获得一次向任意2个DWORD([edx]和[edx]+4)写入数据的机会。那写入数据的机会如何利用呢,这里就需要HeapSpray,Jscript9.dll里HeapSpray常用的数据类型如下:
Array:
JavaScript的Array在IE11中有两种形式:
(1) Js::JavascriptNativeIntArray
主要数据结构:
Js::JavascriptNativeIntArray
+0x0 vftable
+0x10 Array size
+0x14 first buffer address
+0x18 second buffer address
Buffer
+0x4 item size
+0x8 item buffer size (超过这个size会分配一块新内存)
(2) Js::JavascriptArray
数据结构同 Js::JavascriptNativeIntArray,当Array中保存了对象后,数字元素会以2N+1的奇数形式存放用来区别对象指针。
ArrayBuffer和TypedArray:
ArrayBuffer表示通用的、固定长度的原始二进制数据缓冲区。ArrayBuffer不能直接操作,而是要通过类型数组对象TypedArray或 DataView 对象来操作,它们会将缓冲区中的数据表示为特定的格式,并通过这些格式来读写缓冲区的内容。
Js::JavascriptArrayBuffer
+0x0 vftable
+0x1C ArrayBufferAddress
+0x20 ArrayBufferSize
Js::TypedArray<unsignedint>
+0x0 vftable
+0x10 Js::JavascriptArrayBuffer
+0x1C ArrayBufferSize
+0x20 ArrayBufferAddress
需要注意的是ArrayBuffer是在CRT堆上分配的。
我们可以使用 Js::TypedArray<unsigned int> 做漏洞利用的数据类型,这里只需要修改+0x1C的ArrayBufferSize和+0x20的ArrayBufferAddress就可以实现用户态任意地址读写。
首先需要通过Heap Spray将Js::TypedArray<unsigned int>放到可预测内存地址。
Heap Spray
利用Heap Spray我们可以将Js::TypedArray<unsignedint>排布在指定内存地址,使用如下代码进行Heap Spray:
每个spray[size]分配的内存大小 = 0x20(JavascriptArray Header) + 0x3BF8*4(JavascriptArray Data) + 0x55*0x30(Js::TypedArrayHeader) = 0xFFF0 (对齐)= 0x10000 (64kB):
Heap Spray完检查内存:
这样通过Heap Spray可以在0x128ef000处稳定找到一个Js::TypedArray<unsigned int,0>。 接下来利用这个Double Free的漏洞就可以通过2个DWORD修改的机会篡改这个Js::TypedArray<unsignedint,0>的长度(0x128ef01c)和ArrayBuffer起始地址(0x128ef020),实现任意地址读写。
任意地址读写
在 jscript9!ThreadContext::ReleaseTemporaryGuestAllocator + 0x5bc4a 处下断点,查看Double free内存大小:
被释放的内存为0x0CBytes,因此这里可以用0x0C Bytes ArrayBuffer占位,并将需要修改2 DWORD地址写到ArrayBuffer数据:
这里用0x10000 0xC Bytes的ArrayBuffer尝试占位,占位成功后,在 jscript9!Js::TempArenaAllocatorWrapper<1>::Dispose :触发漏洞,利用双向链表的卸载操作修改我们之前通过Heap Spray排布在0x128ef000处的Js::TypedArray<unsignedint,0>的长度(0x128ef01c)和Arraybuffer起始地址(0x128ef020):
这样就得到了一个起始地址为0x128ef018大小为0x128ef020的Js::TypedArray<unsigned int,0>,再利用这个Js::TypedArray<unsignedint,0>获得一个任意地址读写的Js::TypedArray<unsignedint,0>:
泄露基址
执行Shellcode前,需要获得一些关键函数的地址。比如 kernel32!VirtualProtectStub,通过如下方法泄露函数地址:
(1)利用任意地址写权限,在0x128e0020(即spray[i][0])处写入标记数据0x1BD81BD (0xDEC0DE * 2 |1)
(2)通过遍历spray[i][0]找到被写入标记数据0x1BD81BD的spray[k]
(3)再将JavascriptArray对象保存再spray[k][2]处,通过任意地址写权限,读取0x128e0020处的DWORD,这个就是JavascriptArray的vftable,那么jscript9_base_addr = vftable & 0xffff0000
(4) 搜索jscript9.dll导入表,泄露kernel32!VirtualProtect地址
构造ROP
我们依然使用0x128e0000的spray[k]来存放ROP gadget和 shellcode,其中spray[k][0]存放ROP gadget, spray[k][1]存放shellcode:
执行Shellcode
spray[k][2][0]= 0会调用 jscript9!Js::JavascriptArray::SetItem 给JavascriptArray赋值,SetItem函数在jscript9!Js::JavascriptArray::`vftable'+0x90处,通过篡改spray[k][2]的JavascriptArray的虚表从而执行最终shellcode:
参考文献:
(1) https://www.fireeye.com/blog/threat-research/2015/08/cve-2015-2419_inte.html
*本文原创作者:elli0tn0phacker,本文属FreeBuf原创奖励计划,未经许可禁止转载