原文链接:https://www.zerodayinitiative.com/blog/2018/12/17/seeing-double-exploiting-a-blind-spot-in-memgc
这是我们评选的2018年五大有趣案例的第一个。这些评选出来的bug都具有一些独特的元素,使得其与今年发布的大约1400条报告不同。我们首先来看Pwn2Own冠军的一个案例,以看似不可能的方式来攻击微软Edge浏览器。
在2018年Pwn2Own大会上,Richard Zhu(fluorescence)成功攻陷多个目标,获得了世界破解大师(Master of PWN)的称号。他攻陷的目标之一是Microsoft Edge,使用的利用链包括两个Use-After-Free (UAF)漏洞。其中一个UAF漏洞十分引人注目,以至于被列为我们今年的五大漏洞之一,将会在本系列博客中对此进行详细介绍。这个漏洞编号是CVE-2018-8179
我们来深入研究这个漏洞的一些PoC代码,看看是什么让它如此惊人:
图片显示了poc代码和一些指示操作顺序的注释。主要操作是从步骤3中setRemoteCandidates的调用开始的,这个API需要传入一个JavaScript数组。如图中所示,在遍历该数组过程中出现了问题。在访问arr2[1]时,执行了getter方法(步骤4-5),该脚本能够释放arr2[0]最初引用对象所使用的内存。然后脚本回收内存并用攻击者指定的数据覆写内存(步骤5)。当setremotecandidate继续执行并试图访问arr2[0]最初引用的对象时,就会发生崩溃。
为了实现这幅图中的内容,我们需要更多地了解setremotecandidate处理其参数时发生的事情,它做了如下操作:
- 创建一个名为CModernArray<>的内部数组结构。
- 遍历arr2。对于每个元素,获取一个指向该元素的指针,并将其添加到CModernArray<>中。
- 迭代CModernArray<>,依次处理每个JavaScript对象。
CModernArray<>是在edgehtml.dll中定义的一个c++类。至关重要的是,它将数据存储在从MemGC堆分配的缓冲区中。概括总结下POC的操作:edgehtml.dll在arr2上迭代。在此过程中,它首先将arr2[0]复制到属于CModernArray<>的memgc控制的缓冲区,然后,当访问arr2[1]时,已经从arr2[0]复制的JavaScript对象将被释放并回收,但是CModernArray<>中仍然存在一个未清空的指针。当从CModernArray<>的索引0检索该指针时就会产生崩溃。
把所有这些信息汇总在一起后,我们现在可以理解这里存在一个漏洞是多么不可思议。在整个过程中,所有涉及的对象(JavaScript数组等)都被分配到MemGC堆上。此外,所有指向这些对象的指针都存储在memgc分配的数组和缓冲区中。那么,UAF是如何产生的呢?这类UAF正是MemGC所应该预防的。MemGC被设计成能够识别MemGC堆中当前存在的所有指针,这样当指针仍然存在时,就不能释放MemGC分配的内存。在MemGC堆分配中,MemGC应该是无所不知、无所不见的。那么,为什么MemGC没有检测到CModernArray<>中存在一个未清空指针这一事实呢?
为什么会这样呢?
我现在要告诉你一个可怕的秘密。
并没有这样一个无所不知的“MemGC堆”
实际上有两个MemGC堆,它们对彼此的分配是不可见的。
这两个MemGC堆如下所示,其中一个MemGC堆会在浏览器的JavaScript引擎Chakra中内部使用,所有基于堆的JavaScript对象以及许多内部Chakra数据结构都存储在这个堆上,我们称之为“Chakra堆”。另一个MemGC堆是由chakra.dll提供给外部使用者使用的,尤其是给edgehtml.dll用。我们将其称为“DOM堆”,它取代了前一代的,由Internet Explorer在2014年7月首次引入的内存保护机制。DOM堆用于所有DOM对象,以及从edgehtml.dll执行的大多数其他堆分配。
这两个堆共享一个实现,但是它们由chakra!Memory::Recycler类的两个不同实例表示。当垃圾回收发生时,在“标记”阶段,回收程序会扫描所有存活的堆分配,以及堆栈和处理器寄存器,寻找指向其他堆分配的指针,以便也可以将这些指针标记为活动的。由于并不是内存中的每个值都是真正的指针值,所以这次扫描会得到一些无关的结果。为了过滤掉这些无关结果,Recycler将自动拒绝属于堆分配范围之外的任何值。但是这种决定只是基于某个堆的,一个Recycler实例并不知道其他Recycler实例可能正在使用的地址区域。此外,它不能在属于其他Recycler实例的MemGC分配上放置“标记”。
顺便提一下,甚至有存在两个以上的MemGC堆。JavaScript执行的每个线程都有自己的Chakra堆实例。通常这不会造成问题,因为JavaScript对象不会与创建它的线程以外的任何线程上的代码交互。
现在我们可以理解在运行图中poc代码时发生了什么,在步骤5(见图)中,内存压力迫使Chakra堆进行垃圾收集。这是由与Chakra堆相关的Recycler实例执行的。在扫描堆栈时,recycler会遇到指向CModernArray<>缓冲区的指针,然而,它会立即筛选掉这个指针,因为CModernArray<>缓冲区已经分配到DOM堆上,而不是Chakra堆上。因此,Chakra堆的Recycler从不扫描CModernArray<>缓冲区的内容,导致它会筛掉其中所包含的指向Chakra堆分配内存的未清空指针。
总结
我们已经证明,“MemGC是无所不知的”这种概念是一种误解。虽然MemGC作为一种缓解措施非常成功,但它并非完全没有缺点。
这对于考虑如何打补丁也是有指导意义的.现在,在将每个对象添加到CModernArray<>之前,edgehtml调用chakra::JsVarAddRef来显式地将对象固定在内存中。至于edgehtml!ORTC::UnpackArrayObjectVar这种情况,MemGC陷入了困境,edgehtml中的代码必须求助于手动对象生命周期管理。
你可以关注我的Twitter@HexKitchen,或者关注我们的团队以了解最新的漏洞利用技术和安全补丁。请继续关注下一个年度五大漏洞相关博客,它将于明天发布。