早些时间,Fireeye 发现了一个用 docx 格式的文件进行攻击的 0day。参见《Two for One: Microsoft Office Encapsulated PostScript and Windows Privilege Escalation Zero-Days THE EPS AWAKENS》。
CVE-2015-2545, 这个 CVE 引起了我的注意,当 @PhysicalDrive0 在推特上发出下面这个样本哈希值时。我觉得这个哈希可能与上面提到的文章有关,我检查了这个样本。
哈希值:375e51a989525cfec8296faaffdefa35
在一番分析之后,我发现这个样本完全逃避了 EMET 的检测。
这是运行截图:
现在,一个doc利用也逃避了 EMET 的检测,而且是以一种非常整洁的手段。
幸运的是,Malware Bytes Anti Exploit 和 Hitman Alert 抓到了它(那些想要保护自己的人永远尝试多种防护手段),尽管攻击者比较好地制作了他们的载荷,他们也没有逃过 MBAE 的堆检测(改天我也许会讲一下这个故事)。
这是在加载它的 dll 之前,对一些 api 的调用追踪:
这里有一份可用的 windbg 调试日志。
这些 api 可用在利用的不同阶段帮助导航分析。
ba r4 ZwProtectVirtualMemory -> 对 ZwProtectVirtualMemory 在这里下一个内存读取断点,既然这个利用用到了它的原始调用序号,显然它需要去从 ntdll 的内存里面获取这个序号。剩下的 api 很容易就能获取到。
现在让我们来关注一下保护机制,和这个特别的样本是如何来 Bypass EMET 的。
这个样本利用内存破坏来覆写一个长度为 0x7fffffff 的字符串对象,然后使用 ROP 和 ShellCode,所以 EMET 理应能在 SimExecFlow、Stack Pivot、Caller Check、EAF 与 EAF+ 这几个阶段捕获它的行为。然而没有...
ROP 逃逸
这个漏洞将一个 String 对象的长度覆写为 0x7fffffff,并且利用它去读取整个内存空间以搜索 ROP 小部件。下面包含了一些它搜索的小部件,这从 EPS 文件利用代码的调用方法里面很容易看得出来。
String 对象替换:注意0x7fffffff
ROP 部件搜索
既然 EMET hook 了 ZwProtectVirtualMemory,直接返回到这个函数会变得很明显,因此才有了下面这种新颖的方式。ZwCreateEvent 或者其他任何没有被 EMET hook的 ntdll 的导出函数都可以被用在这里,既然所有的 Nt/Zw 函数跳转共享同一个函数体,任何 函数头+5 都可以被传入任意调用序号以跳转过来,以调用一个不同的 NT API。作者想到了这种创新的方式,并且发现了一个通用的逃逸技巧,前提是它能对抗 ASLR。
这种方法的唯一问题是 ZwProtectVirtualMemory 的调用索引需要被硬编码。攻击者使用任意地址读取在内存里搜索 ZwProtectVirtualMemory 的头部并且读取其序号。
现在,这种技巧面临的问题是,如果 ZwProtectVirtualMemory 被一些防护软件 Hook 了,函数的前部分看起来会像是这样:
在这种情况下,如果攻击者试着去获取索引号,他们只会获得 E9 指令后面一个随机相对偏移里面的一个字节。为了处理这种情况,攻击者精心构造了 PS 文件,这个 PS 文件里面会从 ZwProtectVirtualMemory 开启的内存里面搜寻 B8 [索引号] 00 00 这样的字节模式,并且累加计算它读过 0xC2(0xC2 是 RET N 指令的字节码) 的次数。通过这种方式,它将会知道有多少函数被跳过,直到它遇到一个没有被 Hook 的 NT系列函数调用头,然后获取这个函数的调用索引,最后减去它遇到的 0xC2 的次数。
在我的例子里,另外一些 Hook 也可能被其他所设置,所以对应的汇编看起来是下面这样子的:
现在,利用代码读取了整个内存,直到它发现 0x77be0024,它从这里获取了 0x50,这是 ZwTerminateThread 的调用序号。从 ZwProtectVirtualMemory 开始,它读取了 3 个的 C2,将这两个值进行相减...
完美地逃过了 ROP 检测。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)