SSD Advisory – VhdmpiValidateVirtualDiskSurface LPE

TL;DR

A use after free vulnerability has been found in Windows VhdmpiValidateVirtualDiskSurface function. This vulnerability can be used to overwrite critical memory sections which in turn can be used to execute arbitrary code and gain SYSTEM privileges.

Vulnerability Summary

A vulnerability in the VhdmpiValidateVirtualDiskSurafe allows local attackers on Windows 10 and Windows 11, to trigger a Use After Free, which in turn can be used to gain elevated privileges on the machine.

Credit

The security researcher has reported this to the SSD Secure Disclosure program during the TyphoonPWN 2022 event.

Affected Versions

  • Windows 10
  • Windows 11

CVE

CVE-2022-35751

Vendor Response

Microsoft has released an advisory and a fix, https://msrc.microsoft.com/update-guide/vulnerability/CVE-2022-35751

Vulnerability Analysis

If we take a look at the VhdmpiValidateVirtualDiskSurface function inside of the thevhdmp.sys driver:

__int64 __fastcall VhdmpiValidateVirtualDiskSurface(_VHD_HANDLE_CONTEXT *HandleContext, PIRP Irp)
{
  void *SystemBuffer; // r14
  unsigned int v4; // ebx
  struct _VHD_SURFACE *Surface; // rdi
  _VHD_VIRTUAL_DISK *VirtualDisk; // rsi
  __int64 p_PassiveLock1; // r15
  struct _VHD_BACKING_STORE_HEADER *i; // rsi
  __int64 (__fastcall *v10)(struct _VHD_BACKING_STORE_HEADER *); // rax
  int v11; // eax
  __int64 v12; // [rsp+28h] [rbp-40h]

  if ( Irp->Tail.Overlay.CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength < 4 )
  {
    v4 = 0xC000000D;
  }
  else
  {
    SystemBuffer = Irp->AssociatedIrp.SystemBuffer;
    if...
    v4 = 0;
    Surface = VhdmpiAddRundownRefSurfaceByHandleContext(HandleContext);
    if ( Surface )
    {
      ....
      ....
      if ( !*(_DWORD *)SystemBuffer )
      {
LABEL_36:
        if ( HandleContext->VirtualDisk->field_5C && Surface->PrefetchState ) //[1]
        {
          VhdmpiReleasePrefetchState(Surface);
          VhdmpiInitializePrefetchState(Surface);
        }
      }
      if...
      VhdmpiReleaseRundownRefVirtualDiskSurface(Surface, 0);
      return v4;
    }
    v4 = 0xC0000184;
  }
  if...
  return v4;
}

We can see that the if block in [1], only checks the feasibility of Surface->PrefetchState and HandleContext->VirtualDisk->field_5C, and then calls the Release and Init Surface.

If when then go into the function VhdmpiReleasePrefetchState function:

void __fastcall VhdmpiReleasePrefetchState(struct _VHD_SURFACE *Surface)
{
  _VHD_PREFETCH_STATE *Buffer; // rbx
  void *PrefetchFileHandle; // rcx
  struct _IO_STATUS_BLOCK IoStatusBlock; // [rsp+50h] [rbp-18h] BYREF
  union _LARGE_INTEGER ByteOffset; // [rsp+70h] [rbp+8h] BYREF

  Buffer = Surface->PrefetchState;
  if ( Buffer )
  {
    if ( Buffer->PrefetchFileHandle )
    {
      if ( Buffer->field_AC )
        VhdmpiWritePrefetchLog(Surface->PrefetchState);
      if ( Buffer->field_AE )
      {
        ByteOffset.QuadPart = 0i64;
        PrefetchFileHandle = (void *)Buffer->PrefetchFileHandle;
        IoStatusBlock = 0i64;
        ZwWriteFile(PrefetchFileHandle, 0i64, 0i64, 0i64, &IoStatusBlock, Buffer, 0x50u, &ByteOffset, 0i64);
      }
    }
    VhdmpiFreePrefetchState(Buffer);
    Surface->PrefetchState = 0i64;
  }
}

We can see that the function will call VhdmpiFreePrefetchState to free Surface->PrefetchState without previously locking this variable from being freed by another thread.

This means that we can create two threads that call the VhdmpiValidateVirtualDiskSurface function continuously until a double free occurs at Surface-> PrefetchState check.

However, there is a problem here, in order to be able to call the VhdmpiValidateVirtualDiskSurface function when we have Open Virtual Disk we need a flag with the value 0x80000008 set, but in the OpenVirtualDisk function of the virtdisk.dll, the library checks for value and exit if (Flags & 0xFFFFF104)! = 0.

DWORD __stdcall OpenVirtualDisk(
        PVIRTUAL_STORAGE_TYPE VirtualStorageType,
        PCWSTR Path,
        VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask,
        OPEN_VIRTUAL_DISK_FLAG Flags,
        POPEN_VIRTUAL_DISK_PARAMETERS Parameters,
        PHANDLE Handle)
{
  P[0] = 0i64;
  v20 = 0;
  if ( (Microsoft_Windows_VIRTDISKEnableBits & 1) != 0 )
  {
    if ( VirtualStorageType )
      DeviceId = VirtualStorageType->DeviceId;
    else
      DeviceId = 0i64;
    McTemplateU0zq_EventWriteTransfer(VirtualStorageType, Path, Path, DeviceId);
  }
  v11 = (PVOID *)WPP_GLOBAL_Control;
  if...
  if ( !VirtualStorageType || !Path || (Flags & 0xFFFFF104) != 0 ) // patch
    goto LABEL_11;
  v13 = Parameters;
  if ( !Parameters || Parameters->Version == OPEN_VIRTUAL_DISK_VERSION_1 )
  {
    v14 = (VirtualDiskAccessMask & 0xFFC0FFFF) == 0;
  }
  else
  {
    if ( (unsigned int)(Parameters->Version - 2) > 1 )
    {
LABEL_11:
      FinalFilePath = 87;
      goto LABEL_25;
    }
    v14 = VirtualDiskAccessMask == VIRTUAL_DISK_ACCESS_NONE;
  }
  ....
  ....
}

Therefore we need to first patch the if statement checking this flags value. The patch will stay in the mem when the poc is launched.

Exploit
Race to win
We can see that in the VhdmpiReleasePrefetchState function, the driver is doing write operations to the Prefetch file. We can refer to a trick to race this case by looking up googleprojectzero‘s Trapping Virtual Memory Access exploit.

In our case, we will be modifying the SMB server so that when victim access to files shared from the server, we can completely control file read and write operations from the client.

Thereby it is possible to trigger the UAF from double free.

We can refer to the project SMBLibrary to be able to create a SMB server. But when checking the source of SMBLibrary it seems that it has only two basic permissions: Read and Write File, while the functions in virtdisk.dll need some special permissions for the file. So I decide to choose impacket smbserver to modify my SMB server.

NOTE: Due to issues with the exploit code (its file size), and use of third party proprietary libraries SSD Secure Disclosure cannot at this time release the full exploit.