CVE-2015-2545 Word 利用样本分析
作者:xd0ol1@知道创宇404实验室
0 引子
在上一篇文章中,我们分析了 Office 文档型漏洞 CVE-2015-1641 的利用,本文将继续对此类漏洞中的另一常见案例 CVE-2015-2545(MS15-099)展开分析。相较而言,这些 Exp 的威胁性更大,例如可采用“Word EPS + Windows EoP”的组合,且很多地方借鉴了浏览器漏洞的利用思路,因此还是很值得我们学习研究的。
1 样本信息
分析中用到的样本信息如下:
1 |
<ol class="linenums"><li class="L0"><code><span class="pln">SHA256</span><span class="pun">:</span><span class="lit">3a65d4b3bc18352675cd02154ffb388035463089d59aad36cadb1646f3a3b0fc</span></code></li><li class="L1"><code><span class="typ">Size</span><span class="pun">:</span><span class="lit">420</span><span class="pun">,</span><span class="lit">577</span><span class="pln"> bytes</span></code></li><li class="L2"><code><span class="typ">Type</span><span class="pun">:</span><span class="typ">Office</span><span class="pln"> </span><span class="typ">Open</span><span class="pln"> XML </span><span class="typ">Document</span></code></li></ol> |
我们将此文件的后缀名改为 zip,解压后可得到如下目录结构:
图0 样本通过 zip 解压后的目录结构
其中,image1.eps
是精心设计的漏洞利用文件,即由 PostScript 语言编写的特殊图形文件,这里 Word 和 PostScript 的关系一定层度上可类比为 IE 浏览器和 JavaScript 的关系,更多关于 PostScript 语言的说明可参考该手册。
此外,本文的分析环境为 Win7 x86+Office 2007 SP3,EPSIMP32 模块的版本信息如下:
图1 EPSIMP32 模块的版本信息
2 漏洞原理分析
首先我们看下原理,简单来说就是 Word 程序在解析 EPS(Encapsulated PostScript)图形文件时存在一个 UAF(Use-After-Free)的漏洞,其错误代码位于 EPSIMP32 模块。为了便于理解,我们给出样本中触发此漏洞的那部分 PostScript 代码,当然有经过一定的反混淆处理:
图2 触发漏洞的那部分 PostScript 代码(PoC)
其中操作符 copy 和 forall 的定义如下:
图3 dict 操作时 copy 和 forall 的定义
结合上述代码,我们给出漏洞原理更为具体的描述:当通过 forall 操作 dict2 对象时,将对 dict2 中的 ‘key-value’ 进行迭代处理,且 pNext 指针指向下一对待处理的 ‘key-value’。然而,proc 中存在 dict1 dict2 copy
的操作,此过程会先释放掉 dict2 原有的 ‘key-value’ 空间,之后再申请新空间进行接下来的拷贝,即原先 pNext 指向的 ‘key-value’ 空间被释放了。而后在 putinterval 操作中将重新用到原先 pNext 指向的空间,并向其中写入特定的字符串。因此,在下一次迭代时,pNext 指向的数据就变成了我们所构造的 ‘key-value’。
接着我们来完整分析下此过程,这里给出 PostScript 对象和 dict 下 ‘key-value’ 对象的定义,它们在后面会涉及到:
1 |
<ol class="linenums"><li class="L0"><code><span class="com">//PostScript对象的定义</span></code></li><li class="L1"><code><span class="kwd">struct</span><span class="pln"> </span><span class="typ">PostScript_object</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L2"><code><span class="pln"> dword type</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> dword attr</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pln"> dword value1</span><span class="pun">;</span></code></li><li class="L5"><code><span class="pln"> dword value2</span><span class="pun">;</span></code></li><li class="L6"><code><span class="pun">}</span><span class="pln"> ps_obj</span><span class="pun">;</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="com">//字典‘key-value’对象的定义</span></code></li><li class="L9"><code><span class="kwd">struct</span><span class="pln"> </span><span class="typ">Dictionary_key_value</span><span class="pln"> </span><span class="pun">{</span></code></li><li class="L0"><code><span class="pln"> dword </span><span class="pun">*</span><span class="pln">pNext</span><span class="pun">;</span></code></li><li class="L1"><code><span class="pln"> dword dwIndex</span><span class="pun">;</span></code></li><li class="L2"><code><span class="pln"> ps_obj key</span><span class="pun">;</span></code></li><li class="L3"><code><span class="pln"> ps_obj value</span><span class="pun">;</span></code></li><li class="L4"><code><span class="pun">}</span><span class="pln"> dict_kv</span><span class="pun">;</span></code></li></ol> |
就每个 PostScript 操作符而言,都有一个具体的处理函数与之对应,我们可以很方便的由 IDA 进行查看,之后通过相对偏移的计算就可以在 OllyDBG 中定位到关键点了:
图4 操作符对应的处理函数
借助如下断点我们将在进程加载 EPSIMP32 模块时断下来:
1 |
<ol class="linenums"><li class="L0"><code><span class="pln">bp </span><span class="typ">LoadLibraryW</span><span class="pun">,</span><span class="pln"> UNICODE </span><span class="pun">[</span><span class="pln">dword ptr </span><span class="pun">[</span><span class="pln">esp </span><span class="pun">+</span><span class="pln"> </span><span class="lit">0x04</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="lit">0x6e</span><span class="pun">]</span><span class="pln"> </span><span class="pun">==</span><span class="pln"> </span><span class="pun">“</span><span class="pln">EPSIMP32</span><span class="pun">.</span><span class="pln">FLT</span><span class="pun">”</span></code></li></ol> |
图5 WINWORD 进程加载 EPSIMP32 模块
很自然的我们会想到在 forall 的对应函数上下断,可以得到与 dict 操作迭代处理相关的代码段如下,其中
EPSIMP32 的模块基址为 0x73790000:
图6 dict 在 forall 操作时的迭代处理
此过程包含4个 call 调用,其中第一个 call 用于获取当前要处理的 ‘key-value’ 和指针 pNext,即指向下次处理的 ‘key-value’,而第二个和第三个 call 分别用于将 key 和 value 存储到操作栈上,最后的第四个 call 则用于处理 proc 中的操作。
我们来跟一下,在第一个 call 调用时,ecx 寄存器指向的内容为 dict2 内部 hash-table的 指针、hash-table 的大小以及包含的 ‘key-value’ 个数:
图7 ecx 寄存器指向的 hash-table
此调用执行完成后,我们会得到 keyZ1 和指向 keyZ2 的指针:
图8 keyZ1 及指向 keyZ2 的指针
而当第二个和第三个 call 调用完成后,我们可以看到 keyZ1 的 key 和 value 被存储到了操作栈上:
图9 将 keyZ1 存储到操作栈上
在第四个 call 调用中,对于 proc 的各操作符,首先会获取对应处理函数的地址,而后以虚函数的方式进行调用,相关代码片段如下:
图10 调用操作符的处理函数
这里我们主要关注 copy 操作,由分析可知,在其处理过程中会将 dict2 内部 hash-table 上对应的所有 ‘key-value’ 空间都释放掉,即上述 pNext 指向的 keyZ2 空间被释放掉了,如下给出的是进行该 delete 操作的函数入口:
图11 delete ‘key-value’ 的函数入口
同样,此时入参 ecx 寄存器指向的内容中包含了 dict2 的 hash-table 指针,接下去的操作将逐次释放
keyZ1~keyZ8 的空间,最后 hash-table 也会被释放掉:
图12 释放 dict2 上的 ‘key-value’ 空间
而释放的 keyZ2 空间,即 pNext 指向的空间,将在随后的 putinterval 操作中被重新写入特定的伪造数据:
图13 由 putinterval 操作写入伪造数据
因此,在 forall 的下一次迭代过程中,根据 pNext 指针获取的 ‘key-value’ 就变成了我们所伪造的数据,并且之后同样被存储到了操作栈上:
图14 伪造的 ‘key-value’
3 漏洞利用分析
这里我们接着上一节的内容来继续跟下漏洞的利用,此时伪造的 ‘key-value’ 已经被存储到了操作栈上,下述给出的是本次迭代中 forall 操作所处理的 proc 代码:
图15 第二次迭代时处理的 proc 代码
也就是将操作栈上的 key 和 value 分别赋给 xx_19169
以及 xx_26500
,操作完成后得到的
xx_19169
如下:
图16 xx_19169 中的内容
可以看到,xx_19169
的 type 字段为 0x00000003,即表示的是整型,所以对于本文的分析环境来说,接下去的处理过程将会按照 “old version” 的分支来进行:
图17 不同版本执行分支的选择
而 xx_26500
则是实现漏洞利用的关键,由图18可知它的 type 字段为 0x00000500,表明这是一个string类型,且 value2 字段为泄露出来的指针,在此基础上经过一系列构造后,可得到 string 对象如下:
图18 获取 RW primitives
在 PostScript 中会为每个 string 对象分配专门的 buffer 用于存储实际的字符串内容,其基址及大小就保存在该 string 对象中。就最终样本伪造的 string 对象来说,其 buffer 基址为 0x00000000,且大小为 0x7fffffff,因此借助此对象可以实现任意内存的读写。之后代码会通过获取的 RW primitives 来查找
ROP gadgets,从而创建 ROP 链,同时由 putinterval 操作将 shellcode 和 payload 写入内存:
图19 创建 ROP 链并写入 shellcode 和 payload
之后再通过修改操作符 bytesavailable 处理函数中的如下 call 指针跳转到 ROP 链上:
图20 控制 EIP 跳转到 ROP 链
其中,ROP 链包含的指令如下,可以看到首先进行的是 stack pivot 操作,接着会将 shellcode 所在的页属性置为可执行,最后跳转到 shellcode 的入口:
图21 ROP 链中的操作指令
这里借助了一个小技巧来绕过保护程序对 ZwProtectVirtualMemory 调用的检测,对于 ntdll 模块中的
Nt/Zw 函数,除了赋给 eax 寄存器的 id 不同外,其余部分都是相同的。ROP 链在完成 eax 的赋值后,也就是将 ZwProtectVirtualMemory 函数中的 id 赋给 eax 后,会直接跳过 ZwCreateEvent 函数(该函数未被 hook)的前5字节并执行余下的那部分指令,通过这种方式能实现任意的系统调用而不会被检测到:
图22 绕过保护程序对 ZwProtectVirtualMemory 调用的检测
下面我们再来简单看下 shellcode,和大多数情况一样,它的主要作用就是获取相关的 API 函数,然后创建并执行 payload 文件。样本中 shellcode 的部分数据经过了加密处理,因此会有一个解密的操作:
图23 对 shellcode 中的数据进行解密
而后,代码通过查找 LDR 链的方式来获取 msvcrt 模块的基址:
图24 获取 msvcrt 模块的基址
之后从 msvcrt 模块的导入表中得到函数 GetModuleHandleA 和 GetProcAddress 的入口地址,由
GetModuleHandleA 函数可以获取到 kernel32 模块的句柄,最后再借助 GetProcAddress 调用来逐个获取下述的导出函数地址:
图25 获取相关的 API 函数
紧接着 payload 的内容,即图19所示代码中介于首尾字符串 “5555555566666666” 之间的那部分数据,会被写入到临时目录下的 plugin.dll 文件中,分析可知这是一个恶意的程序:
图26 样本创建的恶意 dll 文件
通过 LoadLibraryA 函数加载该 plugin.dll 模块后,将会在临时目录下另外再释放一个名为 igfxe.exe 的程序,其作用是获取远程文件并执行之:
图27 释放的 igfxe.exe 程序
4 结语
本文基于样本文档分析了 CVE-2015-2545 的利用,然鉴于笔者就 PostScript 语言所知尚少,固有些点也是没能给讲透彻,希望能有更多这类漏洞的分析文章出现。另外,错误之处还望各位加以斧正,欢迎一起交流:P
5 参考
[1] The EPS Awakens
[2] Microsoft Office Encapsulated PostScript and Windows Privilege Escalation Zero-Days
[3] 警惕利用Microsoft Office EPS漏洞进行的攻击
[5] 文档型漏洞攻击研究报告
[6] PostScript Language Reference Manual
[7] How the EPS File Exploit Works to Bypass EMET (CVE-2015-2545)