写在前面的话
上周,Google曾发布过一系列博客文章来对五个iOS漏洞利用链进行过详细分析。据了解,Google的威胁分析团队(TAG)在2019年2月份发现了有攻击者正在使用这些iOS漏洞利用链来实施攻击,本人此前也对其中的第一条漏洞利用链进行过分析,那么在这篇文章中,我将会对这条漏洞利用链中的iOS内核堆栈进行分析,并使用该漏洞利用链中的技术来实现堆溢出攻击。
堆概念回顾
在开始讨论具体的技术实现细节之前,我们需要大致回顾一下iOS内核堆的概念以及堆溢出是如何实现的。
堆环境,是采用了类似C或C++这种编程语言开发的应用程序的上下文环境,它允许函数分配一个内存的临时独占区来存储变量和结构化数据。分配完成后,这些内存区域将被永久保留,直到程序员手动将内存释放回堆管理器,此时堆管理器可以“回收”它以供其他代码或程序使用。
为了保证程序的正常运行,使用堆环境的程序员需要遵循一些简单的规则,而这一点非常重要。比如说,它们必须避免写入已分配区域之外的地址,而且还要小心不要在已释放回堆的分配内存中写入或读取数据。
如果不遵循这些规则,堆的独占性保证将失效。这可能会导致不同的变量意外地被分配到相同的底层内存地址。这些变量可能具有不同的安全属性,例如一个可能包含要处理的数据,另一个则包含安全关键属性(如函数指针),而攻击者将有可能利用这种特性来接管某个进程,但在一般情况下,接管进程的是iOS操作系统内核。
iOS堆溢出漏洞利用分析
在本文所介绍的iOS漏洞利用技术中,主要的目标是iOS的图形驱动器。这个图形驱动器中有一个对象,该对象通过IOMalloc函数进行分配,而这个分配器使用了kalloc分配器,其中存在的堆溢出漏洞将导致该对象的数据溢出到kalloc分配的相邻对象中。
kalloc堆分配器的工作方式与glibc堆分配器不同,但是它的大致用途基本相同,即它允许内核驱动程序开发人员在管理系统的正常过程中为变量分配和释放内存。
这里需要注意的是,kalloc的对象分配操作是与操作系统无关的,设备上运行的应用程序是看不到这些对象分配的位置和数据内容的。其次,使用kalloc分配的对象通常不会使用活动对象之间的堆元数据来跟踪分配块长度之类的信息。当释放kalloc堆分配时,开发人员必须提醒kfree目标区域的原始分配长度,以便kfree可以将其回收并用于其他分配。如果错误这个原始分配长度出现错误,则会触发区域传输漏洞,从而导致堆溢出,不过iOS 9之后的iOS系统现在已经有了抵御此类攻击的缓解措施。出于这个原因,iOS内核开发人员通常会利用更加易于使用的API来间接调用kalloc,比如说_MALLOC。
kalloc分配器在内部使用区域分配策略(通过zalloc实现),它会根据分配目标的大小将分配集群在一起。例如,大小为4096字节的分配将与其他4096字节的分配一起分配到kalloc.4096区域中,而大小为2048的分配将在kalloc.2048区域中分配。这意味着,当2048字节kalloc分配的对象发生堆溢出时,在kalloc.2048区域中,被损坏的相邻受害者对象大小是相似的。
那么在这个特定的攻击中,如果目标对象的长度可以被修改,那么就会存在该漏洞。因此,漏洞利用开发人员可以选择在哪个kalloc区域触发漏洞。开发人员可以选择在kalloc.4096区域中分配他们所感兴趣的对象,这个区域比那些存储空间小的区域操作次数要少,因为程序倾向于分配和回收分配较小的区域,因此存储空间小的区域操作会更频繁。
漏洞成因
iOS图形驱动器的开发人员在这里对复杂输入数据结构做了一个错误的“假设”,并根据这个错误的“假设”来设定堆内存的分配大小。通过提供错误的数据输入,内核将会向堆分配区域写入比原本更多的数据条目,而最后的几个数据条目将会溢出到与其相邻的其他对象中。
漏洞利用
线性堆溢出漏洞通常允许利用漏洞的开发人员将攻击者控制的数据溢出到内存分配限制之外的区域,但在这里情况并非如此,溢出的数据必须始终是指向IOAccelResource2对象的指针。攻击者无法控制该指针的值,也无法控制其指向的数据。
像这种域控制漏洞通常很难利用,但我们也可以使用一些创造性的方法来将该漏洞转换为其他更容易利用的类型。我们可以使用堆溢出来构造一个“释放后使用”漏洞,这样我们就可以构建出一个更容易利用的漏洞了。
首先,攻击者需要找到一个合适的目标对象,该对象的第一个字段指向目标对象控制下的缓冲区指针。这个缓冲区需要在一个攻击者可控的时间释放,也可以是在目标对象本身被释放的时候。
利用漏洞开发人员现在可以触发线性堆溢出,此溢出将导致目标对象的指针值被替换为指向IOAccelResource2对象的指针。
下一步是触发资源数据本身的释放,这将导致目标对象的指针为空,内核中目前还没有对象希望这个指针为空,而此时所有需要跟该对象指针交互的行为都将出现用后释放事件。与此堆溢出相比,释放后使用漏洞将为攻击者提供更大的控制范围,因此这是一个相对可靠的策略。
那么问题来了:攻击者应该选择哪个对象来进行攻击呢?
攻击者在选择攻击对象时有一些限制,目标对象必须与溢出对象相邻,因此利用攻击者需要使其位于资源区附近的同一个kalloc.4096区域中,并且要在它的开头附近分配一个kalloc指针,并使它在一个可控的时间释放那个指针。
在我们的分析样例中,攻击者使用了recv_msg_elem数组作为攻击对象。单个recv_msg_elem对象的长度只有32字节,但在recvmsg_x系统调用开始时,可以同时分配连续的组。通过操纵recvmsg_x系统调用的参数,我们可以安排同时分配其中的几个参数,从而将分配作为一个整体注入到kalloc.4096区域。此数组中的每个recv_msg_元素都以指向uio结构的指针开头,它是基于一个可控长度字段并通过kalloc分配的,并且最终kfree在系统调用结束时会释放recv_msg_elem数组。
选择这个recv_msg_elems数组作为攻击对象,满足了我们所有的需求,并且可以将堆溢出转化为用后释放漏洞来使用。
我们的操作顺序如下:首先,我们安排一个recv_msg_elems数组,位于kalloc.4096的resources_buf对象旁边。接下来,我们触发resources_buf的溢出,它将用指向IOAccelResource 对象的指针覆盖第一个recv_msg_elems数组项的uio指针。
接下来,我们释放IOAccelResource2对象和资源缓冲区,但是recv_msg_elem数组的指针覆盖仍然存在,继续指向IOAccelResource2对象的位置,但现在是空的。目标recv_msg_elems对象没有理由相信其uio对象已被释放,但对它的任何使用现在都会导致触发用后释放漏洞。
其实使用RevixMSGEELEM数组作为攻击对象,还有一个麻烦。此次分配操作发生在recvmsg_x系统调用的开始,但该系统调用会阻塞等待消息到达的线程。当请求完成或被取消时,recvmsg_x将释放其内部的目标对象。这也就意味着,系统调用将需要发生在一个从属线程上,因为它可能阻塞调用线程。
参考资料
1、https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
2、https://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/
3、https://media.blackhat.com/bh-us-12/Briefings/Esser/BH_US_12_Esser_iOS_Kernel_Heap_Armageddon_WP.pdf
*参考来源:azeria-labs,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM