我们都知道,APP的沙盒是用来存储这个APP的本地数据的。其实,同一个开发者开发的多个APP的沙盒数据是可以共享,也就是你可以拿到另一个应用的沙盒数据。但沙盒共享却无法在macOS 上实现。
在深入分析之前,让我们先介绍一些概念。 XPC 是 macOS 和 iOS 中使用的 IPC 机制之一。客户端可以发送消息,也可以请求服务器的回复,消息和回复都是用基本的 XPC 类型(字典、数组、字符串、字节数组、int64、uint64、fds、其他 XPC 连接等)构造的。在 hood下,每条消息都被序列化为一个 mach 消息,XNU 最低级别的 IPC 机制,并在 mach 端口上发送。服务器端,消息的反序列化是被延迟的,这意味着你不能轻易地用反序列化机制调整堆(就像使用 CFPropertyListCreateWithData)。
XPC 服务相当简单(而且不是很干净,有很多内存泄漏)。客户端可以创建会话(但不能销毁它们),服务器将返回关联的客户端 ID。要创建会话,客户端将其任务端口(将其视为客户端进程上的句柄或 fd,赋予其全部权限)发送到将存储它的服务器。这个任务端口将在不同的地方被使用,以获得其关联的审计令牌(一个包含一些信息的不透明结构,如进程 pid/uid/gid/ruid/rgid)。
由于多种原因,这本身就是一个非常糟糕的设计:
你不应该依赖审计令牌,它们是不透明的结构,将来可能会发生变化,并且操作它们的函数是私有的。
除非服务绝对需要它,否则进程不应将其任务端口提供给服务,因为它赋予了客户端的全部权限。
此时,服务器已经拥有客户端审计令牌,它由内核和每条 mach 消息一起发送,并且可以使用 xpc_connection_get_audit_token(一个私有 API.)检索,你可以通过公共 XPC API (xpc_connection_get_{euid,guid,pid,asid}), 但要格外小心。
进程可以发送一个假的任务端口(类似于任务端口的mach端口),并向服务器发送一个任意的审计令牌。
如果由于某种原因,服务器确实需要使用客户端提供的任务端口来获取其关联的审计令牌,则它应该只需要任务名称,一个只能与 task_info 和 mach_task_is_self 一起使用的极低权限的任务端口,你可以通过调用 task_get_special_port(mach_task_self(), TASK_NAME_PORT, &task_name) 获取。它还应该检查用户发送的端口是否确实是一个真实的任务名称端口(通过使用 mach_port_kobject 查询端口对象类型并将其与 IKOT_TASK_NAME=20 进行比较),并将使用 task_info 检索到的审计令牌与由mach 消息中的内核。
虽然结果糟糕,但还是可以在他们的会话中创建、查询和删除条目。当客户端创建条目并成为其所有者时,它可以选择应使用审计令牌的索引来保护条目(pid/uid/gid...)和授权 UID 列表(然后将它们存储在条目中->allowed_uids)。在 UID 列表中具有审计令牌值的进程可以查询条目,并且具有相同审计令牌值的进程可以删除它们。例如,如果一个 gid 为 0 的进程创建了一个受审计令牌索引 2 (audit_token[2] = gid) 和 UIDs=[501] 保护的条目,那么所有具有 gid 0 的进程(并且只有它们)可以删除该条目以及所有具有 gid 501 的进程(并且只有它们)都可以查询它。最后,条目由增量 ID 标识,并且可以在创建时与任意 XPC 对象相关联(存储在 entry->object 中),当查询条目时,这个 XPC 对象被发送到客户端。
快速查看代码会发现删除代码中有一个悬空指针创建。如果客户端尝试删除条目但无权这样做,则条目 XPC 对象上的引用数量将减少(使用 xpc_release),而之前并未增加。由于 XPC 对象上只有一个引用,它实际上在第一次无效删除时被释放,但条目保持活动并且指针没有失效。如果条目随后被查询或删除,只会得到一个 use-after-free。
然而,糟糕的是,删除我们自己的条目并不是那么容易。我们需要在创建条目和删除条目时使用不同的审计令牌。问题在于审计令牌包含进程审计用户/会话 ID、其 pid、uid、euid、gid、egid 及其 pidversion,所有这些值都不能轻易被修改。此外,我们的漏洞利用是严重沙盒漏洞和低权限的,我们不能生成新的进程或 fork(有新的 pid),也不能改变我们的 (e)uid 或 (e)gid。幸运的是,我们可以自己执行,execve 会破坏我们之前的任务端口(修补一个旧的和被低估的漏洞)。当我们尝试使用在 execve 系统调用之前创建的客户端 ID 删除条目时,服务器将尝试使用 get_audit_token_value 获取旧任务 uid,task_info 将失败,因为旧任务已被删除,get_audit_token_value 将返回 -1,我们将执行易受攻击的代码。另一种解决方案可能是创建一个假任务端口来向 task_info 发送任意值,但 execve 解决方案更简单。
既然我们可以触发漏洞,那如何利用它呢?首先要做的是能够使用任意数据或有趣的对象重用它。从利用者的角度来看,macOS堆既有好的属性,也有坏的属性。好的方面是,所有微小的分配(< = 1008字节)都是在相同的页面中连续分配的,这意味着我们可以重用具有不同大小的对象的分配,并且可以在不破坏元数据的情况下溢出一个块。不好的是,所有微小的分配都是在相同的页面中连续分配的,这意味着堆中有很多活动,并且对其进行调整并不容易。此外,每个物理 CPU 内核有一个magazine,因此如果分配块的内核与尝试重用它的内核不同,它将永远不会成功。可以尝试调整所有magazine,但这很难实现,而且快速调整和重用通常更有效,以确保所有利用步骤都在同一个 CPU 内核上完成,这也是为什么堆利用通常更可靠的原因。
要对任意数据重用我们释放的分配,一种简单的方法是将它与 XPC 字节数组的数据缓冲区重用。问题是 XPC 字节数组数据以 0x40 字节元数据为前缀(因为它们是用 dispatch_data_create_alloc 分配的):
所以我们不能只用我们释放的分配大小分配很多 xpc_data_t 并希望它放在正确的位置。幸运的是,当 XPC 字节数组或字符串被反序列化时,它们的数据缓冲区在 xpc 对象之前被分配:
这意味着,如果对堆进行碎片整理并且我们正在反序列化字符串“AAAAAAAAAAAAAAA”,则在内存中我们将具有以下模式:
当我们释放 XPC 对象时,这两个分配将合并形成一个 16+48=64 字节的空闲块。为了对堆进行碎片整理,我们只需要创建很多微小的分配,幸运的是服务器有很多内存泄漏,很容易通过发送虚拟消息来填充所有漏洞:
为了使用任意数据干净地重用我们的 XPC 对象,我们遵循需要以下步骤:
创建大量(100000)微小的分配来填补堆中的空洞;
创建一些 (100) XPC 字符串以真正确保我们针对的空闲列表为空;
创建我们的受害者 XPC 字符串,例如合并后的大小将位于不太常用的空闲列表中(我们任意选择了 0x250);
创建一些(10)微小的障碍分配,以确保我们释放的对象不会与下一个分配合并;
使用微小的 XPC 字节数组创建和释放少量条目 (200) 以制造漏洞,例如我们未来的大释放分配将不会被我们的数据以外的其他东西重用。
通过使用XPC字节数组数据缓冲区创建200个条目,尝试重用已释放的合并块。
这种技术非常稳定,在Alles服务器上工作得很好(保护服务器存在 POW 挑战,因此进行密集测试并不容易,但连续 10 次尝试中有 9 次成功)。如果一切顺利,我们有以下布局:
现在我们可以重用XPC对象了,接下来在服务器上执行代码,读取标志并将其发送给我们的客户端。xpc_release实际上是objc_release和_os_object_release的包装器,所以我们可以使用经典的Objective-C方法来获得代码执行,喷雾超过256 mb内存的XPC字节数组 ,将假类对象放入spray,重用分配,用指向spray的硬编码ISA指针替换第一个指针,找到一个堆栈枢轴和ROP!由于 macOS/iOS 共享缓存包含1.5GB 代码并在所有进程中加载到相同地址。
我们开始编写一个 ROP 链来打开标志,将它映射到内存中的一个硬编码地址并将其发送回客户端,但是当我们尝试它时,打开失败了,我们回顾了沙箱配置文件并意识到服务器确实无法读取标志,只能读取客户端。因此,需要利用另一个客户端,与我们连接到同一台服务器。这证明我们不需要RCE,只需要服务器任务端口来获取客户端任务端口然后对客户端执行操作。
任务端口操作是 XNU 中的一种经典漏洞利用方法,自 iOS 10 以来,通过限制非平台二进制文件使用平台二进制文件任务端口,它已经得到了缓解。但是,macOS上没有这种缓解。此外,平台二进制文件是在iOS内核中硬编码的二进制文件,并签名调试信任缓存,所以没有什么能阻止我们使用服务器和客户端任务端口。正如我们在本文开头所说的,拥有一个任务端口就足以控制底层的进程。当前进程的任务端口名可以通过mach_task_self函数检索,但在BigSur上它实际上总是等于0x203(在所有以前的版本中都是0x103)。然而,苹果计划在未来让它变得更随机一些,所以不要太依赖它:
此外,XPC可以作为xpc_mach_{send/recv}_t对象来发送和接收任意端口权限(通过使用私有API)。这些目标也非常简单。
正如所看到的,我们已经知道服务器任务端口名称(0x203),并且由于类指针在共享缓存中,我们可以重用在我们自己的进程中可以获得的值。构建一个伪造的 xpc_mach_send_t 对象就像复制一个真实的对象一样简单。
现在,如果我们使用伪造的 xpc_mach_send_t 对象重用释放的分配并查询虚假条目,我们将拥有服务器任务端口!
使用服务器任务端口,获取客户端任务端口只需要使用mach api:
一旦我们有了受害者的任务端口,所要做的就是操纵它的一个线程来强制它读取标志,然后读取它的内存。要调用函数,我们只需要在正确的寄存器(RDI、RSI、RDX、RCX、R8、R9)中设置参数,修改 RIP(指令指针)以使其指向所需的函数并使 RSP(堆栈指针)指向将用作函数返回地址的无限循环 (\xEB\xFE) 的地址。然后我们恢复线程并等待 RIP 指向无限循环小工具。例如,这是打开标志的代码:
我们可以重用这段代码来读取内存中的标志,然后使用 mach_vm_read_overwrite 在受害者内存中读取它。
完整的漏洞利用代码可以在 Synacktiv github 上找到。
本文翻译自:https://www.synacktiv.com/publications/macos-xpc-exploitation-sandbox-share-case-study.html如若转载,请注明原文地址