写在前面的话
本文由研究人员Mickey发表于其个人博客,内容来源于该研究人员在OBTS v6.0上的演讲内容,本文将讨论苹果的OTA更新机制细节,更新过程中暴露的安全问题,以及绕过SIP后如何实现任意内核代码执行。
苹果的OTA更新
在大多数情况下,macOS更新是通过OTA更新过程完成的。OTA,即over-the-air。在系统设置中,我们可以通过点击“现在更新”按钮直接更新系统:
OTA更新机制属于增量更新,因此比完整的系统更新要更快,更新包体积也更小。
该机制主要用于次要版本更新,通常每两个月更新一次。但如果苹果认为内核中存在一个紧急高危漏洞,同时该漏洞已经被积极利用,且无法通过RSR(快速安全响应)进行修补的话,则会发布紧急OTA更新(从Apple CDN服务器下载):
从上表中可以看到,OTA软件包是为当前操作系统版本定制的。
比如说,为了更新到12.6,12.5和12.4的包是不同的。更新过程的本质是应用修补程序代码,因此不同的操作系统版本具有不同的修补程序代码。在大多数情况下,系统越旧,包大小越大。
OTA更新包内容
下载并解压缩OTA包后,可以看到包的内容如下:
boot目录中包含与引导过程相关的内容,增量更新的真正修补程序代码位于名为payloadv2的目录中。其中有一个名为payload.bom的关键文件,其中包含了OTA包中的所有条目及其校验和值,文件payload.bom.signature用于验证payload.bom文件的完整性,文件pre.bom和post.bom列出了更新前后系统上的所有条目及其校验和值,Info.plist文件提供了关于当前OTA包的一些基本信息,如预版本、目标版本等。
在payloadv2文件夹中,有一些重要的文件需要注意。新系统中的新数据文件会被压缩到名为data_payload的文件中,ecc_data文件夹包含一些与文件权限相关的文件,links.txt文件列出了新系统的所有硬链接,而removed.txt文件列出了需要从当前系统中删除的所有条目。
更新步骤
常规的OTA更新步骤可以抽象成下列三个阶段:
阶段1
系统下载UpdateBrainService的URL位于下面这个XML文件中:
System/Library/AssetsV2/com_apple_MobileAsset_MobileSoftwareUpdate_MacUpdateBrain/com_apple_MobileAsset_MobileSoftwareUpdate_MacUpdateBrain.xml:
我们可以看到相关的基地址URL和构建完整URL的路径参数,其中包含了版本信息、发布日期、包大小、SHA1值和其他有用的信息。
阶段1运行流程如下:
获取到下载URL之后,进程nsurlsessiond负责将UpdateBrainService下载到临时目录。同时,com.apple.StreamingUnzipService会将其解压缩到相同的临时位置。接下来,mobileassetd进程则将解压缩的内容移动到下列受信任位置:
/System/Library/AssetsV2/com_apple_MobileAsset_MobileSoftwareUpdate_MacUpdateBrain/
最后,在launchd进程生成UpdateBrainService之前,会将xpc服务Bundle复制到目标路径。
阶段2
阶段2和阶段1有些类似:
不同的是XML路径:
/System/Library/AssetsV2/com_apple_MobileAsset_MacSoftwareUpdate/com_apple_MobileAsset_MacSoftwareUpdate.xml
以及下载URL和目标路径:
/System/Library/AssetsV2/com_apple_MobileAsset_MacSoftwareUpdate/
阶段3
阶段3则是UpdateBrainService本身,特定的xpc服务信息如下:
绕过签名认证(CVE-2022-42791)
修改OTA包
根据之前的描述,我们可以确认的是OTA包的最终路径(/System/Library/AssetsV2/com_apple_MobileAsset_MacSoftwareUpdate/)受SIP保护。
但提取的内容首先会存储在临时路径下:
/var/folders/zz/zyxvpxvq6csfxvn_n00000y800007k/T/com.apple.nsurlsessiond/CFNetworkDownload_XXXXXX.tmp/[hash].asset
而这个路径是完全没有限制的,它属于nsurlsessiond,且可以由root用户直接修改。
因此,在mobilesetd进程将其移动到受信任位置之前,我们有一个时间窗口来修改内容。这样一来,可信位置中OTA包的内容就不可信了,需要进行验证。
当我尝试直接替换payload.com文件时,由于权限问题,mobileassetd进程无法调用文件API,因此会拒绝移动到最终路径:
但事实是,一旦它通过了这里的检查,它将调用rename API并移动包的内容,所以我们替换目标文件为时还过早。
操作成功之后的日志如下:
幸运的是,字符串“Moving file in …”暗示着绕过检测的时间窗口期,因此一旦我们从日志中监控到了这个字符串,我们就可以替换目标文件了。
OTA包验证
正如之前所说,payload.bom文件会列出OTA包中所有的条目和其校验和:
下面的函数可以用于验证包内容
它会打开并读取payload.bom文件,并使用最终路径的真实摘要值来根payload.bom文件中指定的文件摘要值进行对比:
如果匹配不成功,函数返回false,验证失败。但如果它只验证payload.bom本身呢?
这里还有另一个函数负责执行验证,即verify_package_signature:
它首先会打开payload.bom文件并计算其SHA1,然后再打开payload.bom.signature文件并读取文件内容签名。接下来,它会从系统证书文件“/System/Library/SoftwareUpdateCertificates/iPhoneSoftwareUpdate.pem”中获取公钥,而这个公钥是受到SIP和SSV保护的:
最后,它会调用SecKeyVerifySignature API并使用公钥验证签名,计算SHA1和文件内容签名:
SIP绕过
能够绕过签名认证之后,我们就能够绕过SIP了。函数ParallelPatchRemoveFiles能够读取OTA包中的removed.txt文件,并删除txt文件中所有的条目:
此时,我们就能够通过修改txt文件来拿到一个用于移除任意SIP保护路径的原语。此方法适用于所有的macOS平台,包括Intel和苹果M芯片在内。
内核影响
如果想要在绕过SIP之后实现操作系统内核劫持,最大的问题就是SSV保护。SSV,即“Signed System Volume”,这个功能是macOS Big Sur才引入的新功能,但这个功能仍然使用了一个独立的只读系统卷宗来保护系统文件。
现在的事实在于,苹果需要通过OTA更新来升级操作系统内核文件,因此OTA更新过程必须能够具备“打破”SSV保护的能力。
那OTA更新又是如何做到的呢?
macOS系统有一个隐藏的更新卷宗(/System/Volumes/Update/mnt1),它是当前操作系统的快照,所有的补丁修复其实都应用到了这个快照上。如果更新成功,则更新并重启真实系统。如果更新失败,则回退到之前的快照。
在对UpdateBrainService进行深入分析之后,我们总结出了以下OTA更新流程:
Function Name | Action |
verify_package_[signature|contents] | 验证OTA包完整性 |
prepare_snapshot | 在/System/Volumes/Update/mnt1卷宗准备一个当前操作系统快照/snapshot |
copy_patched_files | 将payloadv2/patches/XXX(BXDIFF5格式)补丁应用到快照上 |
copy_archived_files | 从payload.[000-999](AppleArchive格式)提取Payload并应用到快照上,根据payload.XXX.ecc(AppleArchive格式)文件修复文件权限 |
copy_archived_data_files | 将data_payload提取至快照 |
verify_postbom | 根据post.bom验证快照中新系统文件的校验和 |
SIP绕过问题(CVE-2022-46722)
如下图所示:
提取的文件夹本身是受限的,但其中的文件和其他目录并不是受限的,比如说AssetData文件夹。因此,我们就可以直接修改OTA包的内容了。
这样一来,验证OTA包的完整性就完全没有了意义,我们可以在这个OTA包验证完成之后,修改OTA包内容,并直接拿到SIP绕过原语。
如何在绕过SIP之后实现内核渗透
我们可以直接通过SIP绕过原语来实现内核补丁文件替换,并最终执行任意内核代码。
通过监控“/System/Volumes/Update/restore.log”,我们发现它会在更新过程中输出一些有价值的信息,比如说“Unarchiving files in parallel…”:
这里我写了一个名为spin_for_log的函数并在所有的更新完成之后等待一个正确的时机:
这样一来,我们就可以尝试使用定制内容来替换原内核了。
降级风险(CVE-2023-35983)
UpdateBrainService的主执行文件其实是一个空调用:
所有的函数都是在UpdateBrainLibrary.dylib中实现的,更新代码也不例外:
因此,攻击者就可以通过替换dylib中的旧版本信息来实现降级攻击了,而且旧版本内容由苹果签名,Library验证也能通过。
跟OTA更新包类似,UpdateBrainService Bundle包可以被root用户修改,这样一来攻击者就能够在UpdateBrainService被移动到受信任路径之前替换其内容(旧版本内容),并实现降级。
苹果的解决方案
现在,苹果重新调整了整个流程,现在系统会确保整个进程是受信任的,且包内容不会被篡改。苹果具体的修复方案如下:
首先,nsurlsessiond进程会创建一个名为downloadDir的受限目录,然后将UpdateBrainService Bundle包下载到这个受信位置。接下来,新引入的com.apple.StreamingUnzipService.privileged.xpc服务会在受信位置同步进行解压缩操作。
当下载和解压操作完成之后,进程之后要执行的任务就和之前类似了。
需要注意的是,nsurlsessiond进程和com.apple.StreamingUnzipService.privileged.xpc服务现在拥有了新的特殊权限com.apple.rootless.storage.MobileAssetDownload来修改受限目录。
值得一提的是,OTA包的下载过程现在也遵循这种新的处理流程了。
PoC概念验证
PoC地址:【CVE-2022-46722】
安全演示
演示视频:【点我观看】