最近的iOS内核漏洞调查

2020-06-12 17:24:04

最近,我发现自己希望有一份在线参考,提供近年来每个公开的iOS内核漏洞利用的高级利用流的简要摘要;由于没有这样的文档,我决定在这里创建它。

本文总结了针对IOS 10到IOS 13的本地应用程序上下文中的原始IOS内核攻击,重点介绍了从漏洞授予的初始原语到内核读/写的高级利用漏洞流。在这篇文章的最后,我们将简要介绍IOS内核利用漏洞的缓解措施(硬件和软件方面),以及它们如何与利用漏洞中使用的技术相对应。

这不是你典型的P0博客文章:没有扣人心弦的零日攻击,没有新奇的攻击研究,也没有惊心动魄的恶意软件逆向工程。因为我需要这些信息,并认为其他人可能也会觉得有用,所以我写了这些内容作为参考。已经事先警告过你了。

不幸的是,没有一本名为“安全研究人员技术黑客术语”的权威词典,这使得我很难准确地描述我想要传达的一些高级概念。为此,我决定为这篇文章的上下文赋予以下术语特定的含义。如果这些定义中的任何一个与您对这些术语的理解不一致,请随时建议改进术语,我可以更新此帖子。:)。

常见利用漏洞原语的几个示例包括:N字节线性堆溢出、受控地址处的整数递增、Write-What-Where、任意内存读/写、PC控制、任意函数调用等。

特定于IOS内核攻击的常见利用漏洞原语是拥有对假MACH端口(Struct IPC_PORT)的发送权限,该端口的字段可以直接从用户空间读取和写入。

利用策略:用于将漏洞转换为有用的利用原语的低级、特定于漏洞的方法。

例如,这是Ian Beer针对iOS 11.1.2的Async_Wake利用中使用的利用策略:

信息泄漏被用来发现任意MACH端口的地址。分配一页端口,并基于其地址从该页中选择特定端口。IOSurfaceRootUserClient漏洞被触发以释放Mach端口,从而产生对已知(和部分控制)地址处的悬空Mach端口的接收权限。

最后一部分是通用/与漏洞无关的原语,我将其解释为特定于漏洞的利用策略的结束。

通常,利用策略的目标是产生高度可靠的利用原语。

利用技术:将一个利用原语转换为另一个(通常更有用)利用原语的可重用和合理通用的策略。

面向返回的编程(ROP)就是利用漏洞技术的一个示例,它通过重用可执行代码小工具将任意PC控制转变为(几乎)任意代码执行。

特定于IOS内核攻击的攻击技术是使用伪MACH端口通过调用pid_for_task()读取4字节的内核内存(将对伪MACH端口的发送权限转换为任意内核内存读取原语)。

利用流:高级、漏洞无关的利用技术链,用于将漏洞授予的利用原语转换为最终目标(在本文中,内核从本地应用程序上下文读取/写入)。

本节将简要概述针对IOS 10到IOS 13的本地上下文中的IOS内核利用漏洞攻击。我将描述高级利用漏洞攻击流程,并列出实现该流程所使用的利用漏洞攻击原语和技术。虽然我试图追踪每一个原始的(即,在漏洞利用代码发布之前开发的)公共利用漏洞,但我预计我可能遗漏了一些,无论是作为源代码还是作为足够完整的写作/演示文稿。请随时联系并提出我遗漏的任何建议,我可以更新这篇帖子。

对于每个漏洞,我都概述了漏洞、漏洞策略(特定于漏洞)和后续的漏洞流(一般)。漏洞攻击的哪些部分是特定于漏洞的,哪些部分足够通用,可以视为整个流程的一部分,这两者之间的界限是主观的。在每种情况下,我都强调了我认为足够通用的漏洞授予的特定利用原语。

漏洞:CVE-2016-7644是XNU的set_dp_control_port()中的争用条件,会导致Mach端口过度释放。

利用策略:分配许多MACH端口,并通过竞相SET_DP_CONTROL_PORT()删除对它们的引用(可以确定竞争何时获胜)。通过删除隐藏的引用来释放端口,让持有对悬挂的Mach端口的接收权限的进程填充一页内存。

