沙盒逃逸编年史:对CVE-2019-0880漏洞的深入分析

2020-09-02 10:52:128904人阅读

0x01 漏洞分析

这篇文章描述了splwow64.exe中的一个可利用的任意指针取消引用漏洞。此漏洞可以从Internet Explorer渲染器进程触发,并允许沙盒逃逸。

该漏洞允许通过将特定的LPC消息制作到splwow64.exe进程,从而从低完整性进程中任意写入splwow64.exe的地址空间。

该漏洞已经在Windows 7 x64和Windows 10 x64计算机上进行了测试。

在本文中,我将描述该漏洞的位置以及如何利用该漏洞从Internet Explorer沙盒中实现完整的沙盒逃逸。

该漏洞似乎未在Windows 7上进行修补,因此,我不会发布用于利用此漏洞的源代码。

漏洞描述

该漏洞在于处理对splwow64.exe进程的特定LPC调用,从而可以使用任意参数在splwow64地址空间中调用memcpy函数。这为攻击者提供了极为强大的原语,因为它允许在不依赖内核漏洞的情况下,在更高完整性进程的内存中进行写操作,并逃逸浏览器沙箱。

splwow64.exe进程

Splwow64.exe是一个Microsoft可执行文件,每当32位应用程序访问你已安装的打印机之一时,该文件便会执行。此进程特别有趣,因为它是Internet Explorer策略列入白名单的进程之一。换句话说,任何来自低完整性IE渲染器进程的调用都将生成此进程,这将导致splwow64.exe作为中等完整性进程被加载。

在深入分析漏洞本身之前,让我们尝试深入了解Splwow64流程的实际工作方式。

LPC端口创建

开始执行后,splwow64.exe将通过调用ZwCreatePort API创建LPC端口,并将开始等待传入连接。

让我们看一下ZwCreatePort函数。

 NTSTATUS NTAPI ZwCreatePort(PHANDLE,POBJECT_ATTRIBUTES,ULONG,ULONG,ULONG);

如你所见,第二个参数是指向“对象属性”结构的指针,该结构将指向UNICODE_STRING函数,该函数包含由进程创建的LPC端口的名称。

为了连接到该LPC端口,我们需要了解的第一件事是如何生成LPC端口名称!如果你将检查ZwCreatePort的Object Attributes指针参数多次(每次重新引导计算机后),你会注意到LPC端口名的一部分将在每次重新引导后更改。

LPC端口名称如下所示:

在Windows 10上

 \\RPC Control\\UmpdProxy_1_VARIABLEPART_0_2000

在Windows 7上

 \\RPC Control\\UmpdProxy_1_VARIABLEPART_0_0

每次重新启动计算机后,VARIABLEPART都会更改。

这意味着要能够从IE Sandboxed进程实际连接到该LPC端口,我们将需要了解如何生成LPC端口名称。

对我们来说幸运的是,生成LPC端口名的可变部分的算法非常简单,看起来像这样:

· 调用OpenProcessToken API,将当前进程的句柄作为参数传递。

· 调用GetTokenInformation API,将TokenStatistics作为TOKEN_INFORMATION_CLASS传递

· 访问新获得的TOKEN_STATISTICS结构的AuthenticationId.LowPart字段,并将其转换为十六进制字符串。

现在,你可以连接到LPC端口了!

LPC消息处理

现在,我们将需要了解splwow64进程如何解析LPC消息。由于对splwow64进程的内部工作原理的完整解释不在本文的讨论范围之内,因此我们将只关注漏洞分析,以获取有关漏洞本身及其利用的足够知识。

简而言之,splwow64将以以下方式解析传入的LPC消息:

· 它仅接受长度为0x20字节的传入消息。

· 它将把位于LPC消息的偏移量0x30、0x38和0x40处的三个指针作为参数传递给GdiPrinterThunk函数。

这意味着只要发送的消息为0x20字节长,我们就可以使用任意参数调用GdiPrinterThunk函数!

现在,我们将需要了解GdiPrinterThunk实际如何工作,以查看是否可以通过控制函数参数来触发一些有趣的东西。

