Project Zero 对近几年 iOS 内核漏洞利用技术的总结 - 嘶吼 RoarTalk – 回归最本质的信息安全,互联网安全新媒体,4hou.com

Project Zero 对近几年 iOS 内核漏洞利用技术的总结

h1apwn 系统安全 2020-06-27 10:20:00
收藏

导语:这篇文章总结了针对iOS 10到iOS 13的本地应用程序上下文中的原始iOS内核利用,重点是从漏洞初始原语到内核读/写的高级利用流程。

我最近希望获得一个在线参考,以简要概述近年来每种公共iOS内核利用程序的利用思路。由于不存在此类文档,因此我决定自己创建一个。

这篇文章总结了针对iOS 10到iOS 13的本地应用程序上下文中的原始iOS内核利用,重点是从漏洞初始原语到内核读/写的高级利用流程。在本文的结尾,我们将简要介绍iOS内核漏洞利用缓解措施(包括硬件和软件),以及它们如何映射到漏洞利用中使用的技术。

这篇文章没有0 day漏洞利用,也没有新颖的漏洞利用研究或令人兴奋的恶意软件的逆向。由于我需要这些信息,并认为其他人也可能会觉得有用,因此这篇文章可以被被用作参考。

0x01 术语说明

不幸的是,没有称为“安全研究人员的技术黑客术语”的权威词典,这使得很难准确描述我想传达的一些高级概念。为此,我决定将以下术语赋予本文特定的含义。如果这些定义中的任何一个与你对这些术语的理解不一致,请随时提出改进的术语。

漏洞利用原语:在漏洞利用过程中开发的,比较通用的函数。

常见漏洞利用原语的一些示例包括:n 字节线性堆溢出,在受控地址处的整数溢出,在何处写入,任意存储器读/写,PC控制,任意函数调用等。

特定于iOS内核利用的常见利用原语是具有对伪造的Mach端口(结构ipc_port )的发送权,该伪造的Mach端口可直接从用户空间读取和写入其字段。

漏洞利用策略:一种特定于漏洞的低级方法,用于将漏洞转变为有用的漏洞利用原语。

例如,这是Ian Beer的iOS 11.1.2的async_wake攻击中使用的攻击策略:

信息泄漏用于发现任意Mach端口的地址。将分配端口页面,并根据其地址从该页面中选择特定端口。所述IOSurfaceRootUserClient 漏洞被触发以解除分配端口,产生一个接收到端口的已知地址。

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

通常,利用策略的目的是产生高度可靠的利用基元。

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

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

针对iOS内核开发的一种利用技术是通过调用pid_for_task()使用伪造的Mach端口读取4字节的内核内存(将对伪造的Mach端口的发送权转换为任意内核内存读取原语)。

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

0x02 自iOS 10起公开的iOS内核漏洞

本节将简要概述从针对iOS 10到iOS 13的本地上下文iOS内核漏洞利用。我将描述高级漏洞利用流程,并列出用于实现该漏洞的漏洞利用原语和技术。虽然我试图追踪每个原始的(即在漏洞利用代码发布之前开发的)公共漏洞利用,它们既可以作为源代码使用,也可以作为完整的文稿使用。

对于每种漏洞利用,我都概述了漏洞细节,漏洞利用策略(特定于该漏洞)以及随后的漏洞利用流程(通用)。漏洞的哪些部分特定于该漏洞与哪些部分的通用程度足以被视为总体流程的一部分之间的界限是主观的。在每种情况下,我都强调了我认为足够通用的漏洞所授予的特殊利用原语。

mach_portal-iOS 10.1.1

由Google Project Zero(@ i41nbeer)的Ian Beer提供。

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

利用策略:set_dp_control_port()分配了许多Mach端口,并删除了对它们的引用,通过删除隐藏的引用来释放端口,从而使处理过程拥有接收权以悬挂Mach端口填充内存页面。

后续利用流程:通过调用mach_zone_force_gc()强制进行区域垃圾回收,并使用包含指向主机端口的指针的离线(OOL)端口数组重新分配悬空端口的页面。在一个悬挂端口之一上调用mach_port_get_context()以公开主机端口的地址。使用此值,可以猜测内核任务端口所在的页面,每个悬挂端口的上下文值设置为包含内核任务端口的页面上每个潜在ipc_port 的地址,并且OOL端口在用户空间中被接收回去,以向内核任务端口提供发送权限。

参考:mach_portal利用代码

iOS漏洞利用链1-iOS 10.1.1

由Google威胁分析小组的ClémentLecigne(@ _clem1)在野外发现。由Google Project Zero 的Ian Beer和SamuelGroß@ 5aelo)分析。

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

利用策略:要溢出的缓冲区直接放置在recv_msg_elem 结构之前,这样越界写操作将使用IOAccelResource 指针覆盖uio 指针。所述IOAccelResource 指针被释放并用重新分配的 UIO 在开始结构OSDATA 数据缓冲器来管理IOSurface 。该UIO 被释放,留下一个悬空OSDATA 数据经由缓冲访问IOSurface 。

