查看原文
其他

《深入理解苹果用户空间文件系统》——BlackHat USA2023 议题分享

蚂蚁安全实验室 蚂蚁技术AntTech
2024-08-22

近日,备受瞩目的 Black Hat USA 2023 在美国拉斯维加斯落下帷幕。作为全球网络安全领域知名度和影响力最高的大会,黑帽大会每年发布优秀的研究成果和产品技术,受到网络安全行业和爱好者的密切关注。

大会首先发布了网络安全领域最受关注的两大榜单:素有“安全界奥斯卡”之称的 Pwnie Awards ,及微软安全响应中心(MSRC)公布的年度最具价值研究员榜单。两大榜单中,Pwnie Awards 作为一项网络安全领域殿堂级的荣耀,由全球权威安全研究专家担任奖项评委,筛选条件严苛,任何安全研究员获得提名就意味着其研究成果已经具有世界范围的影响力,摘冠折桂则是技术实力的绝对象征。蚂蚁集团旗下的蚂蚁安全光年实验室研究员蒋宇豪(@danis_jiang)和应鑫磊 (@0x140ce) 凭借《URB 神剑:破解 VMware 虚拟机逃逸的戈尔迪之结》研究成果,荣膺“最佳提权漏洞奖”,代表蚂蚁集团登上了全球白帽黑客最高奖项领奖台。


第二份重量级榜单,来自于微软安全响应中心(MSRC)发布的 2023 年度全球“最具价值研究员”榜单。每年度,微软根据全球安全研究人员对微软产品安全的贡献,严格甄选 TOP100,以展示他们全球领先的漏洞挖掘能力。今年蚂蚁安全光年实验室研究员张子明(@ezrak1e)成功入选该榜单并名列前茅。

在 BlackHat USA 2023 会议正式议题部分,蚂蚁安全光年实验室研究员范祚至(@pattern_F_)以《深入理解苹果用户空间文件系统》为题进行了分享,详细介绍了苹果移动端操作系统用户空间文件系统这一攻击面。本文将对这一议题进行深入解读。

全文首先介绍 Apple UserFS 的基本架构,并以访问 UserFS 分区下一个文件为例,详细分析了 UserFS 下文件系统调用的控制流。然后披漏笔者在 UserFS 中发现的 2 个真实漏洞。最后对引入 UserFS 之后,文件系统攻击面的变化进行了对比分析,以帮助读者理清文件系统漏洞挖掘的脉络。

目录


1 Apple UserFS 架构


2 UserFS 控制流分析

2.1 UserFS 文件系统的挂载流程
2.2 UserFS 文件的访问过程


3 UserFS 中的一些漏洞

3.1 CVE-2022-42861
3.2 CVE-2022-42842
3.3 kernel UAF<redacted>


4 引入 UserFS 后文件系统攻击面的变化



Apple UserFS 架构

苹果从 iOS 11 开始,引入了一个新的 app “Files”。这个应用有点类似 windows 上的文件管理器,通过这个应用,iOS 和 iPadOS 可以像在电脑上那样,浏览和操作手机上存储的文件。除此之外,iOS 开始原生支持移动存储设备,而访问移动存储设备上的文件,也必须通过“Files”应用

图1: Files

通过 iPhone 用户手册可知,移动存储设备可以使用各种常见的文件系统格式,包括 FAT32 和 exFAT。为了支持访问移动存储设备,苹果没有选择在内核中集成所需要的文件系统驱动,而是引入了一种新的机制,叫做 UserFS(用户空间文件系统)。UserFS 实现方案由下图中的组件构成,是一种包含了一系列服务进程、XPC 服务、插件、框架和内核扩展,主要代码运行在用户态的文件系统实现。我将在下一节讨论它的工作原理。

图2: Apple UserFS 架构

 UserFS 控制流分析

iOS 访问内部存储是通过内核中的文件系统驱动来实现的,只有在访问外部存储时,例如u盘、移动硬盘等,是通过,且只能通过 UserFS 来处理。因此,要分析调试 UserFS 的功能实现,首先要向 iPhone 连接一块移动硬盘。

图3

2.1 UserFS 文件系统的挂载流程

当 iPhone 接入 usb 外部存储设备时,会触发一个“usb.device.attached”事件,launchd会根据系统配置文件,把这一事件派发给 userfsd 这个服务进程处理。

图4: LaunchEvent

userfsd 启动时,会注册 IOMedia 类型的 IOKit 通知回调,当内核检测到新的块设备文件时,回调函数便会触发。在回调函数中,userfsd 通过内核接口,得到 usb 硬盘的块设备文件路径,然后调用 objc 方法"LiveMountAddDisk:reply:"来挂载该硬盘。这个方法其实是个委托方法,userfsd 并没有实际处理文件系统的挂载,它连接到了一个名为“UVFSService”的 XPC 服务,由 UVFSService 代为实现挂载硬盘挂载操作。

图5: userfsd处理 usb attached 事件

跟进远程方法“createVolumesForTheDevice:how:withReply:”的实现,UVFSService 首先会枚举“/System/Library/PrivateFrameworks/UserFS.framework/PlugIns/”这一路径下的 UserFS 插件,这些插件提供了一组标准的 api 接口,以支撑一种文件系统类型,例如 fsops_taste 会探测分区表结构以及测试分区文件系统类型。测试通过后,当前进程依然没有直接处理硬盘挂载,而是连接到了另一个 xpc 服务 livefileproviderd,并使用 MountClient 这个类对这个远程调用进行了封装。

图6: UVFSService 接管 create volume 请求

为了完成挂载过程的分析,不得不切换到 livefileproviderd 这个进程继续分析,幸运地是,这次终于看到了一些我们熟悉的东西。在“LiveMounterMountVolume:”方法中,会启动熟悉的 /sbin/mount(lifs) 系列命令进行最终的磁盘挂载操作。

图7: livefileproviderd 完成 mount 操作

在mount结束后,我们终于可以在挂载路径“/var/mobile/Library/LiveFiles/com.apple.filesystems.userfsd”目录下看到 usb 硬盘内的文件内容了。

结合上面的分析,下图总结了 usb 硬盘的挂载过程。

图8: UserFS 下的 mount 流程

2.2 UserFS 文件的访问过程

当usb硬盘成功通过UserFS挂载后,就可以访问其中存储的文件了。通常我们会编写如下形式的c语言代码 “int fd = open("/USB-hello/abc.txt", O_RDONLY);” 来打开然后读取一个文件的内容。


在 UserFS 的场景下,这一行代码不需要做任何修改,但是在内核层面,执行流会有一些不同。在 xnu 中,文件系统调用会根据文件系统类型派发到不同的驱动中处理。通过前面的分析,我们知道 UserFS 的实际挂载类型是 lifs,因此 SYS_open 会派发到 lifs.kext 驱动的 lifs_vnop_open 来处理。


lifs_vnop_open 是 lifs_open_request 的浅层封装。它创建了一个 mach 消息,消息 id 被设置为 0x2A5,以表示这是一个 SYS_open,然后把该 mach 消息发送至一个名为 lifs_port 的端口,交由远端处理这次 syscall 请求。消息体中携带了本次 syscall 请求的所有参数,并分配了一个 request_id 来唯一标识这次系统调用。

图9: lifs_open_request

通过以下代码我们知道,lifs_port 是在服务进程 livefileproviderd 中注册的,因此,SYS_open 请求实际上是发送到了用户空间,本次系统调用的控制权也因此传递给了用户态。

图10: livefileproviderd 注册 lifs_port

livefileproviderd 启动了一个 mig server,把 lifs_port 收到的 mach 消息根据 id 派发到不同的 mig handler,例如 SYS_open 是在 mig_lifs_open_send 中处理的。


这段代码的主要逻辑是,调用“LIOpen:withMode:forPID:reply:”方法处理 SYS_open 请求,然后通过 IOConnectCallStructMethod 把处理结果反馈给内核,同时把控制权返还给内核。

图11: mig server 分发处理 lifs 请求

接下来回到内核,livefileproviderd 的 IOConnectCall 实际上对应于内核的 lifs_request_done,用于反馈用户态处理系统调用的结果。


之前我们提到过的 lifs_open_request 函数,在向用户态发送 mach 消息后,会陷入等待。lifs_request_done 同时会根据 request_id 唤醒对应的 request。这样,内核态的 SYS_open 函数得以继续运行,把本次系统调用的结果返回给希望访问 usb 硬盘文件的用户态程序。

图12: 完成 SYS_open 并返回

现在简单用下图总结一下到目前为止 UserFS 下 open 系统调用的控制流,可以认为 lifs.kext 驱动把内核收到的文件相关的 syscall 转发到了用户态,服务进程 livefileproviderd 承担了 userspace syscall handler 的角色。

图13: UserFS 下 SYS_open 流程

但是到目前为止,我们只知道 SYS_open 的实际处理者是 livefileproviderd,但依然没有看到 SYS_open 的具体处理过程,需要继续跟进。在 UserFS 中,SYS_open 实际包含了两步操作,“LIOpen:withMode:forPID:reply:” 和 “LILookup:name:forClient:reply”。这两个方法都是异步的远程 xpc 方法调用,在 UserFS 的场景下,是由 UVFSService 来充当实际的 FileProvider 来提供 LIOpen 和 LILookup 操作的实现代码,因此,我们转入 UVFSService 内部的代码中。


在 UVFSService 的代码中,终于看到关联到了 UserFS 插件的 api 接口,也就是插件 livefiles_exfat.dylib 是 exFAT 文件系统读写等操作的最终实现者,例如 EXFAT_Lookup 对应打开文件,EXFAT_Read 对应读取文件内容,等。

图14: UVFSService 对接到 exfat 插件

最终,我们得到了 UserFS 下文件系统调用的完整控制流,如下图所示。

图15: UserFS 下一个 syscall 的完整流程

 UserFS 中的一些漏洞

3.1 CVE-2022-42861

首先要介绍一个 2020 年的一个漏洞,这是一个内核漏洞,在解携文件 xattr(ExtendedFileAttributes)的过程中存在一处边界检查缺陷,核心漏洞逻辑如下。通过控制 setxattr 系统调用的参数,可以令写入的数据超出 ATTR DATA 的范围,例如,可以把目标地址指定为 AppleDouble 文件头的控制字段 AD ENTRY,从而引发一系列的副作用,进而把漏洞转化为任意地址读写。

图16: xattr 内核漏洞

我们今天讨论的是 UserFS,事实上,UserFS 与内核文件系统驱动(KernelFS)在功能上是等价的,因此,相似的代码逻辑同样也在UserFS 中存在。在 UserFS 中上述漏洞对应的 xattr 处理逻辑在方法LiveFS`-[LiveFSAppleDoubleloadAttrHeader]中实现,在反编译代码中,可以看到代码逻辑与上述漏洞代码基本一致, ae_offset  这一字段的范围检查也是存在问题的,因此 UserFS 中这个漏洞依然存在。

图17: 对应的 UserFS 漏洞

关于这个 UserFS 漏洞,由于其 kernel 版本我已在 2020 年报告给苹果,它理应随 kernel 漏洞一起被修复。然而事实是,它直到 2022 年在我再次把 UserFS 版本的漏洞报告提交给苹果之后,才在 iOS 16.2 中完成修复。我想这背后的原因是,UserFS(written in objc) KernelFS(written in c)是两套独立的代码,而苹果忽略了KernelFS bugfix 应及时同步到 UserFS 这一点。

3.2 CVE-2022-42842

第二个漏洞(CVE-2022-42842)也要从 KernelFS 说起,这是另一个 xattr 漏洞,漏洞修复补丁如下图所示。

图18:xattr 内核漏洞

它的成因是在创建新的 resource fork 结构,忘记对 AD ENTRY 的 offset 字断做边界检查,从而导致了内核堆上的越界写问题。通过使用 @jaakerblom 提出的 kmsg type confusion 技术,可以成功把该漏洞转化可控的内核任意地址读写,实现内核提权

图19: 内核提权

跟上个漏洞一样,这个漏洞也存在 UserFS 版本。在 UserFS 中对应的处理逻辑在方法LiveFS`-[LiveFSAppleDoubleloadADHeader] 中,由反编译代码可以看出,这里对 offset 的处理与 KernelFS 漏洞补丁之前的逻辑是相同的,未对 offset 做进一步的检查。

图20: 对应的 UserFS 漏洞

3.3 kernel UAF<redacted>

除了服务进程、UserFS 插件等用户态代码,UserFS 还存在少量的内核代码,也就是 lifs.kext。笔者对该内核模块也做了分析,在其中发现了一处 UAF 漏洞,但是截止到目前,苹果还没有完成这个漏洞的修复工作,因此不便透露漏洞的细节。

引入UserFS后文件系统安全模型的变化

既然我们已经在 UserFS 中找到了一些漏洞,那它们的危害到底是怎样的,是不是有机会通过 UserFS 攻陷 iOS 呢?


首先,毫无疑问的是,前两个 KernelFS 版本的漏洞,是可以完成提权操作的,笔者已经通过 exploit 证明了这一点。而 UserFS 版本的漏洞,虽然也是可控的越界写问题,但是漏洞代码是在 UVFSService 这个 xpc 服务进程上下文中,这个进程是受沙盒限制的。也就是说,即使成功完成漏洞利用,我们也只能在 UVFSService 进程空间运行 shellcode,也就是只能拿到 UVFSService 同等权限,且受沙盒限制。因此,UserFS 可以显著降低文件系统漏洞的危害。


其次,虽然 lifs.kext 中存在一个 UAF 漏洞,且理论上可以认为 UAF 漏洞是可以利用的,但是该内核模块在 iOS app 默认的沙盒规则下是不可访问的,因此无法直接通过 lifs.kext 漏洞攻击内核。


但是,UVFSService 中的内存越界漏洞使我们有机会拿到 UserFS 服务进程的执行权限,而 UserFS 服务进程是可以直接与 lifs.kext 通信的,因此,可以通过 UserFS 的服务进程作为跳板,间接的触发 lifs.kext 中的漏洞,从而发起内核攻击。所以,单从理论上来讲,还是有机会通过 UserFS 攻陷 iOS 内核的。


综上,关于引入 UserFS 后,文件系统安全模型的变化,可以得到以下初步结论。

图21: 引入 UserFS 后安全模型的变化

最核心的变化是,文件 syscall 的处理者由内核转变为了普通应用程序。我们看到,用户态 app 请求访问一个文件时,在 KernelFS 下,文件 syscall 直接由对应的文件系统内核驱动来处理,而在 UserFS 下,lifs.kext 会把文件 syscall 转发到用户态的 UserFS 服务进程来处理。


由于 UserFS 的存在,从文件系统漏洞的角度来看,主要攻击目标从内核驱动变成了拥有普通权限的服务进程,相对应的,漏洞利用的能力也从内核降级为用户态权限。UserFS 的引入,对于降低文件系统漏洞的危害,是大有裨益的。


最后,UserFS 并不是为了取代 KernelFS,这两者同时存在于 iOS 中,老的 KernelFS 攻击面依然存在,而新引入的 UserFS 机制带来了新的攻击面,从这个角度来看,iOS 的攻击面其实是增加了的。但是,UserFS 只在 iOS 访问外部存储设备时发挥作用,并且 iOS 访问外部存储设备也只经过 UserFS,在这个场景下,UserFS 的漏洞降级能力将会发挥作用,可以有效阻断来自外部文件系统的潜在威胁。综上来看,我认为 UserFS 是苹果在安全上的一次成功尝试。


延伸阅读

继续滑动看下一个
蚂蚁技术AntTech
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存