保安成小偷帮凶,沙箱被利用提权——攻击macOS内核居然可以这样!

在疫情防控常态化的背景之下,为了“停课不停学”,众多高校纷纷开启了网课模式。近日,支付宝光年安全实验室收到清华大学网络与信息安全实验室(NISL)的邀请,为同学们分享在 macOS 内核(XNU)上的攻防实战思路和成果。为了让更多听众能够远程参与,本次分享以钉钉直播的方式公开进行。包括实验室的同学在内,一共吸引了两百多位听众参与了线上公开课交流。

保安成小偷帮凶,沙箱被利用提权——攻击macOS内核居然可以这样!

下面带读者一起回顾一下这次分享的内容

原理解析

目前主流的操作系统对应用程序区分对待,应用不同的沙盒配置、用户组等措施区分可执行代码的特权。通常系统代码执行最高权限就是操作系统内核和扩展模块(驱动程序)。恶意程序为了获得尽可能多的能力,例如隐藏自身运行痕迹或者读取机密信息(如钥匙串中保存到应用程序密码和 iCloud 访问凭据等),就要想方设法从远程代码执行作为起点,突破沙箱限制和用户组限制,获得内核代码执行的权限。此类攻击方式称为本地权限提升(Local Escalation of Privileges)。

在以往公开的研究当中,无论是各种黑客破解秀还是学术论文,攻防的战场都相对集中在内存安全上。通过利用错误的边界检查、对象生命周期管理等未定义行为,转换成漏洞利用原语,改写内存中关键结构的内容实现高权限的任意代码执行。曾有论文《SoK: Eternal War in Memory》将其称之为“永恒之战”,可见内存安全问题当下层出不穷和亟待解决的现状。既然暂时不能彻底消灭漏洞,就提升攻击门槛。操作系统引入了多种缓解措施(mitigation)来提升利用难度,硬件厂商也在指令集层面实现了控制流保护、内存标签扩展等技术进行对抗。

但操作系统软件的复杂度决定了攻击手段不会局限在某个方面。支付宝光年安全实验室就向苹果报告了 macOS 的多个逻辑漏洞,避开所有的通用内存防御,实现 100% 稳定性的权限提升。在 macOS High Sierra(10.13.6)上从 Safari 浏览器的沙箱内逃逸,然后获取 root 权限,最终让 XNU 内核加载一个没有代码签名的内核扩展,实现对 macOS 的完全控制。本次公开课的内容就是详细讲解了其中攻击内核的部分的技术细节和启示。

macOS 允许 root 权限的用户安装内核驱动程序,但要求驱动程序必须经过苹果认证签名,以及在安装后需要用户额外确认一次(User-Approved Kernel Extension Loading)才会激活。通过分析 XNU 的源代码可以发现,针对驱动程序的签名验证还有用户确认的逻辑实际上都在用户态实现。这些进程都必须拥有 XNU 认可的“良民证”,即嵌入在代码签名中,称之为 entitlements 的一段 XML 字符串。

只有启用了com.apple.private.security.

kext-management 特权,并拥有苹果签名的程序才能安装内核驱动。同时macOS 默认配置下启用了 SIP (System Intergrity Protection),禁止注入代码到其他进程,即使有 root 权限也不行。系统内核认为这足以保证攻击者无法伪造这种特权,因此来自用户态的请求是可信的,会直接执行提交过来的代码。

保安成小偷帮凶,沙箱被利用提权——攻击macOS内核居然可以这样!

我们在这种模型上找到了实现的漏洞。由于内核完全信任了特定用户态进程,因此我们不需要攻击 XNU 本身,而只需要在用户态想办法注入恶意代码到对应进程,打破信任边界即可。macOS 自带了一些命令用于应用程序的调试和采样,其具有 com.apple.system-task-ports 特权,可以绕过 SIP 访问受限制的系统进程。如果被采样的进程是 swift 编写的,为了还原程序上下文堆栈的符号信息,系统会在特定的目录下搜索 swift 相关的动态链接库并载入。搜索路径有四个备选,优先级最高的路径受到系统保护无法修改。但我们给进程强制添加一个 sandbox,禁止其访问系统自带的合法路径,就会强迫其走备选的代码分支,从环境变量 DEVELOPER_DIR 控制的目录中载入任意代码,造成运行库劫持。

