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.