【OSG】macOS 10.12.2本地提权和XNU port堆风水

原文作者:Vitaly Nikolenko译者:依然范特西 校对:布兜儿
原文链接:Local Privilege Escalation for macOS 10.12.2 and XNU port Feng Shui
首发看雪论坛:【OSG】macOS 10.12.2本地提权和XNU port堆风水

0x00 简介

从yalu 10.2 中,我们可以学到很多新的漏洞利用技术,特别是XNU堆风水和绕过内核保护。 在本文中,我们将讨论XNU堆风水的一些技术细节,并使用该技术在macOS中获得root权限。最后但同样重要的是,漏洞源码可以从以下网址下载:

0x01 内核堆栈溢出

为了使用XNU堆风水在macOS 10.12中获得root权限,我们需要一个内核漏洞。在本文中,我们选择mach_voucher堆溢出为例,Mach_voucher_extract_attr_recipe_trap() 是可以在沙箱内被调用的Mach陷阱。这是在iOS 10和macOS 10.12中新添加的功能,但是它含有一个严重的缓冲区溢出漏洞。

d71rt073i56.png

在这个函数中,args->recipe_size是一个指向整数的用户态指针,所以mach_voucher_extract_attr_recipe_trap() 将通过copyin() 从用户态拷贝size的值。

446in89rqbm0.png

然后该函数使用sz值在内核堆上分配一个内存块。但是,开发人员忘记了args-> recipe_size是一个用户态指针,然后将其用做copyin() 中的 size 值。 我们知道用户态指针可能大于sz值,这将导致内核堆中的缓冲区溢出。
请注意,如果我们要在该地址上分配一个内存块,我们可能无法控制用户态指针的地址。不过这不是问题。如果遇到未映射的内存,copyin() 函数会自动停止。 所以,我们可以在高地址上分配一个内存块,然后通过取消映射其余的内存块来控制溢出数据。
446ina2jpor0.png

0x02 通过port进行堆风水

在iOS 10和macOS 10.12中,苹果添加了一个新的防护机制,用以检查在错误区域释放的攻击,因此我们无法使用经典的vm_map_copy(修改vm_map_size)技术来执行堆风水。另外,苹果在iOS 9.2和macOS 10.11中添加了一个空闲列表(freelist)随机化的机制,所以我们不能较容易地预测再分配的内存块的位置。 为了解决这些问题,我们需要一个新的堆风水技术。在Yalu 10.2中,qwertyoruiop使用OOL_PORTS来获取可用于执行任意内核内存读写的内核任务端口。 这种技术绕过了XNU堆中的所有防护机制。接下来,我们将讨论这种技术的细节。
Mach msg是XNU中最常用的IPC机制,大量消息通过 “complicated message” 发送。通过MACH_MSG_OOL_PORTS_DESCRIPTOR msg_type的 ”complicated message”,我们可以向内核发送out-of-line端口。例如,我们发送了32个MACH_PORT_DEAD ool端口(32 * 8字节=0x100字节)到内核的kalloc.256区域。
42423423.png
保存在mach msg中的ool端口都是ipc_object类型的指针,这类指针可以指向用户态地址。 因此,我们可以使用mach_voucher漏洞来溢出这些指针,并在用户态下修改一个ipc_object指针,使它指向一个伪造的ipc_object。 另外,我们也可以在用户态下为假的端口创建假的任务。
31231313.png
为了保证溢出正确的ipc_object指针,我们需要做一些堆风水操作。 首先,为了确保新分配的内存块是连续的,我们向内核发送大量的ool端口消息。 然后使用在中间收到的一些消息戳一些slots。 然后我们再次发送一些消息,使得溢出点在slots的中间部位。 最后,我们使用mach_voucher在溢出点触发堆溢出。
446ip2ru4i90.png
溢出之后,我们可以接收到剩余得 mach 消息,来找到被破坏的端口(不是MACH_PORT_DEAD 端口)。
446ip41tfpa0.png

0x03 内核内存任意读写

首先,我们将伪造的ipc_object的io_bits设置为IKOT_CLOCK。所以我们可以使用clock_sleep_trap() 通过遍历内核来获取内核中计时任务的地址。 这个地址将帮助我们稍后找到内核数据。
446ipedl1v50.png
然后我们将伪造的ipc_object的io_bits设置为IKOT_TASK,为假的端口制造一个假的任务。 通过将值设置为faketask + 0x380(在arm64中为0x360),我们可以通过pid_for_task() 读取任意32位内核内存。这是因为该函数不检查任务的有效性,只返回((faketask + 0x380)+ 0x10)的值。 所以我们可以在没使用任何工具和ROP的情况下获得可造性内核读取。
446ipg2vp590.png
446iph1n7mf0.png
通过计时任务泄露内核地址,我们可以在内存中搜索内核镜像的魔数,然后找到kslide。
446ipi9j31o0.png
获取内核基地址后,我们可以遍历所有进程来查找内核ipc_object和内核任务。然后我们可以dump出这些信息,给我们的伪造的ipc_object和任务。通过对假的ipc_object和任务使用task_get_special_port() ,我们可以获得内核任务端口。请注意,内核任务端口是非常强大的。它可以通过mach_vm_read()和mach_vm_write()进行任意内核内存读写。
446ipjo49820.png

0x04 root提权

每个进程的凭据信息,posix_cred结构,存储在内核内存中。首先我们需要找到我们的进程信息(通过内核基址+ allproc),然后找到我们的利用程序的posix_cred结构数据。 之后,我们通过kernel_task_port使用mach_vm_write() ,将cr_ruid(实际用户ID)值设置为0(也就是root)。最后,而且非常重要的是,我们可以使用system(“/ bin / bash”)获取root shell!
446ipm366tr0.png

0x05 总结

在本文中,我们介绍了如何使用mach_voucher堆溢出和port堆风水来实现macOS 10.12.2上的本地提权。漏洞源码可以从下面的网址下载:

0x06 参考

Yalu 102: GitHub - kpwn/yalu102: incomplete iOS 10.2 jailbreak for 64 bit devices by qwertyoruiopz and marcograssi
1004 - project-zero - Project Zero - Monorail