GdiPrinterThunk函数

GdiPrinterThunk是一个非常复杂的函数,其工作流程将由位于第一个参数中指定地址的偏移量0x4处的字节确定。如前所述,我们实际上可以通过编写特定的LPC消息来控制GdiPrinterThunk函数传递的三个参数。换句话说,这意味着我们能够控制GdiPrinterThunk工作流程!

此函数是实际的任意解除引用漏洞所在的位置:如果作为第一个参数传递的地址的偏移量0x4处的字节为0x76(在Windows 7上为0x75),则将使用由攻击者完全控制的参数来调用memcpy函数。 !

1591686012167316.png

看一下伪C代码:

 void GdiPrinterThunk(LPVOID firstAddress, LPVOID secondAddress, LPVOID thirdAddress)
 {
   ...
 
     if(*((BYTE*)(firstAddress + 0x4)) == 0x75){
       ULONG64 memcpyDestinationAddress = *((ULONG64*)(firstAddress + 0x20));
 
       if(memcpyDestinationAddress != NULL){
         ULONG64 sourceAddress = *((ULONG64*)(firstAddress + 0x18));
         DWORD copySize = *((DWORD*)(firstAddress + 0x28));
 
         memcpy(memcpyDestinationAddress,sourceAddress,copySize);
       }
     }
 
 ...
 }

这是一个任意指针取消引用,使我们可以从低完整性进程中有意地写入splwow64.exe地址空间!

但是我们如何触发它呢?

如前所述,将使用以下参数调用GdiPrinterThunk函数:

· RCX设置为LPC消息的偏移量0x30中指定的地址

· RDX设置为在LPC消息的偏移量0x40中指定的地址

· R8设置为在LPC消息的偏移量0x38中指定的地址

要构造我们的“任意地址写”原语,我们可以创建一个共享节,并在LPC消息的偏移量0x30处指定此共享节的地址。

创建共享部分后,我们可以在所需的偏移量处设置要写入的地址和要从中读取的地址,然后发送LPC消息!

解析LPC消息时,GdiPrinterThunk将访问在消息的偏移量0x30处指定的共享内存地址,如果从共享内存地址功能开始的第四个字节为0x76(在Windows 7上为0x75),则将调用具有在共享内存地址中指定由攻击者控制的参数的memcpy函数!

0x02 漏洞利用

我们可以构建功能非常强大的“ Write What Where Primitive”!不幸的是,我们仍然需要解决一些问题才能真正逃逸Internet Explorer沙箱:

· W ^ X内存:可执行页的内存不可写。换句话说,我们不能只是将有效负载写入可执行内存页面。

· ASLR:我们有能力在所需的地方写我们想要的东西。问题是我们没有信息泄漏,该信息泄漏将使我们能够知道目标进程中函数指针的地址,从而通过覆盖它来执行代码。

· 任意执行:我们可以任意写入splwow64进程的内存,但是我们仍然不知道如何在需要时触发有效负载。

W ^ X内存

由于我们无法写入可执行内存页面,也无法通过调用VirtualProtect使内存页面可写,因此我们需要考虑其他事项。首先想到的是用LoadLibraryA或WinExec之类的函数的地址覆盖现有的函数指针,只要我们能够使用任意参数触发对此函数指针的调用,我们就可以完成!

看一下OpenPrinterW函数:

1591686012190439.png

如你在上面的截图中看到的,该函数将移至RAX寄存器中位于winspool.drv函数.data部分中的地址,并通过调用LdrpValidateUserCallTarget(控制流保护)来验证该地址。

在Windows 7上,它将仅跳转到.data节中保存的地址,如下图所示。

1591686012164919.png

由于winspool.drv DLL的.data部分是可写的,因此我们可以使用“任意地址写”原语来覆盖存储的地址!

1591686013151240.png

如上图所示,该地址已被我们选择的地址覆盖!

逆向分析