后续的利用流程:悬空的OSData 数据缓冲区通过IOSurfaceRootUserClient 实例重新分配,并且数据内容通过IOSurface 属性读取,以提供KASLR保护,当前任务的地址以及悬空数据缓冲区/ IOSurfaceRootUserClient 的地址。然后,释放数据缓冲区并使用IOSurfaceRootUserClient 的patch版本进行重新分配,这样,在修改的用户客户端上调用外部方法将返回从内核的__DATA 段读取的内核任务的地址。数据缓冲区被释放并再次重新分配,以便调用外部方法将执行OSSerializer :: serialize()gadget,导致任意读写后将内核任务端口的地址存储在当前任务的特殊端口列表中。从用户空间读取特殊端口将向内核任务端口发送权限。

参考:iOS漏洞利用链1-AGXAllocationList2 :: initWithSharedResourceList堆溢出

extra_recipe-iOS 10.2

伊恩·比尔(Ian Beer)。

漏洞:CVE-2017-2370是线性堆缓冲区溢出,可通过XNU的mach_voucher_extract_attr_recipe_trap()中的非特权上下文来实现,这是由于攻击者控制的用户空间指针用作对copyin()的调用中的长度。

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

后续漏洞利用流程:在漏洞函数之后放置另一个ipc_kmsg,并对其进行读取,以确定分配的地址。接下来,将AGXCommandQueue 用户客户端重新分配到同一内存中,并读取虚拟方法表以确定KASLR。然后,将覆盖虚函数表,以使AGXCommandQueue 上的虚函数调用将调用OSSerializer :: serialize()gadget,从而生成2参数的任意内核函数调用原语。调用函数uuid_copy()给出任意内核读/写原语。

参考:iOS上的面向异常的利用extra_recipe利用代码

Yalu102-iOS 10.2

由Luca Todesco(@qwertyoruiopz)和Marco Grassi(@marcograss撰写

漏洞:CVE-2017-2370(与上面相同)。

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

后续利用流程:伪造的Mach端口被转换为时钟端口,而clock_sleep_trap()用于对内核映像指针进行暴力破解。然后将端口转换为伪任务端口,以通过pid_for_task()读取内存。从泄漏的内核映像指针向后扫描内核内存,直到找到内核文本库为止,这会破坏KASLR。最后,构建一个伪内核任务端口。

注意:该漏洞利用不适用于启用PAN的情况。

参考:Yalu102漏洞利用代码

ziVA-iOS 10.3.1

Zimperium的Adam Donenfeld(@doadam)。

漏洞:在多个漏洞AppleAVE2 由于外部方法分享IOSurface 与用户空间的指针和信任IOSurface 指针从用户空间读取。

利用策略:创建一个IOSurface 对象,并调用AppleAVE2 外部方法以泄漏其地址。iofence 在指针IOSurface 使用泄露另一个外部方法调用,打破KASLR。所述IOSurface 对象被释放,并与使用控制的数据重新分配IOSurface 属性堆喷。将泄漏的指针提供给信任从用户空间提供的IOSurface 指针的AppleAVE2 外部方法,可以劫持伪造的IOSurface 上的虚函数调用;这被视为在已知地址用受控目标对象进行的oneshot劫持虚函数调用。

后续的利用流程:被劫持的虚函数调用与OSSerializer :: serialize()gadget一起使用,以调用copyin()并覆盖2个sysctl_oid 结构。sysctls被覆盖,因此读取第一个sysctl会调用copyin()来更新第二个sysctl的函数指针和参数,而读取第二个sysctl则使用OSSerializer :: serialize()gadget来调用带有3个参数的内核函数。这个3参数的任意内核函数调用原语用于通过调用copyin()/ copyout()来读写任意内存。

注意:iOS 10.3引入了task_conversion_eval()的初始形式,这是一种较弱的缓解措施,它阻止用户空间访问实际内核任务端口的权限。iOS 10.3之后的任何漏洞利用都需要构建伪造的内核任务端口。

参考:Ro(o)tten appleziVA漏洞利用代码

async_wake-iOS 11.1.2

伊恩·比尔(Ian Beer)。

漏洞:CVE-2017-13861是IOSurfaceRootUserClient :: s_set_surface_notify()中的漏洞,该漏洞导致在Mach端口上删除了额外的引用。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和In-the-wild iOS利用链3中得到了证明。

参考:async_wake漏洞利用代码

iOS漏洞利用链2-iOS 10.3.3

由克莱门特·莱西尼(ClémentLecigne)在野外发现。由Ian Beer和SamuelGroß分析。

漏洞:CVE-2017-13861(与上面相同)。

利用策略:两个Mach端口,端口A和端口B,作为堆喷的一部分分配。触发该漏洞后,将丢弃对端口A的引用,并释放了A周围的端口,从而导致端口指针悬空。通过调用mach_zone_force_gc()强制进行区域垃圾回收,并使用包含模式的OOL端口堆喷重新分配包含端口A的页面,该模式包含以下模式:端口A的ip_context 字段与指向端口B的指针重叠。调用mach_port_get_context()给出端口B的地址。漏洞再次通过端口B触发,从而导致在已知地址悬挂Mach端口的接收权。

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

参考资料:iOS漏洞利用链2-IOSurface

v0rtex-iOS 10.3.3

通过Siguza(@ S1guza)。

漏洞:CVE-2017-13861(与上面相同)。

利用策略:对该端口进行了堆喷,并丢弃了一个端口上的引用。页面上的其他端口被释放,留下悬挂的Mach端口的接收权。

后续漏洞利用流程:使用mach_zone_force_gc()强制进行区域垃圾回收,并通过IOSurface 属性Spray 用OSString 缓冲区重新分配包含悬空端口的页面。所述OSString 缓冲区包含初始化端口的关键领域和允许的索引图案OSString 包含端口通过调用来确定mach_port_get_context()假端口上。释放包含伪端口的OSString 并将其重新分配为普通的Mach端口。调用mach_port_request_notification()将真实Mach端口的地址放入伪端口的ip_pdrequest中字段,然后通过IOSurface 读取OSString 的内容以获取地址。再次使用mach_port_request_notification()获取伪端口本身的地址。

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

参考:v0rtex编写v0rtex利用代码

CVE-2018-4150 bpf-filter-poc的利用-iOS 11.2.6

漏洞分析和POC,由Corellium的Chris Wade(@cmwdotme)提供,由littlelailo(@littlelailo)利用。

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

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

后续利用流程:伪造的Mach端口被转换为时钟端口,而clock_sleep_trap()用于对内核映像指针进行暴力破解。然后将端口转换为伪任务端口,以通过pid_for_task()读取内存。从泄漏的内核映像指针向后扫描内核内存,直到找到内核文本库为止,这会破坏KASLR。漏洞利用的最后部分还不完整,但是在此阶段使用现有代码可以简单,确定性地构建伪内核任务端口。

注意:该漏洞利用不适用于启用PAN的情况。

参考文献:CVE-2018-4150 POC针对CVE-2018-4150-bpf-filter-poc的不完全利用漏洞利用代码

multi_path-iOS 11.3.1

伊恩·比尔(Ian Beer)。

漏洞:CVE-2018-4241是XNU的mptcp_usr_connectx()中的对象内线性堆缓冲区溢出,原因是边界检查不正确。

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

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

参考:multi_path漏洞利用代码

multipath_kfree-iOS 11.3.1

JohnÅkerblom@jaakerblom)。

