通过通用主机控制接口逃逸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>

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