CVE-2017-5375&CVE-2017-5400&CVE-2016-9079浅析-firefox中的JIT喷射

来源:互联网 发布:怎样显示淘宝价格曲线 编辑:程序博客网 时间:2024/04/30 11:48

CVE-2016-9079是去年影响较大的一个firefox浏览器中SVG Animation模块的UAF漏洞。当用户使用firefox浏览器浏览包含恶意Javascript和SVG代码的页面时,会允许攻击者在用户的机器上远程执行代码。攻击者利用该漏洞,对Windows用户的firefox和Tor浏览器进行了针对性的攻击,并可能获取到了部分匿名用户的真实IP。分析认为,一些情报机构可能利用了该漏洞来收集个人信息。前一段时间,有人在github贴出了一份这个漏洞的exploit,本文将详细分析漏洞的成因,EXP的构造和JIT喷射。

漏洞成因

漏洞发生在nsSMILTimeContainer::NotifyTimeChange中。
这里写图片描述
先来看看EXP构造的页面和触发的过程。
这里写图片描述
在trigger中ic的结束时间和ia的结束时间相关,animateB的结束时间又和ic的结束时间相关,但是animateB起始时间又大于ia的结束时间,所以实际上animateB永远不会开始。在UpdateCurrentInterval函数中将mElementState标记为STATUS_POSTACTIVE,这会创建一个新的interval,并在随后调用RegisterMilestone函数。
这里写图片描述
当创建一个新的interval时,RegisterMilestone会向container的nsSMILTimeContainer对象的mMilestoneEntries数组中增加一个新的entry,数组因此被释放并重新分配,使得NotifyTimeChange中的指针p引用无效的内存。
这里写图片描述
这里写图片描述
在exploit函数中完成喷射操作之后调用pauseAnimations最终会调用到NotifyTimeChange触发漏洞。
这里写图片描述
这里写图片描述
这里写图片描述

执行流

我们来看看代码中heap spray的对象是怎么构造的。
这里写图片描述
首先,exploit函数中构造的对象将执行流引导到heap_spray_fake_objects函数中构造的对象。
这里写图片描述
这里写图片描述
这里写图片描述
如注释所示,我们可以看到,heap_spray_fake_objects函数中构造的对象最终将执行流引导到JIT payload。在101C0CB8 处call dword ptr [eax+138h]进入JIT payload。
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

JIT 喷射

JIT喷射大大降低了利用像UAF这种内存损坏错误的门槛,因为攻击者只需劫持指针跳转到JIT喷射的shellcode。JIT喷射中机器代码可以隐藏在诸如JavaScript的高级语言的常量中:这绕过了DEP;攻击者可以强制JIT编译器将这些常量发送到可预测地址的许多可执行代码区域中:这绕过了ASLR。实现第一点可以在ASM.JS代码中注入NOPS(0x90)。

VAL = (VAL + 0xA8909090)|0;VAL = (VAL + 0xA8909090)|0;

Firefox的ASM.JS编译器生成以下x86机器代码。

00: 05909090A8    ADD EAX, 0xA890909005: 05909090A8    ADD EAX, 0xA8909090

当我们跳到偏移01(第一个指令的中间)时,我们可以执行我们的隐藏代码。

01: 90    NOP02: 90    NOP03: 90    NOP04: A805  TEST AL, 0506: 90    NOP07: 90    NOP08: 90    NOP09: A8...

因此,在我们的四字节常量中有三个字节来隐藏我们的代码,一个字节(0xA8)将ADD EAX,…指令包含在像TEST AL,05这样类似NOP的指令中。实现第二点可以多次请求ASM.JS模块。以Firefox 50.1.0的源代码为例,每次请求ASM.JS模块都会调用CodeSegment::create,它会调用AllocateCodeSegment。
这里写图片描述
AllocateCodeSegment中进一步调用AllocateExecutableMemory。
这里写图片描述
AllocateExecutableMemory调用VirtualAlloc,返回一个64KB对齐的新的RW(PAGE_READWRITE)区域。
这里写图片描述
返回到CodeSegment::create之后,ASM.JS代码被复制到RW区域。ExecutableAllocator::makeExecutable调用VirtualProtect使得RW区域可执行(PAGE_EXECUTE_READ)。
这里写图片描述
这里写图片描述
多次请求一个ASM.JS模块导致会创建许多RX区域。由于VirtualAlloc(64KB)的分配粒度,我们可以选择一个固定地址(如0x1c1c0000),并且可以确定喷射的机器代码位于那里。可以在https://github.com/nmatt0/CVE-2016-9079找到去年的利用代码。它在内存中解析PE头寻找dll加载基址绕过ASLR。
这里写图片描述
很明显JIT喷射的代码量更少也更简单,是一种不错的思路。作者也给了JIT Spray的例子:WinExec_cmd_Firefox_50.1.0.html和WinExec_cmd_Firefox_50.1.0_dynamic.html,在windbg中手动设置EIP即可完成喷射。关于这个JIT喷射的漏洞编号是CVE-2017-5375。在详细介绍补丁及其绕过导致CVE-2017-5400之前,以下是Firefox中ASM.JS常量中隐藏x86代码的两种方法。如前所述,我们可以在ASM.JS常量赋值中隐藏x86指令。