漏洞:CVE-2018-4241(与上面相同)。

开发策略:内核堆预先分配的4096字节ipc_kmsg 结构附近的mptses 结构的几个TCP套接字。触发该漏洞两次,以破坏两个mptses 结构中mpte_itfinfo 指针的低2个字节,从而关闭套接字会导致两个损坏的指针的kfree()。每个指针已损坏指向0x7a0 字节到ipc_kmsg 分配,产生4096字节的2个消息。包含部分释放的ipc_kmsg 结构之一(带有ipc_kmsg的Mach端口)通过使用mach_port_peek()来检测损坏的msgh_id 字段,找到了完整的标头,但释放了邮件内容)。找到端口后,通过堆喷预分配的ipc_kmsg 结构来重新分配孔,并在每个结构中放置一条消息。填充孔与替换部分的ipc_kmsg 标头重叠在原始ipc_kmsg 的Mach消息内容(部分释放)上,以便在原始端口上接收消息会读取替换ipc_kmsg 标头的内容。标头包含一个指向自身的指针,该指针公开了替换的地址ipc_kmsg分配。再次触发该漏洞以释放替换消息,从而将部分释放的预分配ipc_kmsg保留在已知地址处。

后续攻击流程:通过堆喷AGXCommandQueue 用户客户端来重新分配损坏的ipc_kmsg 中的漏洞。在用户空间中的Mach端口上收到一条消息,该消息复制了AGXCommandQueue 对象的内容,使用该vtable可以从中确定KASLR保护。然后,通过堆喷更多预分配的ipc_kmsg 结构,并以稍微不同的内部布局释放并重新分配已损坏的ipc_kmsg,从而对内容进行更多控制。一条消息放置在每个刚刚堆喷的ipc_kmsg 结构中,以修改重叠的AGXCommandQueue 并劫持虚拟方法调用。被劫持的虚拟方法使用OSSerializer :: serialize()gadget调用copyout(),该工具用于识别哪个堆喷的AGXCommandQueue 用户客户端与损坏的ipc_kmsg中的插槽重叠。依次更新每个刚刚分配的ipc_kmsg 结构的内容,以标识哪个端口与损坏的ipc_kmsg 相对应。通过发送到预分配端口的异常消息来更新AGXCommandQueue 对象的内容,将预分配端口和用户客户端端口一起使用以构建3参数的任意内核函数调用原语。

参考:multipath_kfree利用代码

empty_list-iOS 11.3.1

伊恩·比尔(Ian Beer)。

漏洞:CVE-2018-4243是由于不正确的边界检查而在XNU的getvolattrlist()中部分控制的8字节堆越界写入。

利用策略:由于重大的触发约束,此漏洞被视为在kalloc.16 分配结束时对8个字节的堆进行了越界写入。内核堆被整理为kalloc.16 和ipc.ports 区域的交替块模式,进一步的整理将逆向kalloc.16 free表。释放各种kalloc.16 分配之后,重复触发该漏洞,直到块末尾的kalloc.16 分配溢出,从而破坏了后续页面上第一个ipc_port 的前8个字节。通过调用mach_port_set_attributes()释放损坏的端口,留下持有接收权的程序。

后续利用流程:强制进行区域垃圾回收,并使用OOL ports数组重新分配悬空端口,该数组包含指向与ip_context 字段重叠的另一个Mach端口的指针,以便通过调用mach_port_get_context()检索另一个端口的地址。然后使用管道缓冲区重新分配悬空的端口,并使用pid_for_task()将其转换为内核读取原语。以另一个端口的地址为起点,找到相关的内核对象。最后,将伪端口转换为伪内核任务端口。

参考:empty_list漏洞利用代码

iOS漏洞利用链3-iOS 11.4