后续攻击流:通过调用mach_zone_force_gc()强制执行区域垃圾收集,并使用包含指向主机端口的指针的行外(OOL)端口数组重新分配悬挂端口的页面。在其中一个悬挂端口上调用MACH_PORT_GET_CONTEXT()以泄露主机端口的地址。使用此值,可以猜测内核任务端口所在的页面。每个悬空端口的上下文值被设置为包含内核任务端口的页面上每个潜在IPC_端口的地址,并且OOL端口被接收回用户空间,以向内核任务端口授予发送权限。

谷歌威胁分析小组的Clément Lecigne(@_clem1)在野外发现了这种病毒。Google Project Zero的伊恩·比尔(Ian Beer)和塞缪尔·格罗伯(Samuel Groo)(@5aelo)分析了这一点。

漏洞:该漏洞是对IOkit函数AGXAllocationList2::initWithSharedResourceList()中的IOAccelResource指针的线性堆越界写入。

利用策略:要溢出的缓冲区直接放在recv_msg_elem结构之前,这样越界写入将使用IOAccelResource指针覆盖uio指针。IOAccelResource指针被释放并使用假的UIO结构重新分配,该结构位于由IOSurface属性管理的OSData数据缓冲区的开头。UIO被释放,留下一个悬挂的OSData数据缓冲区可通过IOSurface属性访问。

后续攻击流程:使用IOSurfaceRootUserClient实例重新分配挂起的OSData数据缓冲区插槽,并通过IOSurface属性读取数据内容以提供KASLR幻灯片、当前任务的地址和挂起的数据缓冲区/IOSurfaceRootUserClient的地址。然后,使用IOSurfaceRootUserClient的修改版本释放并重新分配数据缓冲区,以便在修改后的用户客户端上调用外部方法将返回从内核s__data段读取的内核任务的地址。再次释放和重新分配数据缓冲区,以便调用外部方法将执行OSSerializer::Serialize()小工具,从而导致任意读写,将内核任务端口的地址存储在当前任务的特殊端口列表中。从用户空间读取特殊端口赋予内核任务端口发送权限。

漏洞:CVE-2017-2370是可从XNU的mach_voucher_Extract_Attr_Recipe_Trap()中的非特权上下文访问的线性堆缓冲区溢出,原因是攻击者控制的用户空间指针用作调用copy in()的长度。

攻击策略:调用易受攻击的Mach陷阱来创建kalloc分配,并立即用受控数据溢出,从而损坏后续IPC_KMSG对象的IKM_SIZE字段。这会导致IPC_KMSG(为MACH端口预先分配的消息)相信它的容量比它大,从而与后续分配的前240个字节重叠。通过将MACH端口注册为用户空间线程的异常端口,然后使具有受控寄存器状态的线程崩溃,可以重复且可靠地重写后续分配的重叠部分,并且通过接收异常消息可以读取那些字节。这在损坏的IPC_KMSG的末尾提供了一个受控的240字节越界读/写原语。

后续攻击流:将第二个IPC_KMSG放在损坏的IPC_KMSG之后并读取,以便确定分配的地址。接下来,在相同的槽中重新分配AGXCommandQueue用户客户端,并读取虚拟方法表以确定KASLR幻灯片。然后覆盖虚拟方法表,以便AGXCommandQueue上的虚拟方法调用调用OSSerializer::Serialize()小部件,从而产生一个双参数任意内核函数调用原语。调用函数uuid_copy()会产生任意内核读/写原语。

攻击策略:调用易受攻击的Mach陷阱来创建kalloc分配,并立即用受控数据溢出,覆盖OOL端口数组的内容,并在用户空间中插入指向假Mach端口的指针。接收包含OOL端口的消息产生对其内容可以直接控制的伪MACH端口的发送权。

随后的攻击流程:假Mach端口被转换为时钟端口,并且CLOCK_SLEEP_TRAP()用于暴力强制内核映像指针。然后将该端口转换为伪任务端口,以通过pid_for_task()读取内存。内核内存从泄漏的内核映像指针向后扫描,直到找到内核文本库,从而破坏KASLR。最后,构造了一个伪内核任务端口。

漏洞:AppleAVE2中存在多个漏洞,原因是外部方法与用户空间共享IOSurface指针并信任从用户空间读取的IOSurface指针。

