沙盒逃逸编年史:对CVE-2019-0880漏洞的深入分析 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

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

h1apwn 资讯 2020-09-01 10:15:00
455351
收藏

导语:这篇文章描述了splwow64.exe中的一个可利用的任意指针取消引用漏洞。

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函数。 !

看一下伪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函数:

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

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

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

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

逆向分析

为了实现上述目的,我们将需要知道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系统上运行,因此,我选择不发布漏洞利用代码。

本文翻译自:https://byteraptors.github.io/windows/exploitation/2020/05/24/sandboxescape.html如若转载,请注明原文地址:
  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2024 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务