再探Office EPS漏洞-EMET Bypass分析
2021-11-19 18:0:52 Author: mp.weixin.qq.com(查看原文) 阅读量:2 收藏

前言

该漏洞和上次分析的 CVE-2017-0261 为同一类型,同样是 EPS 文件解析产生的 UAF 漏洞,因此在分析时就不再讲解 EPS 的语法和相关结构了。分析该漏洞是因为样本能够完全绕过 EMET,故此深入分析下绕过的原理并改造 CVE-2017-0261 绕过 EMET。

调试环境

调试是直接在 Office2007 上进行调试,调试环境如下:

OS:            Win7 x64 SP1
Office:        Ofiice 2007 x86
Image name:    EPSIMP32.FLT
ImageSize:     0x0006E000
File version:  2006.1200.4518.1014
样本hash:     375e51a989525cfec8296faaffdefa35

漏洞分析

漏洞成因

在分析之前说明下 dict 对象的结构:

查看 eps 文件,漏洞触发代码如图所示:

forall 操作符遍历 dict 对象,在执行过程中使用 copy 将一个新的 dict 对象拷贝到正在遍历的 dict 对象中,此时原本的 dict 对象将会被释放。接着通过构造特殊结构的字符串对象,覆盖被释放的 dict 对象的结构,导致在第二次遍历 dict 的对象时去获取了构造好的字符串对象,这样便产生了一个有限大小的读写原语。

在 windbg 中定位到 forall 操作符的代码,查看此时操作栈的对象可以发现 xx_41 为遍历的对象:

接着定位到 copy 操作符所在位置,查看此时操作栈的情况可以得知 xx_41 将会被 xx_18467 覆盖:

继续深入分析,到图中所示执行操作符 delete 位置时,查看参数:

经过分析可以发现 eax 的值正是 xx_41 中的 keyZ1:

查看 keyZ1,正是 Dict_Object 对象,里面储存的为 0x1000 大小的 array 对象:

当执行完 delete 后,keyZ1 所占有的内存被释放,随后一直循环 delete 直到整个 xx_41 的内容全部被释放。

执行完 copy 后,查看操作栈中发现 xx_41 的内容已经变成 xx_18467:

当执行到 putinterval 时,可以看到由于创建的字符串大小为 35(0x23),实际会分配 0x24 大小的结构用于存储字符串,而该大小正是 dict 对象结构的大小。因此当 keyZ2 被释放后,此时再次创建一个 0x24 大小的字符串将会占用 keyZ2 的内存空间:

forall 第二次要取的值仍为原来的 keyZ2,但此时 keyZ2 指向的内存已经被故意构造的字符串占用,导致了 UAF:

当 forall 执行第二遍时,此时将把故意构造的字符串当作 dict 对象获取到操作栈中,key 和 value 会被压入栈中:

最终 xx_26500 获取了字符串,xx_19169 获取了整数,从 xx_26500 的结构可以看出构造了一个大小为 0x2710 的读写原语:

漏洞原理部分分析完毕。

漏洞利用

构造读写原语

接下来参考上次分析的 CVE-2017-0261 的漏洞利用部分,尝试在了解漏洞原理的基础上自己构造读写原语,构造思路如下:

  1. 利用获取到的 0x2710 大小的 xx_26500 字符串对象构造指向 string 结构的 0x30 结构和 0x28 的 string 结构
  2. 获取 0x30 结构和 0x28 结构的首地址,并使用两个地址指向首地址
  3. 将构造好的读写原语的首地址放置在任意一个 string 对象的 value2

构造字符串结构很容易,但是要能获取到结构所在地址。因此在结构的位置上选取了 xx_26500 字符串中 0xfc 的位置,该位置存储的内容为指向后四个字节的地址可以准确的定位,因此将该值作为 stringbase:

接着开始构造指向 string 结构的 0x30 结构和 0x28 的 string 结构,通过 putinterval 操作符将构造好的结构放入 stringbase+0xc 的位置:

将 0x30 结构的首地址放入 stringbase 中,0x28 结构的首地址放入 stringbase+4 中,将 stringbase+4 的地址放入 0x28 结构的首地址 0x24 中,这样 0x30 结构就指向了 0x28 结构。具体的 eps 代码和结构如下图所示:

构造好读写原语的相关结构后,就需要把结构首地址放置在任意一个 string 对象的 value2 后,这一步打算重复漏洞触发的过程,将构造好的读写原语的 PostScript 结构字符串覆盖原本正常的 dict 结构,最终获得了一个能够读写任意内存的读写原语:

EMET Bypass 分析

样本在构造 ROP 这里开始 Bypass EMET,不像一般的 ROP 直接调用 VirtualProtect 来修改内存属性,而是调用 ZwProtectVirtualMemory。但是 EMET 对 ZwProtectVirtualMemory 进行 hook,因此不能直接调用。样本获取到 ZwProtectVirtualMemory 的地址后会往后遍历,当遍历到 retn 后计数加一,直到遍历到没有被 hook 的函数后获取该函数的调用号,将调用号减去 retn 计数就得到了 ZwProtectVirtualMemory 原本的调用号:

通过 ROP 将调用号赋值给 eax,之后再通过调用未被 hook 的 ZwCreateEvent 函数的后 5 个字节直接调用 ZwProtectVirtualMemory 修改 shellcode 的内存:

可以看到此时 ZwProtectVirtualMemory 是被 hook 的,而通过这种方式则完美绕过了 hook:

然而只绕过 hook 是不够的,需要 shellcode 绕过 EAF,样本通过 fs:[0]获取 SEH 链拿到 msvcrt.dll 的句柄,随后通过回退搜索 MZ 头寻找 msvcrt.dll 的基地址。通过 msvcrt.dll 的导入表获取函数地址并最终将 shellcode 后的 PE 文件写入到本地文件中并启动:

利用手法移植

在了解了样本的绕过思路后,在 CVE-2017-0261 上尝试绕过 EMET。

首先将原本获取 VirtualProtect 的地址改为获取 NtProtectVirtualMemory 和 NtCreateEvent:

随后修改 ROP 链直接通过 ZwProtectVirtualMemory 的调用号调用 ZwCreateEvent+0x5 的位置修改内存属性,成功绕过了 EMET:

shellcode 由于样本采用了 PE 文件落地的方式,容易被查杀,因此修改 shellcode 采取 syscall 的方式直接写注册表自启项,最终成功绕过了 EAF:

总结

CVE-2015-2545 是 EPS 文件解析类的首个漏洞,CVE-2016-0261 无论是在漏洞触发和漏洞利用上都和该漏洞十分相似,连辅助函数都基本和 2545 保持一致。

不同的是该样本通过 syscall 绕过了 EMET 对于关键函数的 hook,这种绕过的思路可以应用在其他具有能读写任意内存的读写原语的漏洞中。

同时 shellcode 也与传统的从 PEB 结构直接获取 kernerl32.dll 的基地址不同,通过 SEH 链获取 msvcrt.dll 的基地址在获取导入表函数地址绕过 EAF,这种利用都是值得借鉴的。

参考链接

[1] 野外的 CVE-2015-2545 逃逸了 EMET: https://bbs.pediy.com/thread-216046.htm

[2] CVE-2015-2545 Word 利用样本分析: https://paper.seebug.org/368/


文章来源: https://mp.weixin.qq.com/s?__biz=Mzg2MTY0MDc1Mw==&mid=2247484386&idx=1&sn=3a9cee16f921c88a8b64130aa1aee21c&chksm=ce1542bcf962cbaa913ff60fc0e3bd534517878f3f3a9a1355c9b54d7e2274d82f4e37b816ec&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh