我们将介绍一种在iOS 11.3.1上绕过APFS缓解的方法
作者:白小龙和蒸米(Spark) @ Alibaba Security Lab
原文链接:
https://weibo.com/ttarticle/p/show?id=2309404245794218721506
0x00 简介
由于需要安装非沙盒程序和修改系统设置,对于越狱来说拥有一个有读写权限的root分区是非常重要的。但是在iOS上root文件系统基本上总是只读的。因此在越狱现代iOS的过程中,重新挂载root文件系统是一个非常关键的步骤。但是苹果不会轻易的让你将root文件系统重新挂载。
在本文中,我们将介绍苹果在 iOS 11.3 中新引入的防止root文件系统重新挂载为读写权限的缓解措施,并给出了全新的绕过缓解措施的技术。通过我们的研究,我们新的缓解措施绕过技术将在iOS 11.3.1上结合Ian Beer的ftp0一起使用,这意味着,你可以在iOS 11.3.1上越狱!
0x01 iOS 11.3之前重新挂载的方法
当我们通过mount()系统调用重新挂载root文件系统时,内核的__mac_mount()函数将被调用,并且它最终会调用mount_common()。在mount_common()中会有一个MACF检查,mac_mount_check_remount() 会检查是否允许重新挂载。这个MACF检查是由Sandbox.kext中的hook_mount_check_remount()来处理,它检查重新挂载的文件系统是否是root文件系统(ROOTFS)。如果是,它将停止重新挂载文件系统。
在iOS 11.3之前,最常用的重新挂载root文件系统的方法是绕过沙盒检查,此方法最初由Xerub提出。此方法删除了root文件系统中挂载标志中的ROOTFS, RDONLY, NOSUID标志位,在Jonathan Levin 的 HITB 18 AMS的演讲中对此已有详细描述。
0x02 对iOS文件系统的基本认识
但是,只要存在bypass方法,自然也会有新的缓解措施。在iOS 11.3之后,如果我们用原来的方法重新挂载root文件系统,当我们对文件进行修改(写入或创建)时内核会出现如下崩溃:
这说明了iOS文件系统出现了新的缓解措施。我们研究了iOS的文件系统并针对新的缓解措施提出了新的绕过方案。在本文中我们将阐述这一新方案,在阐述方案细节之前我们先介绍一些iOS文件系统结构的一些基础知识并解释为什么会发生内核崩溃。
苹果支持很多文件系统,包括APFS, HFS+,FAT等等。当文件系统挂载后会创建一个通用的"mount"结构体(如下所示)。它表示文件系统是如何挂载的。
在这个结构体中,mnt_flag有像RDONLY, ROOTFS, NOSUID这样的标志位,它表示了基本的挂载状态。mnt_ops是由文件系统实现的一组函数指针,用于mount, unmount, ioctl等操作。mnt_data是一个指向具体挂载系统中"mount"私有结构体的指针。在内部一个具体文件系统的mount/remount/unmount会检查并操作这个"mount"私有结构体以使其生效。
0x03 根本原因(iOS 11.3 新缓解措施)
在内核崩溃日志中它的路径"com.apple.xbs/Sources/apfs/apfs-748.52.14"说明这个崩溃发生在APFS文件系统。也就是说iOS挂载的是APFS root文件系统。APFS (Apple File System的缩写)是苹果新推出的文件系统,目前在苹果所有设备上使用。它具有克隆、加密、快照等功能。
那么在APFS中发生了什么导致内核崩溃了呢?要回答这个问题,我们首先要知道这个root文件系统是如何挂载的。通过执行iOS上mount 命令,我们可以获取当前挂载的文件系统的信息如下:
这里我们用"tmutil localsnapshot /"为root分区创建一个新的快照,其名为"com.apple.TimeMachine.2018-05-30-154704",然后执行"mount -t apfs -o -s=com.apple.TimeMachine.2018-05-30-154704 / /tmp"命令把这个新的快照挂载在/tmp目录下。当挂载完成后,我们看到一样有"com.apple.TimeMachine.2018-05-30-154704@"这种前缀。因此我们看出iOS上root分区的"com.apple.os.update-CA59XXXX@"同样表示是一个快照。苹果对快照的解释是:快照是特定时间点上一个只读的文件系统的实例;操作系统通过快照可以更备份更高效并且提供了一种将更改还原到给定时间点的方法。显然iOS把只读的快照挂载为root文件系统。这种用快照挂载为root文件系统的方法就是iOS11.3新的缓解措施,这也是重新挂载失败的原因。
那么,为什么之前重新挂载的方法会失败呢?简短来说,我们只是把root文件系统的mount标志位修改成了read-write,但root文件系统仍然是快照模式。特别是,文件系统的"mount"私有结构体(mnt_data)没有被修改,仍然表示为只读快照。也就是说之前的方法重新挂载的是一个"可写"的只读快照,这显然会导致冲突,其结果就是,不可避免的产生了内核崩溃。
为了解释根本原因的技术细节,让我们回顾一下内核崩溃日志。根据内核崩溃日志来看,我们缺少了范围所至的大小。在APFS文件系统中"extent"这个术语指的是表示文件位置和大小的一个内部数据结构。通过逆向afps的内核扩展,我们可以确定文件的extent是一个btree的结构并且存储在APFS的私有结构体"mount"中(即mount结构体的mnt_data字段,参见0x02)。但是快照挂载的mnt_data中并没有有效的文件extent结构。当我们尝试修改文件时APFS文件系统会在挂载的文件系统的mnt_data中查找是否有有效的文件extent。因此在之前的重新挂载的方法中,当我们在重新挂载的快照root文件系统上修改文件时。虽然修改后的mnt_flag允许APFS查找extent,但是快照的mount结构体中的mnt_data字段并没有有效的extent,这种错误便导致了内核崩溃。
0x04 新的绕过方法
有了这些发现,很快就想到了一个简单的解决方案:我们需要让快照的mnt_data成功找到文件extent。为了实现这个目标,我们需要创建一个正确组织了文件extent的mnt_data结构体,就像正常可读写挂载的root文件系统中的一样。但是手动创建有效的mnt_data 是一项艰巨而复杂的任务。我们能让APFS创建一个与可读写root文件系统相同的mnt_data吗?如果我们能创建一个新的可读写的root文件系统的mount结构体并能从中获取到mnt_data,那么我们的答案是肯定的。
然后对于我们新的绕过方法在脑海里就有了一个基本的思路,包括了以下步骤:
用新挂载的mount结构体中的mnt_data替换掉root文件系统中的mnt_data
uint64_t newMPVnode = getVnodeAtPath(newMPPath); uint64_t newMPMount = readKern(newMPVnode + off_v_mount); uint64_t newMPMountData = readKern(newMPMount + off_mnt_data); /* 3. 修改mount的flag并重新挂载 */ uint64_t rootVnode = getVnodeAtPath(“/”); uint64_t rootMount = readKern(rootVnode + off_v_mount); uint32_t rootMountFlag = readKern(rootMount + off_mnt_flag); writeKern(rootMount + off_mnt_flag, rootMountFlag & ~ ( MNT_NOSUID | MNT_RDONLY | MNT_ROOTFS)); mount(“apfs”, “/”, MNT_UPDATE, &devpath); /* 4. 从新创建的mount中的mnt_data替换掉root文件系统中的mnt_data */ writeKern(rootMount + off_mnt_data, newMPMountData);在上面的代码中,readKern()从内核地址读取数据,writeKern()将数据写入到内核地址,这可以在 Xerub、Electra、V0rtex、mach_portal 或 Qilin 工具包的越狱代码中找到。getVnodeAtPath()是我们通过路径获取vnode地址的新gadget,它采用了 Ian Beer 在内核中执行代码的技术。
通过我们新的绕过方法,你现在拥有了一个可读写的root文件系统。你可以对系统文件进行修改,在沙盒外的路径上安装二进制文件等。下图展示了在已越狱的iOS 11.3.1上重新挂载成功的状态。
0x05 总结
关于这种新的绕过方法您应该知道的一件事是,您对root文件系统所做的修改将在您重新启动后还原。因此,这是一个完美版重新挂载方案(我们可能在不久的将来讨论持久化的文件系统重新挂载方案)
最后但同样重要的是,我们将在 DEFCON(2018 年 8 月 9 日至 12 日@拉斯维加斯)上有更多地关于 iOS 越狱和 macOS 漏洞挖掘的话题讨论。欢迎在 twitter 上关注我们:@bxl1989 和 @SparkZheng。 :-)