通过通用主机控制接口逃逸VMWARE

2019-08-30 约 157 字 预计阅读 1 分钟

声明:本文 【通过通用主机控制接口逃逸VMWARE】 由作者 aliyuuu 于 2019-08-30 09:08:00 首发 先知社区 曾经 浏览数 122 次

感谢 aliyuuu 的辛苦付出!

通过通用主机控制接口逃逸VMWARE

原文地址:https://www.zerodayinitiative.com/blog/2019/8/15/taking-control-of-vmware-through-the-universal-host-control-interface-part-2

0x00 前言

这篇博客着眼于普渡大学暑期实习生Abdulellah Alsaheel 的Pwn2Own获奖项目。这是该获奖漏洞研究系列的第二部分。您可以在这里阅读该系列的第一部分

在今年的温哥华Pwn2Own比赛期间,Fluoroacetate团队展示了他们如何在VMware Workstation中实现从客户操作系统权限逃逸至宿主机操作系统权限。他们利用了虚拟USB 1.1 UHCI(通用主机控制器接口)中的越界读/写漏洞(ZDI-19-421)。

虽然此漏洞影响了各种VMware产品,但本博客的分析主要基于Workstation 15.0.3,采用Fluoroacetate的exp。该漏洞在VMware Workstation 15.0.4中被修补,编号为VMSA-2019-0005.1

0x01 漏洞说明

为了让VMware guest虚拟机访问USB设备,VMware guest虚拟机需安装名为uhci_hcd的内核设备驱动程序。“hcd”代表“主机控制器驱动程序”。此驱动程序允许guest虚拟机与主机端的主机控制器接口(HCI)进行通信,主机端通过该硬件接口与物理USB端口进行通信。通过向USB设备定义的各种端点发送或接收USB请求块(URB)分组来完成通信。USB设备通过各端点发送或接收数据包传送实现通信,这些端点或从主机接收数据包(OUT),或向主机发送数据包(IN)。我们通过将特制的OUT数据包发送到一个名为Bulk的特定端点触发漏洞。

uhci_hcd驱动程序处理的数据包在内存中使用uhci_td(传输描述符)结构表示:

<center>传输描述符(TD)结构

请注意,token字段包含一些未标明的位对齐子段。尤其注意的是,最低8位表示的是“分组ID”,定义了分组的类型。前10位是名为MaxLen的长度字段。

为了触发此漏洞,guest虚拟机必须发送精心设计的TD结构,将Packet ID设置为OUT(0xE1)。此外,由MaxLen子字段指示的TD的缓冲区长度必须大于0x40字节才能溢出堆上的对象。使用windbg attach调试vmware-vmx.exe,然后触发漏洞,我们会得到以下访问冲突:

调用堆栈显示了一系列处理UHCI请求的函数:

程序在调用memcpy从TD的缓冲区复制数据的过程中发生崩溃:

这是memcpy从TD缓冲区复制到堆中的内容:

让我们看看目标缓冲区大小是多少:

缓冲区的大小为0x58,vmware-vmx通过[number_of_TD_structures]*0x40+0x18计算目标缓冲区的大小。因为这一次我们只发送了一个TD结构,缓冲区大小是1*0x40+0x18=0x58字节。

在调用memcpy的过程中,我们可以精确指定要复制的字节数。为此,我们将OUT TD的token字段的子字段MaxLen(21位到31位)设置为所需的memcpy大小减1。

很明显,有了这个,我们就可以溢出堆。但是,除了溢出堆之外,漏洞利用作者还能利用此漏洞执行其他越界写入。函数NewURB()(位于vmware_vmx+0x165710)用来处理传入的URB数据包。每次函数NewURB()接收TD时,它都会将TD的MaxLen值添加到称为光标的变量中。光标变量指向函数接收TD结构时应该写入的位置。通过这种方式,该MaxLen字段可用于在处理后续TD时部分地控制目的地址。

0x02 漏洞利用