由克莱门特·莱西尼(ClémentLecigne)在野外发现,由Ian Beer和SamuelGroß分析。

漏洞:由于未能清除释放的指针,因此从AppleVXD393UserClient :: DestroyDecoder()可以双重释放。

利用策略:创建并释放目标56字节的分配,使悬空指针保持完整。使用IOSurface 属性Spray 使用OSData 缓冲区重新分配插槽。再次调用易受攻击的方法以释放缓冲区,留下悬空的OSData 缓冲区。再次使用包含单个目标Mach端口指针的OOL端口数组重新分配该插槽,并通过IOSurface 属性在用户空间中读取内容,从而生成端口的地址。再次调用易受攻击的方法以释放OOL端口,并使用另一个OSData 重新分配插槽包含两个指向Mach端口的指针的缓冲区。存放OOL描述符的存放端口被破坏,丢弃了对Mach端口的两个引用。这样就使该过程具有在已知地址悬挂Mach端口的接收权。

后续利用流程:执行区域垃圾回收,并使用分段的OOL内存堆喷重新分配悬空的端口,以便调用mach_port_get_context()可以标识堆喷的哪一部分重新分配了端口。释放该段,并使用管道缓冲区重新分配悬空端口,从而在已知地址处提供受控的伪造Mach端口。伪端口转换为时钟端口,而clock_sleep_trap()用于强行使用KASLR。接下来,将伪端口转换为伪任务端口,并使用pid_for_task()建立内核读取原语。最后,将伪端口转换为伪内核任务端口。

参考:iOS漏洞利用链3-XPC + VXD393 / D5500 IOFree

Spice-iOS 11.4.1

Synacktiv的Luca Moro(@JohnCool__)进行漏洞分析和POC 。由Siguza,Viktor Oreshkin(@ stek29),Ben Sparkes(@iBSparkes)和littlelailo进行利用。

漏洞:“ LightSpeed”漏洞(可能是CVE-2018-4344)是XNU的lio_listio()中的一种竞争状况,原因是状态管理不当导致UAF。

利用策略:在一个线程的循环中调用易受攻击的函数,以通过从kalloc.16 分配一个缓冲区并竞速两次释放该缓冲区来反复触发该漏洞。另一个线程重复发送一条消息,其中包含从kalloc.16 分配的OOL端口数组,立即通过IOSurface 属性将大量kalloc.16 分配包含指向用户空间中伪造的Mach端口的指针,并接收OOL端口。当竞争成功时,双释放会导致OOL端口阵列被释放,并且随后的喷射可能会使用伪造的OOL端口阵列重新分配插槽。在用户空间中接收OOL端口会给出一个获得对伪造的Mach端口的权利,该端口的内容可以直接控制。

后续漏洞利用流程:第二个Mach端口在伪端口上注册为通知端口,在伪端口的ip_pdrequest 字段中公开了第二个端口的地址。使用mach_port_get_attributes()修改了伪端口,以构造内核读取原语。从公开的端口指针开始,读取内核存储器以找到相关的内核对象。使用iokit_user_client_trap()将伪端口转换为伪用户客户端端口,从而提供7参数的任意内核函数调用原语。最后,构建一个伪内核任务端口。

注意:该漏洞利用不适用于启用PAN的情况。

分析是对文件pwn.m中的实现进行的,因为这似乎可以与该列表中的其他漏洞利用实现进行最直接的比较。

参考资料:LightSpeed,iOS / macOS沙箱逃逸Spice利用代码

footm1ll-iOS 11.4.1

Luca Moro的漏洞分析和POC。由Tihmstar(@tihmstar)利用。

漏洞:“ LightSpeed”漏洞(与上面相同)。

利用策略:在一个线程的循环中调用易受攻击的函数,以通过从kalloc.16 分配一个缓冲区并竞速两次释放该缓冲区来反复触发该漏洞。另一个线程发送固定数量的消息,其中包含从kalloc.16 分配的OOL端口数组。当竞争成功时,两次释放会导致OOL端口数组释放,从而在某些消息中留下悬空的OOL端口数组指针。第一个线程停止触发漏洞,并创建了大量IOSurface 对象。依次接收每个消息,并使用IOSurface 喷射大量kalloc.16 分配,其中包含指向用户空间中伪造的Mach端口的指针属性。每次堆喷都可以使用虚假的OOL端口阵列从悬空的OOL端口阵列重新分配插槽。成功接收用户空间中的OOL端口将为伪造的Mach端口提供接收权,该伪造的Mach端口可以直接控制其内容。

后续漏洞利用流程:第二个Mach端口在伪端口上注册为通知端口,在伪端口的ip_pdrequest 字段中公开了第二个端口的地址。使用pid_for_task()修改了假端口,以构造内核读取原语。从公开的端口指针开始,读取内核存储器以找到相关的内核对象。使用iokit_user_client_trap()将伪端口转换为伪用户客户端端口,从而提供7参数的任意内核函数调用原语。最后,构建一个伪内核任务端口。

注意:该漏洞利用不适用于启用PAN的情况。

参考资料:LightSpeed,一个iOS / macOS沙箱逃逸漏洞利用代码

Chaos - iOS 12.1.2

奇虎360 Vulcan Team的Zhao Qixun Zhao(@ S0rryMybad)。

由于XNU的task_swap_mach_voucher()无法遵守MIG生存期语义,导致在ipc_voucher 对象上添加或删除了额外的引用,因此漏洞CVE-2019-6225可以UAF。