但这一步又遇到了一个问题。iOS / macOS 为了防御动态库劫持,使用了一种名为 Library Validation 的手段,即动态载入运行库之前会检查其代码签名是否来自苹果或者同一开发者。我们在旧的 macOS 系统上找到了对应程序,但是不带 Library Validation 的版本,成功触发这一代码劫持漏洞。有了用户态任意注入代码的权限之后,通过注入代码到管理内核驱动的服务进程,窃取其 com.apple.private.security.

kext-management 特权,成功欺骗 XNU 内核执行了没有任何代码签名的模块。

保安成小偷帮凶,沙箱被利用提权——攻击macOS内核居然可以这样!

攻击过程

  1. 运行任意一个 swift 程序

  2. 从旧版本(El Capitan)macOS 系统中复制一个没有保护的 symbols  命令

  3. 释放一个恶意的 libswiftDemangle.dylib 到任意目录

  4. 调用 sandbox_init_with_parameters 添加沙箱配置,取消当前进程对系统自带 libswiftDemangle.dylib 的访问权限

  5. 设置当前进程的 DEVELOPER_DIR 环境变量,指向恶意的路径

  6. 执行 symbols 命令对 swift 程序采样,触发代码劫持漏洞,载入恶意的动态链接库

  7. 使用这个 symbols 的调试特权附加到 kextd 进程,注入最后阶段的攻击载荷

  8. 使用 kextd 提交驱动程序的特权向 XNU 内核注入无签名的扩展,实现用户态 root 权限到内核任意代码执行

官方修复

在这个攻击链条中最重要的突破口实际上是利用了代码动态库劫持,将恶意代码注入到合法的进程中,从而绕过基于代码签名的信任边界。苹果在 macOS Mojave 之后强制启用了 Hardened Runtime 机制,即将 Library Validation 验证机制的启用条件从 opt-in 改成了 opt-out,默认全部开启。针对从老版本系统复制“可信”程序的问题,苹果在新的 AppleMobileFileIntegrity 驱动中硬编码了进程采样工具的标识符,即不再信任旧版本的程序。此外针对任意进程调试,苹果引入了一个新的 com.apple.system-task-ports.safe 特权,以限制旧版程序的能力。目前,此问题已经得到修复。

不过最关键的 XNU 驱动验证上仍然保留了用户态验证的策略。同时 kextd 进程还加入了 com.apple.security.cs.allow-unsigned-executable-memory 特权,也就是仍然执行无签名的代码,给漏洞利用提供了方便。假设 kextd 之后出现进程间通信的漏洞,能够接管控制流,仍有可能通过用户态特殊进程的任意代码执行来攻击内核。

总结和教训

通常攻击操作系统内核,大量的研究和案例大体都是类似的思路,即通过 syscall 或者平台特定的内核调用机制(如 XNU 的 IOKit),触发内存访问违例,篡改系统内核的结构甚至劫持控制流,从而获得更高的代码执行权限。与以往的研究相比,这次分享的案例具有鲜明的特点:

  1. 完全没有利用常规的内存安全问题,严格意义上甚至没有攻击 XNU 本身,而是滥用了其信任边界

  2. 从旧版本的操作系统中复制具有合法苹果代码签名,同时允许加载第三方代码的程序,进一步突破信任模型

  3. 原本作为系统安全防线的沙箱机制,在特定条件下却变成了漏洞利用的重要条件,“以子之盾攻子之盾”。

这次线上分享除了这一套原创漏洞和利用之外,还简单介绍了 Psychic Paper、以及 phoenhex&qwerty team 在Pwn2Own 2019 所用的内核漏洞等来自其他安全研究员的案例。网络安全专业的同学在做研究时通常更喜欢去解决一些普适性的问题,有时会忽视这种平台特性相关的设计缺陷。而在实战应用中,这种类型的漏洞有时能以更低的成本实现攻击的目的。


支付宝光年安全实验室

隶属于支付宝安全实验室。通过对基础软件及设备的安全研究,达到全球顶尖破解能力,致力于保障蚂蚁金服及行业金融级基础设施安全。因发现并报告行业系统漏洞,数十次获得Google、Apple等国际厂商致谢。


雷锋网版权文章,未经授权禁止转载。详情见转载须知

保安成小偷帮凶,沙箱被利用提权——攻击macOS内核居然可以这样!

(完)