为了利用此漏洞,必须先对vmware-vmx进程进行堆布局。为了执行堆布局工作,漏洞利用主要依赖于前端(客户端)上的SVGA3D协议,它用于通过SVGA FIFO与主机通信。在后端(主机端),VMware使用DX11Renderer组件处理请求。漏洞利用代码从初始化阶段开始,先初始化SVGA FIFO内存,然后分配SVGA3D对象表。

堆布局的总体策略如下。exp首先创建hole或未分配内存的island,每个都是0x158字节大小,正是分配一定数量的TD加缓冲区头所需的大小。TD可能会在某个hole内进行分配。之后,exp创建一个名为资源容器的结构,大小为0x150字节,表示与图形界面相关的数据。这样做是为了破坏紧跟在TD之后的资源容器

漏洞利用代码使用以下步骤布局堆:
- 定义并绑定大小为0x5000的Context内存对象。
- 定义大小为0x1000 的内存对象(SPRAY_OBJ),用于重复地绑定结构(例如,着色器)。
- 定义大小为0x158的2400个着色器,将它们绑定到SPRAY_OBJ。之后,使用SVGA_3D_CMD_SET_SHADER在主机中喷射着色器
- 迭代喷射着色器并执行以下操作:
---释放偶数编号的着色器
---创建一个surface,分配一个大小为0x150的资源容器。通常是在着色器空出的hole中进行分配。此外,主机将分配大小为0x160的关联数据缓冲区。由于大小不同,这些数据缓冲区将位于低碎片堆(LFH)的单独区域中。每个0x150字节的资源容器将包含指针指向其关联的0x160字节数据缓冲区。
---再创建两个surface,分配两个大小为0x160的资源容器。由于它们大小为0x160字节,在此步骤中分配的资源容器在内存中位于上一步骤的0x160字节数据缓冲区附近。出于这个原因,这些资源容器被称为“相邻”资源容器。下面将解释这些“相邻”资源容器的目的。
- 释放所有剩余着色器,释放大小为0x158的块。这些大小为0x158的hole将与大小为0x150的资源容器交替。

越界写功能

在强调漏洞利用的一般结构之前,先介绍触发漏洞的WriteOOB函数。在整个漏洞利用期间,为了不同的目的我们需要多次调用WriteOOB,例如泄漏vmware-vmx.exekernel32.dll基址,以及最终的代码执行步骤。函数的参数如下:

WriteOOB()(void * data, size_t data_size, uint32_t offset)

data参数是一个指向缓冲区的指针,该缓冲区包含我们打算写入主机堆栈的数据。size参数指定数据的长度。最后,offset参数指定要写入数据的位置,表示相对被损坏的资源容器头的偏移。

该函数首先分配和初始化帧列表和五个TD结构。回想一下,在堆布局过程中,我们创建了大小为0x158的hole。此函数发送五个TD结构,因此堆上分配的缓冲区大小为5*0x40+0x18=0x158。我们希望这能够分配在hole上,这样,紧随TD之后,可以破坏一个资源容器

除了最后一个TD(终止TD)之外,每个TD结构使用link字段链接到下一个TD结构。对于前三个TD结构,MaxLen子字段设置为0x40。前三个TD结构的分组ID子字段设置为USB_PID_SOF,因此对于每个TD结构,光标将前进0x41。第四TD结构的分组ID也设置为USB_PID_SOF,但是对于该TD,MaxLen设置为从offset参数计算的值。这使光标成为了一个可控量。在第五TD中,分组ID设置为USB_PID_OUT,以便将data缓冲器的内容写入光标位置。

内存泄漏并绕过ASLR

现在,已经准备好了漏洞原语,接下来我们就要泄漏vmware-vmx.exe的基地址。我们通过在TD之后破坏资源容器中数据缓冲区的指针来完成的。该指针位于资源容器内的偏移0x138处。该漏洞通过将其替换为0x00来破坏数据指针的最低有效字节。当引用损坏的指针时,它不再指向数据缓冲区。相反,它指向位于数据缓冲区附近的0x160字节的“相邻”资源容器。这些“相邻”资源容器中有一些函数指针,当数据被复制回guest虚拟机时,将得到vmware-vmx.exe基地址:

为精确控制数据指针,我们需要计算移动的光标字节数:

  • 最初,光标指向大小为0x158的缓冲区的开头,考虑到第一个0x18字节被保留为缓冲区头,我们只能控制0x140字节。
  • 资源容器的堆头占用0x8个字节。
  • 资源容器中数据指针的偏移量为0x138。

因此,总和为0x140 + 0x8 + 0x138 = 0x280,这是光标必须移动的字节数,指向我们打算填写的字节。

为了将泄漏的函数指针写回到guest虚拟机,exp迭代喷射了2400次surface,并利用SVGA_3D_CMD_SURFACE_COPY获取每个的数据。它继续迭代,直到找到能够泄露vmware-vmx.exe基址的函数指针。

kernel32.dll基址,利用过程和偏移计算与vmware-vmx.exe是一样的,除了一个小细节。它不是填充单个字节指针,而是利用vmware_vmx_base_address+0x7D42D8覆盖整个数据指针,即Kernel32!MultiByteToWideCharStub在导入地址表中的存储地址,这可以泄露出kernel32.dll基地址。

逃逸到宿主机做代码执行

为了实现代码执行,exp再次覆盖堆上的资源容器。这次,漏洞会覆盖资源容器的0x120字节。这完成了三件事:

​ 1 - 将字符串calc.exe写入资源容器
​ 2 - 填写资源容器的某些必要字段。
​ 3 - 覆盖资源容器中偏移量0x120的函数指针,使它指向kernel32!WinExec

这是资源容器在被破坏后的样子:

最后当guest主机调用SVGA_3D_CMD_SURFACE_COPY访问此资源容器时,WinExec函数将被调用,calc.exe字符串的地址作为第一个参数传递。该漏洞必须遍历所有2400个surface,以确保使用到被破坏的资源容器

漏洞利用摘要

查看上述材料,漏洞利用可以总结如下:

​ - 堆布局:
​ ---分配大小为0x158的2400个着色器
​ ---释放大小为0x158的备用着色器
​ ---对于每个被释放的着色器,使用大小为0x150的资源容器(例如,surface)填充hole。在此资源容器中,将有一个指向大小为0x160的关联数据缓冲区的指针。另外还要创建两个着色器,分配两个大小为0x160且与数据缓冲区相邻的资源容器
​ - 泄漏vmware-vmx.exe基地址(迭代64次,直到找到地址):
​ ---调用WriteOOB破坏大小为0x150的资源容器并将指针的最低有效字节填写到其数据缓冲区,以便它指向相邻的0x160字节资源容器。该内存包含一些函数指针。
​ ---遍历2400个surface并使用SVGA_3D_CMD_SURFACE_COPY将数据传回到guest主机,直到泄漏出地址。
​ - 泄漏kernel32.dll基地址(迭代64次,直到找到地址):
​ ---调用WriteOOB破坏大小为0x150的资源容器,并将VMWare-vmx.exe中kernel32.dll导入表中的函数地址填充到其数据缓冲区的指针。
​ ---遍历2400个surface并使用SVGA_3D_CMD_SURFACE_COPY将数据传回到guest主机,直到泄漏出地址。
​ - 逃离guest主机并在宿主机获得代码执行(迭代64次,直到代码执行):
​ ---调用WriteOOB以破坏大小为0x150的资源容器。填充“calc.exe”字符串并使用kernel32!WinExec地址填充到函数指针位置。
​ --- 通过SVGA_3D_CMD_SURFACE_COPY迭代访问2400个surface,直到触发WinExec的执行。

0x03 结论

基于特定的内存损坏错误,可有效实现VMware虚拟机逃逸。该漏洞利用通过半暴力的方式实现代码执行。在VMware中发现可利用的漏洞仍然是一个挑战,但一旦发现漏洞,它也不会太过难以利用。VMware SVGA提供了各种操作和对象,例如资源容器着色器。通过调整它们的大小以及存储的数据和函数指针,可以有效的辅助漏洞利用工作。

您可以在Twitter上找到我@ 0xAlsaheel,并关注ZDI 团队获取最新的漏洞利用技术和安全补丁。

</center>

关键词:[‘技术文章’, ‘技术文章’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now