利用策略:喷射了大量ipc_voucher 对象,并且触发了两次漏洞,以减少凭证上的引用计数并将其释放。页面上的其余内存将被释放,并强制进行区域垃圾回收,将悬空的ipc_voucher 指针留在线程的**ith_voucher** 字段中。

后续利用流程:悬挂的凭证由OSString 缓冲区使用IOSurface 属性Spray 重新分配。调用thread_get_mach_voucher()以获得到该凭证的新分配的凭证端口的发送权,这导致指向该凭证端口的指针存储在与OSString 缓冲区重叠的伪凭证中;读取OSString 属性将显示凭证端口的地址。所述OSString 重叠假凭证被释放并具有大堆喷,这两个在硬编码的地址包含一个假的Mach端口控制的数据的分配,并更新假的重新分配iv_port指向伪造的端口的指针。再次调用thread_get_mach_voucher()以获得对假端口的发送权,并标识哪个OSString 缓冲区包含假Mach端口。这样,该进程就可以向**IOSurface** 属性缓冲区中**的伪造的Mach端口发送权限,该端口位于已知地址(大致等效于悬挂的Mach端口)。通过重新分配OSString 缓冲区以将伪端口转换为伪任务端口并调用pid_for_task()来构建内核读取原语。读取任意内存。找到相关的内核对象,并将伪端口转换为伪映射端口,以将伪端口重新映射到用户空间,从而无需重新分配它。最后,伪端口被转换为伪内核任务端口。

注意:A12引入了PAC,它限制了使用某些涉及代码指针的利用技术的能力(例如,vtable劫持)。此外,iOS 12在ipc_port_finalize()中引入了缓解措施,以防止在端口处于活动状态时释放端口(即,端口未被破坏,例如,因为进程仍拥有该端口的权利)。这改变了过去漏洞利用的通用结构,即在某个进程仍保留其使用权的同时,该端口将被释放。结果,在iOS 12+漏洞利用中获得假端口的权利似乎比早期漏洞利用的发生晚。

参考:IPC凭证UaF远程越狱阶段2(EN)

voucher_swap-iOS 12.1.2

由Google Project Zero的Brandon Azad(@_bazad)提供。

漏洞:CVE-2019-6225(与上面相同)。

利用策略:对内核堆进行修饰,以将ipc_port 分配块直接放在管道缓冲区块之前。喷射了大量ipc_voucher 对象,并触发该漏洞以减少凭证上的引用计数并释放它。页面上的其余凭单将被释放,并强制进行区域垃圾回收,将悬空ipc_voucher 指针留在线程的ith_voucher字段中。

后续利用流程:悬空的凭证通过OOL ports数组进行重新分配,该数组包含指向先前分配的ipc_port 的指针,该指针与凭证的iv_refs 字段重叠。通过调用thread_get_mach_voucher()来检索凭证端口的发送权,并通过重复调用易受攻击的函数来更新凭证的引用计数,从而更新重叠的ipc_port 指针以指向管道缓冲区。接收OOL端口会产生对伪造的Mach端口的发送权,该端口的内容可以直接控制。调用mach_port_request_notification()以插入指向包含伪造端口中另一个Mach端口的指针的数组的指针ip_requests 字段。使用pid_for_task()构建内核读取原语,并读取另一个Mach端口的地址以计算假端口的地址。找到相关的内核对象,并构建伪造的内核任务端口。

参考文献:voucher_swap:在iOS的12 MIG环境与开发引用计数voucher_swap攻击代码

MachSwap-iOS 12.1.2

本·斯帕克斯(Ben Sparkes)。

漏洞:CVE-2019-6225(与上面相同)。

利用策略:喷射了大量ipc_voucher 对象,并且触发了两次漏洞,以减少凭证上的引用计数并将其释放。页面上的其余凭单将被释放,并强制进行区域垃圾回收,将悬空的ipc_voucher 指针留在线程的ith_voucher 字段中。

后续利用流程:悬空凭证由OSString 缓冲区重新分配,该缓冲区包含使用IOSurface 属性Spray 指向用户空间中虚假端口的虚假凭证。调用thread_get_mach_voucher()以获得对伪造凭证端口的发送权,从而产生对伪造Mach端口的发送权,该伪造Mach端口的内容可以直接控制。一条消息被发送到假端口,以泄露ipc_kmsg 对象的地址。使用pid_for_task()构建内核读取原语,并且从公开的指针开始定位相关的内核对象。最终,构建了伪造的内核任务端口。

注意:该漏洞利用不适用于启用PAN的情况。

参考文献:MachSwap:在iOS 12内核漏洞machswap攻击代码

iOS漏洞利用链5-iOS 12.1.2

由克莱门特·莱西尼(ClémentLecigne)在野外发现。由Ian Beer和SamuelGroß分析。

漏洞:CVE-2019-6225(与上面相同)。

利用策略:喷射了大量ipc_voucher 对象,并触发该漏洞以减少凭证上的引用计数并释放它。页面上的其余凭单将被释放,并强制进行区域垃圾回收,将悬空的ipc_voucher指针留在线程的ith_voucher 字段中。

