最新的漏洞利用开始渐渐脱离基于 ROP 的代码重用攻击。在过去的两年里,出现了一些关于一种新的代码重用攻击的文章,Counterfeit Object-Oriented Programming(COOP)。COOP 是一种顶级的针对 forward-edge 的执行流完整性(CFI) 的攻击方式。在我们把 CFI 解决方案(HA-FI)整合进我们的终端产品中时,这种攻击吸引了我们的注意力。COOP 主要出现在学术界,还没有出现在 exloit 工具包里。这也许是因为攻击者更趋向于使用更简单的方法。在 win10 年度更新中微软的Edge使用了执行流保护(CFG,Control Flow Guard)。在 CFG 中,缺少 backward-edge 的 CFI 更容易受到攻击。但是当 Return Flow Guard (RFG) 出现,使得攻击者不能再依靠淹没栈中的返回地址进行攻击的时候,会发生些什么呢?
我们对评估 COOP 在攻击 CFI 时的效果很感兴趣。这不仅可以使我们保持在学术界和黑客社区中前沿研究中的地位,也可以测试产品的有效性,更改设计,甚至在必要时普遍地提高我们自己的防御能力。在我们的这一系列的两篇博文中的第一篇中,介绍了我们使用 COOP 函数重用对微软的 CFG 以及我们自己的 HA-CFI 进行攻击的评测。
已经有大量的论文,博文和会议发言充分地讨论了微软的执行流保护(CFG,Microsoft’s Control Flow Guard)。Trail of Bits 在两篇最近的帖子里比较了 Clang CFI 和微软 CFG。第一篇帖子着重Clang,第二篇强调微软对 CFI 的实现,还有额外的研究提供了 CFG 的实现的进一步细节。
在过去的几年里,绕过 CFG 也成为了安全会议中中一个流行的主题。在我们引用一些著名的绕过方法之前,最重要的是 CFI 能够进一步分为两种:forward-edge 和 backward-edge。
Forward-Edge CFI:保护间接调用或是 JMP 位置. Forward-edge CFI解决方案包括微软 CFG和 Endgame 的 HA-CFI.
Backward-Edge CFI:保护返回指令. Backward-edge CFI解决方案包括微软的Return Flow Guard,Endgame 的 DBI exploit 防护的一部分, 以及包括 intel 的 CET 在内的其他 ROP 检测。
这个分类帮助我们描绘出了 CFG 保护位置的轮廓——间接调用位置——以及不打算保护的位置——栈返回地址。例如,一个最近的 POC 入选了 exploit 工具包,这个 POC 针对 Edge,使用读/写的原始方法来修改栈中的返回地址。但这并不适用于CFG,不应该作为CFG的弱点来考虑。尽管如此,它成功地证明了 CFG 的有效性,并使攻击者转向劫持执行流,而不是间接调用的位置。这个例子实际上证明了 CFG 缺陷包括以下几点:利用未受保护的函数调用位置,重映射包括CFG代码在内的只读内存区域,并使他们指向需要受到检查的代码,在Charka中提到的JIT编码器的资源竞争,使用基于内存的间接调用。COOP 或是函数重用攻击,在面对CFI的实现时有着公认的局限,因为“limitations of coarse-grained CFI”,他们并没有入选微软的bypass赏金。也就是说,我们不知道有哪些公有领域的 POCs 能证明 COOP 能指定攻击 CFG 的加固的二进制代码。
CFG 对每个受保护的 DLL 添加了一个 __guard_fids_table,它由一系列在二进制代码中合法的RVAs或是间接调用指令中敏感的目的地址组成。一个地址作为 CFG bitmap 索引的一部分而存在。bitmap 里的 bits 能够根据地址是否是合法的目的地址而进行切换。在此之外,也有一个 API 能够对 bitmap 进行修改,例如,为了支持 JIT编码的页面:
kernelbase!SetProcessValidCallTargets 在使用系统调用更新 bitmap 之前会调用ntdll!SetInformationVirtualMemory
win10 创意者更新有一项新增的功能可以抑制导出,也就是说,现在导出函数能在 CFG 保护的调用位置被标记为非法目的地址。这一功能的实现需要使用 CFG Bitmap 中每一个地址的第二位,以及在初始化每个进程的 bitmap 时 __guard_fids_table 中每一个 RVA 条目的一个标记字节。
对于 64 位的系统,地址的第 9-63 位被用于在 CFGbimap 中检索一个 qword,第 3-10 位被用于(模64)访问 qword 中某一指定位。在导出被抑制后,CFG 允许一个给定的地址在 CFG bitmap 中用两位表示。此外,在大多数 DLLs 中__guard_dispatch_icall_fptr 现在被设置为指向 ntdll!LdrpDispatchUserCallTargetES,在其中一个合法的调用目标必须从 CFG bitmap 中删去。
当你把动态解析符号表考虑进去的时候,实现这样一个导出表抑制变得有点复杂,因为使用 GetProcAddress 意味着随后的代码也能调用返回值作为函数指针。只要 CFG bitmap 中每一个条目没有被标记为敏感的或是不合法的(例如,VirtualProtect, SetProcessValidCallTargets等等),执行流保护可以通过把条目对应的两位从“10”(导出表抑制)改为“01”(合法的调用位置),解决这个问题。最后,一些导出表将会在进行创建时以不合法的间接调用开始,但最终在运行时代码中成为合法的调用目的地址。在今后我们的讨论中,这尤为重要。当这一情况发生时,一个调用栈的样例如下:
00 nt!NtSetInformationVirtualMemory
01 nt!setjmpex
02 ntdll!NtSetInformationVirtualMemory
03 ntdll!RtlpGuardGrantSuppressedCallAccess
04 ntdll!RtlGuardGrantSuppressedCallAccess
05 ntdll!LdrGetProcedureAddressForCaller
06 KERNELBASE!GetProcAddress
07 USER32!InitializeImmEntryTable
Schuster et al.认为 COOP 是 CFI 实现的一个潜在的弱点。为了在绕过 forward-edge CFI 的检查之后执行代码,我们可以利用连续的攻击序列和重用已存在的虚函数。在 ROP 在有一个相似的方法,其结果是一系列小段合法函数,每一段代码实现最低限度的功能(例如,载入一个值进 RDX 中),但把它们组合在一起,却可以实现一些复杂的任务。COOP 的一个基本组成部分就是利用主循环函数,在其中可以迭代对象链表或数组,调用每个对象中的虚函数。然后,攻击者把内存中“伪装”的对象组合起来,在某些情况下,可能会覆盖对象,这样就能在主循环中按攻击者安排好的顺序调用合法的虚函数。Schuster et al.证明了使用 COOP payloads 的攻击 win7 32 位和64 位上的 IE10,以及 Linux 64 位上的 Firefox 的方法。这项研究随后被扩展了,证明了递归或是带有许多非直接调用的函数也可以实现这一过程,而不仅仅是循环。随后又继续被扩展到用于攻击 Objective-C 运行时环境。
这项前沿研究极其有趣和新奇。我们想要把这一概念应用到一些现代的 CFI 实现上,以对如下方案进行评估:
a) 在加固的浏览器中构造一个 COOP payload 的难度;
b) 是否能绕过 CFG 和 HA-CFI;
c) 是否能改进CFI使其能检测到 COOP 类型的攻击。
我们使用 COOP 主要的目标是 win10 的 Edge,因为它代表着一个全新的 CFG 加固应用,并且它能让我们在内存中使用 JavaScript 来准备我们的 COOP payload。弱点始终是我们小组的兴趣,为了这个目标,我们专注于劫持 CFI 的执行流,并对攻击者作出了下列假设:
1. 任意的读-写原语都是从JavaScript中获得的。
2. 因为在运行时动态地找到小段代码不是这项研究的内容,因此,允许使用硬编码偏移量。
3. 所有微软创意者更新中最近的防御机制都能被使用(例如,ACG,CIG,带导出表抑制的CFG)。
4. 除了使用COOP以外,攻击者不允许以任何方式绕过CFG。
在我们最初的研究里,我们在对微软年度更新(OS build 14393.953)中的Edge的研究中利用了一个 Theori 中的 POC,我们使用创意者更新中的防御机制设计我们的payload,并在开启导出表抑制的win10创意者更新(OS build 15063.138)中对其进行验证。
一个理想的 POC 会执行一些攻击者的 shellcode 或是启动一个应用程序。攻击者的一个经典的代码执行模型,就是把一些内存中被控制的数据映射为+X,然后跳转到包含最新修改过的 +X 区域的 shellcode。然后,我们的真实目的是在 forward-edge CFI 的保护下,产生一个能够执行一些有意义的代码的 COOP payloads。这样一个payload提供了能够进行测试和改善我们的 CFI 算法的数据。进一步说,攻击 Arbitrary Code Guard (ACG) 或是 Edge 的子进程的办法超出了我们的研究范围。我们确定对于 win10 创意者更新研究的最终目标是使用 COOP 来使 CFG 无效,使得在 DLL 内能够跳转或是调用任意位置的代码。因此,我们总结出下面两个主要的 COOP payloads:
1. 对于 win10 年度更新,以及缺少 ACG 保护的程序,我们的 payload 把我们的控制的数据映射为可执行的代码,在使得 CFG 无效后跳转到我们控制的 shellcode 所在区域。
2. 对于 win10 创意者更新,我们的最终目标是仅仅是使 CFG 无效。
下列 Schuster et al. 设想的蓝图,我们的第一业务是商定 COOP 各个组成部分的术语。学术论文将每个重用函数称为虚函数片段(virtual function gadget)或是vfgadget,当我们描述每一个特定类型的 vfgadget 时使用缩写,例如将主循环(main loop)vfgadget 称为 ML-G。我们选择以更为非正式的方式来命令每种类型的gadget。在接下来的帖子中你能找到的术语定义如下:
Looper:对于执行复杂 COOPpayloads(论文中的 ML-G)至关重要的主循环gadget。
Invoker:一个调用 vfgadget 的函数指针。(论文中的 INV-G)
Arg Populator:带一个参数的虚拟函数,它将一个值加载到寄存器中(论文中的LOAD-R64-G),或是移动栈指针或是把值加载进栈中(论文中的 MOVE-SP-G)
与论文相似,我们编写了脚本来帮助我们识别二进制中的 vfgadgets。我们使用了 IPA Python,推理帮助我们找到了 loopers,invokers 和 argument pupulators。在我们的研究中,我们发现了实现 COOP 的实用的方法就是,在返回到 JavaScript 之前,把 vfgadgets 链接到一起并依次执行少量的 vfgadgets。根据需要通过额外的 COOP payloads 重复这个过程。因此,为了我们的目的,我们发现没有必要将二进制代码提升到 IR。然而,将大量 COOP payload 拼接到一起,比如说完全通过重用代码运行一个 C2 socket 线程,也许会需要提升到 IR。对于 vfgadget 的每个子类型,我们定义了一系列规则,并使用它在 Edge(chakra.dll和edgehtml.dll)的两个二进制文件间进行搜索。这些规则中与 looper vfgadget 相关的一部分包括:
1. 出现在 __guard_fids_table 中的函数
2. 包含一个不带参数的间接调用的循环
3. 循环不能影响到参数寄存器
在 vfgadgets 的所有类中,搜索 loopers 是最耗时的。许多潜在的 loopers 有一些限制使其难以使用。我们寻找到的 invokers 不仅需要有调用虚函数指针的 vfgadgets,还要能够在单一的 counterfeit 对象中,一次性又快又容易地填充六个参数的vfgadgets。因此,当尝试调用单个 API 时,COOP 可以使用快捷方式,完全避免对循环和递归的需求,除非需要返回值。在 x64 程序上能够找到许多寄存器对参数寄存器进行填充。值得一提的是,Schuster et al. 的 COOP 论文中根据 mshtml 提出的大量原始 vfgadgets 仍然能在 edgehtml 中找到。然而,我们在我们的成果中添加了一个要求来避免重用这些,而不是为我们的 COOP payloads 寻找新的 vfgadgets。
COOP Payloads
通过脚本语言触发 COOP,我们实际上能把一些复杂的任务从 COOP 中移开,因为一次性把所有东西拼接在一起非常的复杂。我们能使用 JavaScript 来帮助我们,重复调用微型 COOP payload 序列。这也让我们能把诸如算术和条件操作放回 JavaScript中执行,并保留基本的函数重用来为通过 COOP 调用重要的 API 做准备。此外,我们展示了这种方法的一个例子,包括在我们劫持到的 #1 section 中将 COOP 的返回值传回到 JavaScript,并讨论如何调用 LoadLibrary。
为了简洁,我将只介绍最简单的 payloads。payloads的一个公共的主题是需要调用VirtualProtect。因为 VirtualProtect 和 eshims(译者注:应该是ieshims)APIs 被标记为敏感的且在 CFG 中并不是一个合法的目的地址,我们不得不在创意者更新中使用包装函数。正如 Thomas Garnier 所建议的那样,可以在 .net 库 mscoree.dll 和 mscories.dll 中方便地找到包装函数,例如 UtilExecutionEngine::ClrVirtualProtect。因为微软的 ACG 可以防止创建新的可执行内存,以及把已有可执行内存改为可写,因此,我们需要一个替代方法。使用 VitualProtect 可以把只读内存重映射为可写的,所以我借用了 2015 年黑帽大会里介绍的这种技术,并将包含 chakra! __guard_dispatch_icall_fptr 的页面重新映射为可写,然后重写函数指针,使其指向包含 jmp rax 指令的 chakra.dll 中的任意位置。事实上,在大多数 DLL 中已经存在一个函数__guard_dispatch_icall_nop,它刚好就是一个单一的 jmp rax 指令。因此,我就能有效地绕过 CFG 的保护,因为在通过了所有检查之后,在 chakra.dll 中所有被保护的调用位置将立即跳转到目的地址。想必我们可以采用这种方法进一步探索使用函数重用攻击 ACG 的方法。为了完成这个小小的链接过程,需要以下满足以下条件:
1. 把 mscoroc.dll 载入进 Edge 进程
2. 在 chakra.dll 的只读内存区域调用 ClrVirtualProtect +W
3. 重写 __guard_dispatch_icall_fptr 以通过检查
从上面的 vfgadgets 列表可以看出,对于 COOP 来说 edgehtml 是一个重要的库。因此,我们的第一任务就是泄漏 edgehtml 的基址以及其他必要的组件,例如我们的 counterfeit 内存区域。这样,payload 就能包含硬编码的偏移并在运行时重新定位。使用 Theori 的 POC 中泄漏的 bug,我们就能获得我们想要的基地址。
//OS Build 10.0.14393
var chakraBase = Read64(vtable).sub(0x274C40);
var guard_disp_icall_nop = chakraBase.add(0x273510);
var chakraCFG = chakraBase.add(0x5E2B78); //_guard_dispatch_icall...
var ntdllBase = Read64(chakraCFG).sub(0x95260);
//Find global CDocument object, VTable, and calculate EdgeHtmlBase
var [hi, lo] = PutDataAndGetAddr(document);
CDocPtr = Read64(newLong(lo + 0x30, hi, true));
EdgeHtmlBase = Read64(CDocPtr).sub(0xE80740);
//Rebase our COOP payload
rebaseOffsets(EdgeHtmlBase, chakraBase, ntdllBase, pRebasedCOOP);
使用 COOP 的一个关键部分就是在最初把 JavaScript 传递进 looper 中。使用我们假设的 R/W 原语,我们可以轻易地劫持到 chakra 的 vtable,使其指向我们的 looper,但我们怎么确保 looper 会开始迭代我们 counterfeit 的数据呢?对于这个答案,我们需要进 looper 进行评估,在这里我使用了 CTravelLog::UpdateScreenshotStream:
注意在循环前的第一个块中,代码是在 +0x30 处获取到链表的指针。为了正确启动looper,我们需要劫持 JavaScript 对象的 vtable,使其地址包含在我们的 looper 中,然后在对象 +0x30 处放置一个指针使其指向counterfeit对象列表的首部。实际的counterfeit 对象数据可以通过 JavaScript 进行定义和重新定位。还要注意,循环在对象 +0x80h 处的的下一个指针列表处进行迭代。当构造 counterfeit 流时这很重要。此外,请注意,这个间接调用的位置在 vtable+0xF8h 处。在 counterfeit 对象中的任意伪 vtable 都必须指向设计好的函数指针减0xF8h处,这个地址通常是在邻接 vtable 表的中间。为了启动 COOP 的 payload,我劫持了 JavascriptNativeIntArray 对象,并地freeze() 和 seal() 虚函数进行了重载,如下所示:
本文由 看雪翻译小组 梦野间 编译,来源 Matt Spisak@Endgame
戳👇 图片加入看雪翻译小组哦!
往期热门内容推荐
更多优秀文章,长按下方二维码,“关注看雪学院公众号”查看!
看雪论坛:http://bbs.pediy.com/
微信公众号 ID:ikanxue
微博:看雪安全
投稿、合作:www.kanxue.com