CVE-2015-2545 Word 利用样本分析

0 引子

在上一篇文章中,我们分析了 Office 文档型漏洞 CVE-2015-1641 的利用,本文将继续对此类漏洞中的另一常见案例 CVE-2015-2545(MS15-099)展开分析。相较而言,这些 Exp 的威胁性更大,例如可采用“Word EPS + Windows EoP”的组合,且很多地方借鉴了浏览器漏洞的利用思路,因此还是很值得我们学习研究的。

1 样本信息

分析中用到的样本信息如下:

SHA256:3a65d4b3bc18352675cd02154ffb388035463089d59aad36cadb1646f3a3b0fc

Size:420,577 bytes Type:Office Open XML Document

我们将此文件的后缀名改为 zip,解压后可得到如下目录结构:

图0 样本通过 zip 解压后的目录结构

其中, image1.eps 是精心设计的漏洞利用文件,即由 PostScript 语言编写的特殊图形文件,这里 Word 和 PostScript 的关系一定层度上可类比为 IE 浏览器和 JavaScript 的关系,更多关于 PostScript 语言的说明可参考该手册(https://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf)。

此外,本文的分析环境为 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’ 对象的定义,它们在后面会涉及到:

//PostScript对象的定义 struct PostScript_object { dword type; dword attr; dword value1; dword value2; } ps_obj; //字典‘key-value’对象的定义 struct Dictionary_key_value { dword *pNext; dword dwIndex; ps_obj key; ps_obj value; } dict_kv;

就每个 PostScript 操作符而言,都有一个具体的处理函数与之对应,我们可以很方便的由 IDA 进行查看,之后通过相对偏移的计算就可以在 OllyDBG 中定位到关键点了:

图4 操作符对应的处理函数

借助如下断点我们将在进程加载 EPSIMP32 模块时断下来:

bp LoadLibraryW, UNICODE [dword ptr [esp + 0x04] + 0x6e] == “EPSIMP32.FLT”

图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

?

参 考 链 接

[1] The EPS Awakens

https://www.fireeye.com/blog/threat-research/2015/12/the\_eps\_awakens.html

[2] Microsoft Office Encapsulated PostScript and Windows Privilege Escalation Zero-Days

https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/twoforonefinal.pdf

[3] 警惕利用Microsoft Office EPS漏洞进行的攻击

http://seclab.dbappsecurity.com.cn/?p=603

[4] 针对CVE-2015-2545漏洞研究分析

http://www.4hou.com/technology/4218.html

[5] 文档型漏洞攻击研究报告

http://www.freebuf.com/news/139014.html

[6] PostScript Language Reference Manual

https://www-cdf.fnal.gov/offline/PostScript/PLRM2.pdf

[7] How the EPS File Exploit Works to Bypass EMET (CVE-2015-2545)

http://blog.morphisec.com/exploit-bypass-emet-cve-2015-2545

[8] CVE-2015-2545 ITW EMET Evasion

http://casual-scrutiny.blogspot.com/2016/02/cve-2015-2545-itw-emet-evasion.html

知道创宇404实验室出品

往 期 热 门

原文发布于微信公众号 - Seebug漏洞平台(seebug_org)

原文发表时间:2017-08-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

2017年高频率的互联网校园招聘面试题

参加了2017年校招,面试了阿里、百度、腾讯、滴滴、美团、网易、去哪儿等公司,个人是客户端 Android 方向,总结了面试过程中频率出现较高的题目,希望对大家...

18120
来自专栏游戏开发那些事

【Linux程序设计】之环境系统函数综合实验

这个系列的博客贴的都是我大二的时候学习Linux系统高级编程时的一些实验程序,都挺简单的。贴出来纯粹是聊胜于无。

15430
来自专栏JAVA高级架构

Java阿里面试题

37310
来自专栏开发 & 算法杂谈

MultiRace-Efficient on-the-fly data race detection

     最近在研究数据竞争检测方法,之前的工作是参考了Eraser这个工具1997年提出的基于Lockset方法的动态数据检测,

9420
来自专栏草根专栏

使用xUnit为.net core程序进行单元测试(1)

一. 导读 为什么要编写自动化测试程序(Automated Tests)? 可以频繁的进行测试 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进...

34750
来自专栏刘望舒

Java并发编程(四)Java内存模型

相关文章 Java并发编程(一)线程定义、状态和属性 Java并发编程(二)同步 Java并发编程(三)volatile域 前言 此前我们讲到了线程、同步...

20960
来自专栏岑玉海

hbase源码系列(十三)缓存机制MemStore与Block Cache

这一章讲hbase的缓存机制,这里面涉及的内容也是比较多,呵呵,我理解中的缓存是保存在内存中的特定的便于检索的数据结构就是缓存。 之前在讲put的时候,put是...

49770
来自专栏JadePeng的技术博客

Angular快速学习笔记(4) -- Observable与RxJS

90520
来自专栏张俊红

python数据分析笔记——数据加载与整理

Python数据分析——数据加载与整理 总第47篇 ▼ ? (本文框架) 数据加载 导入文本数据 ? 1、导入文本格式数据(CSV)的方法: 方法一:使用pd....

39880
来自专栏逆向技术

16位汇编语言第二讲系统调用原理,以及各个寄存器详解

   16位汇编语言第二讲系统调用原理,以及各个寄存器详解 昨天已将简单的写了一下汇编代码,并且执行了第一个显示到屏幕的helloworld 问题?   hel...

23900

扫码关注云+社区

领取腾讯云代金券