后续利用流程:悬空的凭证由OOL内存堆喷重新分配。分配了大量的Mach端口,然后调用thread_get_mach_voucher()以获得针对该凭证的新分配的凭证端口的发送权,这导致指向该凭证端口的指针存储在与OOL端口数组重叠的伪凭证中。分配了更多的端口,然后接收OOL内存喷射,公开了伪造凭证的凭证端口地址。悬挂的凭证再次与另一个OOL内存喷射重新分配,该更新更新了凭证的iv_port指向下一页的指针。Mach端口被破坏,区域垃圾被强行回收,伪造的凭证只剩下一个指向悬挂端口的指针。悬挂的端口通过管道缓冲区重新分配。最后,调用thread_get_mach_voucher(),以将伪造的Mach端口发送到已知地址的发送权,该地址的内容可以直接控制。伪端口转换为伪任务端口,并使用pid_for_task()建立内核读取原语。找到相关的内核对象,并将伪端口转换为伪内核任务端口。

参考:iOS漏洞利用链5-task_swap_mach_voucher

iOS漏洞利用链4-iOS 12.1.3

由克莱门特·莱西尼(ClémentLecigne)在野外发现。由Ian Beer和SamuelGroß分析,还由一位匿名研究人员分析。

漏洞CVE-2019-7287是由于未选中memcpy()导致IOKit函数ProvInfoIOKitUserClient :: ucEncryptSUInfo()中的线性堆缓冲区溢出。

利用策略:整理内核堆,以在OOL端口数组之前的kalloc.4096 中放置,在通过IOSurface 属性访问的OSData 缓冲区之前的kalloc.6144中放置。该漏洞被触发与从分配源kalloc.4096 并从所分配的目的地kalloc.6144 ,使目标Mach端口的地址被复制到OSDATA 缓冲器。然后OSDATA的缓冲器被读出,公开的目标端口的地址。再次整理堆以在OOL内存缓冲区之前的kalloc.4096 和kalloc.6144中放置在OOL端口数组之前。再次触发该漏洞,将指向目标端口的指针插入OOL端口数组。释放目标端口,并强制进行区域垃圾回收,在OOL ports数组中留下一个悬空的端口指针。悬空的端口通过管道缓冲区重新分配,并且OOL端口被接收,从而为已知地址的伪Mach端口提供了接收权,该伪造的Mach端口的内容可以直接控制。

后续利用流程:虚假端口被转换为虚假时钟端口,而clock_sleep_trap()用于强行使用KASLR。伪端口转换为伪任务端口,并使用pid_for_task()建立内核读取原语。找到相关的内核对象,并将伪端口转换为伪内核任务端口。

参考:iOS漏洞利用链4-cfprefsd + ProvInfoIOKit关于iOS 12.1.4的安全性内容

攻击iPhone XS Max-iOS 12.1.4

王铁磊(@wangtielei)和徐昊(@windknown)。

漏洞:该漏洞是XNU UNIX域套接字绑定实现中的竞争条件,这是由于临时解锁反模式导致了“UaF”。

利用策略:喷射套接字,并触发漏洞,以使指针指向vnode 结构中悬空的套接字指针。关闭套接字,强制进行区域垃圾回收,并通过OSData 堆喷(可能是IOSurface 属性堆喷)用受控数据重新分配套接字。伪套接字被构造为具有0的引用计数。触发使用后释放来调用伪套接字上的socket_unlock(),这将导致伪套接字/ OSData 缓冲区使用kfree()释放。这样就留下了悬挂的OSData缓冲区,可以使用未指定的方式进行访问。

后续利用流程:悬空的OSData 缓冲区通过OOL ports数组进行重新分配,并且OSData 缓冲区被释放,剩下悬空的OOL ports数组。喷射内核内存以将伪造的Mach端口放置在硬编码地址上(或使用了信息泄漏),并且OOL端口阵列与另一个OSData 缓冲区重新分配,将指向伪造的Mach端口的指针插入到OOL端口阵列中。接收OOL端口,从而在已知地址向伪Mach端口发送或接收权限,伪端口通过未指定的方式转换为伪内核任务端口。

注意:此漏洞的唯一参考是BlackHat演示,因此上面解释存在不确定性。

作者开发了此漏洞利用的两个版本:一个用于非PAC设备,一个用于支持PAC的设备。此处介绍的漏洞利用是针对启用PAC的设备的。non-PAC漏洞利用实际上更简单(劫持socket_lock()使用的函数指针)。

参考:攻击iPhone XS Max

SockPuppet-iOS 12.2和iOS 12.4

由Ned Williamson(@nedwilliamson)与Google Project Zero合作。

该漏洞:CVE-2019-8605是一个由于XNU的in6_pcbdetach()未能清除释放指针的UaF漏洞。

利用策略:在漏洞之上构造了任意读取,任意的kfree()和任意的Mach端口地址公开原语。

任意读取原语:多次触发该漏洞以创建许多与套接字关联的ip6_pktopts 悬空结构。悬空的ip6_pktopts 通过IOSurface 属性通过OSData 缓冲区spray 重新分配,使得ip6po_minmtu 设置为已知值,而ip6po_pktinfo 设置为要读取的地址。所述ip6po_minmtu 字段经由检查的getsockopt() ,以及如果正确的话,的getsockopt(IPV6_PKTINFO)被调用来读取20个字节的数据的地址所指向的ip6po_pktinfo 。

任意kfree()原语:漏洞被多次触发以创建许多与套接字关联的ip6_pktopts 悬空结构。悬空的ip6_pktopts 通过IOSurface 属性使用OSData 缓冲区Spray 重新分配,使得ip6po_minmtu 设置为已知值,而ip6po_pktinfo 设置为可用地址。所述ip6po_minmtu 字段经由检查的getsockopt() ,以及如果正确的话,setsockopt的(IPV6_PKTINFO)被调用来调用kfree_addr()在ip6po_pktinfo 指针。