攻击策略:创建IOSurface对象并调用AppleAVE2外部方法来泄漏其地址。IOSurface中IOFence指针的vtable使用另一个外部方法调用泄漏,从而破坏KASLR。使用IOSurface属性喷射释放IOSurface对象,并使用受控数据重新分配该对象。将泄漏的指针提供给信任从用户空间提供的IOSurface指针的AppleAVE2外部方法允许劫持假IOSurface上的虚拟方法调用;这将被视为OneShot劫持的虚拟方法调用,其中受控目标对象位于已知地址。

后续利用流程:劫持的虚拟方法调用与OSSerializer::Serialize()小工具一起使用,以调用Copin()并覆盖2个sysctl_oid结构。sysctl被覆盖,以便读取第一个sysctl调用copy in()来更新第二个sysctl的函数指针和参数,而读取第二个sysctl使用OSSerializer::Serialize()小部件调用带有3个参数的内核函数。这个带有3个参数的任意内核函数调用原语用于通过调用copy in()/copy out()来读写任意内存。

注意:iOS10.3引入了TASK_CONVERSION_eval()的初始形式,这是一种微弱的缓解措施,阻止用户空间访问实际内核任务端口的权限。IOS 10.3之后的任何攻击都需要构建一个假的内核任务端口。

漏洞:CVE-2017-13861是IOSurfaceRootUserClient::s_set_surface_notify()中的漏洞,可导致在马赫端口上丢弃额外的引用。cve-2017-13865是xnu的proc_list_uptrs()中的漏洞,在将内容复制到用户空间之前,无法完全初始化堆内存,从而泄漏内核指针。

攻击策略:利用信息泄漏发现任意MACH端口的地址。分配一页端口,并基于其地址从该页中选择特定端口。使用IOSurfaceRootUserClient缺陷解除端口分配,从而获得对已知(和部分控制)地址处的悬挂Mach端口的接收权限。

后续利用信息流:释放该页上的其他端口,并强制执行区域垃圾收集,以便使用IPC_KMSG的内容重新分配该页,从而在已知地址提供具有受控内容的假MACH端口。重新分配将端口转换为伪任务端口,通过该端口可以使用pid_for_task()读取任意内核内存。(使用mach_port_set_context()更新要读取的地址,而不重新分配伪端口。)。使用内核读取原语定位相关内核对象,并使用伪内核任务端口再次重新分配伪端口。

注意:IOS 11删除了mach_zone_force_gc()函数,该函数允许用户空间提示内核执行区域垃圾收集,回收区域映射中所有空闲的虚拟页面以供其他区域使用。利用iOS 11和更高版本的漏洞需要开发一种强制区域垃圾收集的技术。为此,至少已经开发了三种独立的技术,如async_wake、v0rtex和未开发的IOS利用链3所示。

利用策略:分配两个MACH端口,端口A和端口B,作为喷射的一部分。该漏洞被触发以丢弃端口A上的引用,并释放A周围的端口,从而导致端口指针悬空。通过调用mach_zone_force_gc()强制执行区域垃圾收集,并使用包含模式的OOL端口Sprake重新分配包含端口A的IP_CONTEXT字段的页面,使得端口A的IP_CONTEXT字段与指向端口B的指针重叠。调用mach_port_get_context()会给出端口B的地址。端口B会再次触发该漏洞,从而获得对已知地址处悬空的MACH端口的接收权限。