VAL = (VAL + 0xA8909090)|0;VAL = (VAL + 0xA8909090)|0;

然而,常量折叠可以轻松地破坏我们隐藏的NOP。如果一个编译器折叠常量,它可能会发出一个指令,直接用MOV EAX 0x51212120分配加法的结果。因此,我们的代码将会消失。使用ASM.JS的外部函数接口(ffi)可以避免这种情况。

function asm_js_module(stdlib, ffi, heap){    'use asm';    var ffi_func = ffi.func    function payload_code(){        var val = 0;        val = ffi_func(            0xa9909090|0,            0xa9909090|0,            0xa9909090|0,            0xa9909090|0,            0xa9909090|0,        ...

在调用它之前,相应的x86代码准备函数ffi_func()的五个整数参数(0xa9909090)。

3f: c70424909090a9   mov     dword ptr [esp],0A9909090h46: c7442404909090a9 mov     dword ptr [esp+4],0A9909090h4e: c7442408909090a9 mov     dword ptr [esp+8],0A9909090h56: c744240c909090a9 mov     dword ptr [esp+0Ch],0A9909090h5e: c7442410909090a9 mov     dword ptr [esp+10h],0A9909090h

再次,如果我们跳到偏移0x52处第三条指令的中间,我们就可以进入我们注入的代码(NOP-sled)。

52: 90              nop53: 90              nop54: 90              nop55: a9c744240c      test    eax,0C2444C7h5a: 90              nop5b: 90              nop5c: 90              nop5d: a9c7442410      test    eax,102444C7h62: 90              nop

这样,我们可以再次隐藏ASM.JS常量中的三字节长指令,而不会在运行时间内重新同步原始指令流。但空间有限。指令MOV DWORD PTR [ESP+0x7c],0xa9909090由c744247C909090a9表示。如果堆栈偏移较高,另一个操作码使用具有四字节的偏移而不是一个字节的偏移,指令 MOV DWORD PTR [ESP+0x80],0xa9909090就会变成c7842480000000909090a9以保持符号正确。因此,我们可以使用上述技巧来隐藏0x80/4×3=0x60字节的payload。然而,我们可以使用两个字节的ASM.JS常量作为payload字节,另外两个字节作为相对短跳转(ebXX)到下一个payload字节。这允许我们摆脱0x80/4=32个参数和0x60个payload字节的限制。例如,我们使用0x07eb9090作为常量来隐藏两个NOP和一个JMP。

00: c78424800000009090eb07   mov dword [esp + 0x80], 0x07eb90900b: c78424840000009090eb07   mov dword [esp + 0x84], 0x07eb909016: c78424880000009090eb07   mov dword [esp + 0x88], 0x07eb9090

当我们从偏移7处开始执行时,我们的两个NOP和随后的JMP被命中以使注入的代码运行。

07: 90       nop08: 90       nop09: eb07     jmp 0x12...12: 90       nop13: 90       nop14: eb07     jmp 0x1d...1d: 90       nop1e: 90       nop1f: eb07     jmp 0x28

这与HITB-JIT-Spray-Attacks-and-Advanced-Shellcode中所展示的技术相似。最多使用两个字节长的指令。建立一个解析VirtualAlloc,分配RWX的内存,将stage1 shellcode复制到它,并跳转到它的stage0 payload仍然足够。好,现在我们来看看firefox是怎么修复的。

  1. 代码分配的地址被随机化。
    这里写图片描述
  2. 32位中每个进程的可执行ASM.JS/WASM代码的数量被减少为128MB(修复后不久,Firefox 51.0.1中128MB的限制增加到160MB)。
    这里写图片描述

但是如果VirtualAlloc在指定的randomAddr上失败,那么我们回到以前的未受保护代码。另外,假设将一个模块的大小保持在64KB以下,那么最多可以分配160MB/64KB=2560个ASM.JS模块。我们的计划如下。

  1. 使用不相关的不可执行内存占用64KB的对齐地址。使用类型化数组可以轻松完成堆喷射:这减少了可用的64KB基地址的数量。
  2. 喷射尽可能多的ASM.JS实例。由于AllocateExecutableMemory中的回退代码,可用的64KB对齐地址越少,我们使用JIT代码占用可预测地址的可能性越大。
  3. 通过触发垃圾回收器来释放1中分配的内存,因为我们不需要它。

这导致了CVE-2017-5400,并在Firefox 52中修复。

参考资料

https://github.com/rh0dev/expdev
https://github.com/nmatt0/CVE-2016-9079
https://rh0dev.github.io/blog/2017/the-return-of-the-jit/
https://rh0dev.github.io/blog/2017/the-return-of-the-jit-part-2/
https://community.rapid7.com/community/metasploit/blog/2016/12/29/a-friendly-fireside-foray-into-a-firefox-fracas