任意Mach端口地址公开原语:漏洞被多次触发以创建许多与套接字关联的ip6_pktopts 悬空结构。悬空的ip6_pktopts 使用包含指向目标端口的指针的OOL端口数组spray重新分配。的ip6po_minmtu 和ip6po_prefer_tempaddr 字段经由读取的getsockopt(),公开目标端口指针的值。使用任意读取原语检查端口是否为预期类型。

后续利用流程:Mach端口地址公开原语用于公开当前任务的地址。创建了两个管道,并使用内核读取原语找到了内核中管道缓冲区的地址。找到了相关的内核对象,并在管道缓冲区之一中构造了伪内核任务端口。任意kfree()原语用于释放另一个管道的管道缓冲区,并且通过堆喷OOL端口数组来重新分配管道缓冲区。然后写入管道,以将指向伪内核任务端口的指针插入OOL端口数组,然后接收OOL端口,从而生成伪内核任务端口。

注意:与该列表中的大多数其他利用线性结构的漏洞利用不同,SockPuppet是按层次结构进行构建的,并始终基于相同的原语。这种独特的结构可能是由于潜在漏洞的强大函数和稳定性所致:该错误直接提供了任意读取和任意免费的原语,并且在实践中,这两个原语都是100%安全可靠的,因为可以检查重新分配是成功的。但是,这种结构意味着在特定于漏洞的利用与通用利用之间的高级利用流程中没有明确的时间边界。相反,该边界出现在漏洞利用代码中的概念层之间。

SockPuppet错误已在iOS 12.3中修复,但在iOS 12.4中已重新引入。

参考:SockPuppet:适用于iOS 12.4的内核漏洞利用SockPuppet利用代码

oob_timestamp-iOS 13.3

布兰登·阿扎德(Brandon Azad)。

漏洞:CVE-2020-3837是由于不正确的边界检查而导致在IOKit的IOAccelCommandQueue2 :: processSegmentKernelCommand()中对多达8个字节的时间戳数据进行线性堆越界写入。

利用策略:整理内核映射以布置两个96 MB的共享内存区域,一个8页的ipc_kmsg ,一个8页的OOL端口阵列和80 MB的OSData 缓冲区(通过IOSurface 属性堆喷)。根据当前时间计算要溢出的字节数,并触发溢出以破坏ipc_kmsg 的ikm_size 字段,这样ipc_kmsg 的大小现在在16页至80 MB之间。包含ipc_kmsg 的端口被销毁,释放了损坏的ipc_kmsg ,OOL端口数组以及一些后续的OSData 缓冲区。更多OSData通过IOSurface 喷射缓冲区,以重新分配OOL端口阵列,该阵列包含一个指向伪端口的指针,该指针位于可能与96 MB共享内存区域之一重叠的硬编码地址上。接收OOL端口,从而对已知地址的伪Mach端口产生接收权,该地址的内容可以直接控制。

后续利用流程:使用pid_for_task()构造内核内存读取原语,找到相关的内核对象,并构建伪造的内核任务端口。

注意:iOS 13引入了zone_require,这是一个缓解措施,可在使用某些对象之前检查是否从预期的zalloc 区域中分配了某些对象。当对象在zalloc_map 之外分配时,实现中的疏忽导致了绕过。

参考:oob_timestamp利用代码

0x03 iOS内核利用缓解措施

接下来,我们将介绍一些当前的iOS内核利用缓解措施。此列表并不详尽,但简要总结了利用开发人员可能会在iOS 13上遇到的一些缓解措施。

Kernel Stack Canaries - iOS 6

iOS 6引入了内核堆栈Canaries(或堆栈Cookie),以防止内核中的堆栈缓冲区溢出。列表中的漏洞均不受堆栈金丝雀的影响,因为它们不针对堆栈缓冲区溢出漏洞。

Kernel ASLR-iOS 6

内核地址空间布局随机化(内核ASLR或KASLR)是一种缓解措施,可在内核地址空间中将内核缓存映像的基地址随机化。在实施内核ASLR之前,内核缓存映像中的内核函数和对象的地址始终位于固定地址。

绕过KASLR是所有现代iOS内核利用的标准步骤。

Kernel Heap ASLR-iOS 6

从iOS 6开始,各种内核堆区域的基址已被随机化,这旨在减轻利用硬编码地址确定对象分配位置的漏洞。

解决内核堆随机化问题是现代iOS内核利用的标准步骤。通常,这涉及堆堆喷,即使没有确切的地址,也要诱导内核分配大量数据以影响堆的形状。同样,可以利用许多漏洞来产生信息泄漏,从而泄露堆上相关内核对象的地址。

W ^ X / DEP-iOS 6

iOS 6还通过确保将内核页面映射为可写或可执行文件,而不是两者(通常称为“写或执行”或“ W ^ X”)来引入大量内核地址空间。这意味着页表不再将内核代码页映射为可写,并且内核堆和堆栈也不再映射为可执行文件。(确保非代码数据未映射为可执行文件通常被称为数据执行保护或DEP。)