后续利用流程:在另一个区域垃圾收集之后,使用分段的OOL内存喷射重新分配悬挂端口B,以便调用mach_port_get_context()可以识别喷射重新分配的端口B的哪个4MB段。该段被释放,并且端口B使用管道缓冲区重新分配,从而在已知地址提供受控的伪MACH端口。(=。伪端口被转换为时钟端口,CLOCK_SLEEP_TRAP()用于暴力强制KASLR。接下来将伪端口转换为伪任务端口,并使用pid_for_task()建立一个4字节的内核读取原语。最后,将伪端口转换为伪内核任务端口。

攻击策略:使用该漏洞喷洒MACH端口并丢弃一个端口上的引用。页面上的其他端口被释放,将接收权限留给悬挂的Mach端口。

后续攻击流:使用MACH_ZONE_FORCE_GC()强制执行区域垃圾收集,并通过IOSurface属性喷射使用OSString缓冲区重新分配包含悬挂端口的页面。OSString缓冲区包含一个模式,该模式初始化端口的关键字段,并允许通过在伪端口上调用mach_port_get_context()来确定包含端口的OSString的索引。释放包含伪端口的OSString,并将其重新分配为正常的Mach端口。调用MACH_PORT_REQUEST_NOTIFICATION()将真实MACH端口的地址放入伪端口的IP_pdrequest字段,并通过IOSurface读取OSString的内容以获得地址。再次使用MACH_PORT_REQUEST_NOTIFICATION()来获取伪端口本身的地址。

字符串缓冲区被释放并重新分配,以便mach_port_get_properties()可以用作4字节的任意读取原语,而要读取的目标地址可通过mach_port_set_context()进行更新。(这类似于pid_for_task()技术,但约束略有不同。)。从实际Mach端口的地址开始,读取内核内存以查找相关的内核对象。字符串缓冲区被释放并再次使用一个假任务端口重新分配,该端口足以将字符串缓冲区重新映射到进程的地址空间。通过映射更新伪端口,以使用iokit_user_client_trap()生成7参数任意内核函数调用原语,并调用内核函数以生成伪内核任务端口。

漏洞:CVE-2018-4150是XNU的BPF子系统中的争用条件,由于在未重新分配相应缓冲区的情况下增加缓冲区长度,导致线性堆缓冲区溢出。

利用策略:触发竞争以不正确地增加缓冲区长度,而不重新分配缓冲区本身。数据包被发送并存储在缓冲区中,溢出到后续的OOL端口数组中,并插入指向用户空间中的伪MACH端口的指针。接收包含OOL端口的消息产生对其内容可以直接控制的伪MACH端口的发送权。

随后的攻击流程:假Mach端口被转换为时钟端口,并且CLOCK_SLEEP_TRAP()用于暴力强制内核映像指针。然后将该端口转换为伪任务端口,以通过pid_for_task()读取内存。内核内存从泄漏的内核映像指针向后扫描,直到找到内核文本库,从而破坏KASLR。攻击的最后部分尚未完成,但在此阶段使用现有代码构建假内核任务端口将是直接和确定的。

漏洞:CVE-2018-4241是XNU';的mptcp_usr_connectX()中由于不正确的边界检查造成的对象内线性堆缓冲区溢出。

利用策略:内核堆经过整理,将一个2048字节的ipc_kmsg结构放置在与几个多路径TCP套接字关联的mptses结构(包含溢出的对象)下方的16MB对齐地址上。该漏洞用于使用零覆盖mptses结构中mpte_itfinfo指针的低3个字节,并关闭套接字。这将触发损坏指针的kfree(),释放16MB对齐边界处的ipc_kmsg结构。释放的IPC_KMSG插槽重新分配有喷射的管道缓冲区。再次触发该漏洞,用零覆盖另一个mptses结构中mpte_itfinfo指针的低3个字节,并关闭套接字,从而导致另一个相同地址的kfree()。这将释放刚刚分配到该插槽中的管道缓冲区,留下一个悬挂的管道缓冲区。

后续攻击流:使用预先分配的IPC_KMSG再次重新分配插槽。用户空间线程崩溃,导致消息存储在与管道缓冲区重叠的预先分配的ipc_kmsg缓冲区中;读取用户空间中的管道会产生ipc_kmsg结构的内容,给出悬挂管道缓冲区/ipc_kmsg的地址。写入管道是为了更改ipc_kmsg结构的内容,以便接收消息会产生对管道缓冲区内的伪MACH端口的发送权限。接收到异常消息,并使用pid_for_task()重写管道以将伪端口转换为内核读取原语。定位相关内核对象,将伪端口转换为伪内核任务端口。

利用策略:对内核堆进行整理,以便将预先分配的4096字节的ipc_kmsg结构放在几个多路径TCP套接字的mptses结构附近。该漏洞被触发两次,以损坏两个MPTS结构中mpte_itfinfo指针的低2个字节,从而关闭套接字会导致两个损坏指针的kfree()。每个指针都被损坏,将0x7a0字节指向IPC_KMSG分配,从而创建了跨越2条消息的4096字节的漏洞。包含部分释放的IPC_KMSG结构之一的MACH端口(IPC_KMSG标头完好无损。

..