为了实现上述目的,我们将需要知道splwow64进程中winspool.drv .data节的地址!幸运的是,Windows系统上的地址空间布局随机化是基于引导的。换句话说,每个系统DLL的基地址在每个进程中都是相同的,直到下一个系统重新启动为止,无论其完整性级别如何。

这意味着将winspool.drv DLL加载到沙盒进程的地址空间中,查找其数据部分,然后从该处找到指向OpenPrinter2W函数的指针,并使用获得的地址在远程进程中通过调用将其覆盖就足够了。

尽管有上述说明,但这还不足以实现完整覆盖:在Windows 7系统上,Internet Explorer渲染器进程是32位的,而splwow64进程是64位的进程。换句话说,我们将无法获得成功利用此漏洞所需的64位地址。

为了解决这个问题,我们有两种选择:

· 生成一个64位进程以泄漏所需的地址。

· 使用Heaven’s gate 技巧在我们的Internet Explorer Wow64进程中加载64位DLL,并泄漏地址。

生成64位进程

这是解决问题的最简单,最稳定的方法。由于Internet Explorer允许从Low Integrity渲染器进程写入LocalLow文件夹,因此要泄漏所需的地址,我们只需要以下各项:

· 创建一个LeakAddresses.exe 64位可执行文件,该文件将加载winspool.drv DLL,获取所需的地址,并将结果保存在LocalLow文件夹中的文件中。

· 将LeakAddresses可执行文件拖放到LocalLow文件夹中,然后通过调用CreateProcess函数运行它。由于不会发生特权提升,因此该执行对用户不可见:该文件将作为低完整性进程执行。

· 通过读取我们的LeakAddresses可执行文件创建的文件来获取所需的地址。

· 使用获得的地址来制作LPC消息,以实现Write What Where原语。

使用 Heaven’s gate 技巧

有关此技术实际上如何工作的完整描述超出了本文的范围。简而言之,Heaven’s gate是利用Windows在64位系统上实现32位代码仿真以加载64位DLL。

通过使用此技术,你将能够在Internet Explorer进程的地址空间中加载64位DLL,从而可以泄漏所需的地址,而无需在磁盘上写入任何文件。

任意代码执行

实际上,为什么选择在OpenPrinterW函数中覆盖指向OpenPrinter2W函数的指针是有原因的。 在花了一些时间逆向 GdiPrinterThunk函数之后,我注意到,如果我们将byte参数设置为0x6A(在Windows 7上为0x69),则会发出对OpenPrinterW函数的调用,第一个参数可由我们控制:因为指向OpenPrinter2W的指针已被我们覆盖,将发出对我们选择的函数的调用!

不幸的是,我们只能控制第一个参数,因此,你应该选择仅包含一个参数的函数。我选择了以下两个函数:

· LoadLibraryA:此函数将在从其调用的进程的地址空间中加载一个库。由于此函数仅使用一个参数,因此我们可以将DLL放到Local Low文件夹中,并在splwow64.exe进程中触发对LoadLibraryA的调用。这样,我们的DLL将由中等完整性级别的进程加载,从而使Internet Explorer沙箱逃逸。

· system:由于WinExec函数具有两个参数,而我们只能控制一个参数,由于msvcrt.dll DLL已加载到splwow64地址空间中,因此我们可以调用此函数,系统函数将仅使用一个参数,并作为命令行将其作为完整性进程来执行。例如,攻击者可以以Medium Integrity用户身份运行Powershell命令。

0x03 分析总结

尽管它很简单,但此漏洞仍使攻击者能够以极其简单和确定性的方式完全逃逸Internet Explorer沙箱的攻击!我发现旧版Windows组件中的这些类型的漏洞非常神奇,并且我认为将来会发现更多此类漏洞。

根据我的测试,此漏洞利用代码仍然可以在功能完善的Windows 7系统上运行,因此,我选择不发布漏洞利用代码。


本文转载自:嘶吼

作者:h1apwn

本文翻译自:https://byteraptors.github.io/windows/exploitation/2020/05/24/sandboxescape.html

原文地址:https://www.4hou.com/posts/x9lE

0
现金券
0
兑换券
立即领取
领取成功