现代的公共iOS漏洞利用程序并没有尝试绕过W ^ X(例如,通过修改页表和注入shellcode);相反,通过修改内核数据结构并执行代码重用攻击来实现利用。这主要是由于存在一种称为KTRR的更强大的,硬件强制的W ^ X缓解。

PXN-iOS 7

苹果的A7处理器是iPhone中的第一个64位ARMv8-A处理器。以前,iOS 6将内核和用户地址空间分开,因此在正常的内核执行期间无法访问用户代码和数据页。随着转向64位,地址空间不再分开。因此,在页表项中设置了“从不执行特权(PXN)”位,以确保内核无法执行驻留在用户空间页中的shellcode。

与W ^ X相似,由于对KTRR的更强保护,PXN作为防止跳转到用户空间shellcode的保护。

PAN-iOS 10

永不特权访问(PAN)是一项ARMv8.1-A安全函数,可防止内核访问用户空间也可以访问的虚拟地址。这用于防止内核取消攻击者提供的指向用户空间中数据结构的指针。它类似于某些Intel处理器上的Supervisor模式访问阻止(SMAP)函数。

尽管以前没有绕过 PAN ,但现代的公共iOS内核漏洞通常会通过将数据喷入内核然后学习数据地址来解决PAN问题。尽管最可靠的技术涉及公开插入内核的数据的地址,但是存在一些通常可以解决PAN的技术,例如堆喷足够的数据以压倒内核映射的随机性,并强制将固定的硬编码地址分配给受控数据。 还存在其他用于在用户空间和内核之间建立共享内存映射的原语,这些原语也可用于解决PAN。

KTRR-iOS 10

KTRR(可能是内核文本只读区域,是Kernel Integrity Protection的一部分)是在Apple A10处理器(ARMv8.1-A)上引入的自定义硬件安全缓解措施。它是WMU保护的一种强大形式,由MMU和内存控制器在连续的单个内存范围内实施,覆盖内核高速缓存映像的只读部分以及一些敏感数据结构(如顶级页表和信任高速缓存) 。苹果公司将其称为内核完整性保护(KIP)v1。

尽管之前两次公开绕过KTRR ,但现代的公共iOS内核漏洞通常通过不操纵受KTRR保护的内存来绕过KTRR。

APRR-iOS 11

APRR(可能代表访问保护重新路由访问权限限制寄存器)是Apple A11和更高版本的CPU上的自定义硬件函数,可通过特殊寄存器间接访问虚拟内存访问权限(通常在页面的页面表条目中指定),从而允许访问可以按原子和按核心更改大页面组的权限。它通过将通常直接指定访问权限的PTE中的位转换为包含真实访问权限的特殊寄存器的索引来工作。更改寄存器值会在具有相同访问权限索引的所有页面上交换保护。APRR有点类似于较新的Intel处理器上可用的“内存保护密钥”函数。

APRR本身不提供任何安全边界,但是可以在一个地址空间内分割特权级别,PPL大量使用它在iOS内核中创建安全边界。

PPL-iOS 12

PPL(页面保护层)是基于APRR并依赖于KTRR的软件层,旨在在内核读/写/执行与直接页表访问之间建立安全边界。PPL的主要目标是防止攻击者修改已进行代码签名的页面(例如,使用内核读/写来覆盖进程的可执行代码)。这必然意味着PPL还必须保持对页表的完全控制,并防止攻击者映射敏感的物理地址,包括页表,页表元数据和IOMMU寄存器。

截至2020年5月,尚未公开绕过PPL。也就是说,到目前为止,现代iOS内核漏洞并未受到PPL的影响。

PAC-iOS 12

指针验证码(PAC)是一种ARMv8.3-A安全函数,它通过将指针值的加密签名存储在指针的高位来减轻指针篡改。Apple随A12一起推出了PAC,并显着增强了实现(与ARM标准相比),以抵御使用内核读/写的攻击者,尽管在大多数情况下它在函数上无法区分。苹果的内核使用PAC来实现控制流完整性(CFI),从而在内核读/写和内核代码执行之间设置了安全边界。

尽管众多公共旁路了iOS内核的基础PAC-CFI的,PAC内核仍然是一种有效的缓解漏洞:它已严重制约了许多开发中的漏洞和缓解了利用技术。例如,过去的攻击利用内核执行原语来构建内核读/写原语(例如,参见ziVA)。如果不先绕过PAC,就无法再在A12上使用。此外,IOKit中PAC保护的指针的广泛使用使将许多错误转化为有用的原语变得非常困难。考虑到IOKit中严重的安全问题的悠久历史,这是一个巨大的胜利。

zone_require-iOS 13

zone_require是iOS 13中引入的一种软件缓解措施,可在使用前添加检查,以确保从预期的zalloc 区域分配了某些指针。iOS内核缓存中最常见的zone_require检查是Mach端口。例如,每次ipc_port 锁定时,都会调用zone_require()函数以检查包含Mach端口的分配是否驻留在ipc.ports 区域中(例如,不包含通过kalloc()分配的OSData 缓冲区)。

由于伪造的端口是现代技术的组成部分,因此zone_require对其具有重大影响。像CVE-2017-13861(async_wake)漏洞上参考ipc_port 不再提供创建一个假的端口的直接路径。虽然zone_require已被公开绕过,但该技术依然有效。

 https://bugs.chromium.org/p/project-zero/issues/detail?id=1986#c5
本文翻译自:https://googleprojectzero.blogspot.com/2020/06/a-survey-of-recent-ios-kernel-exploits.html?m=1如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论