Vulnerability Breakdown ·
CVE-2024-30084 - Windows Kernel Streaming Driver Elevation of Privilege Vulnerability
A vulnerability in the Microsoft Kernel Streaming driver (ks.sys) caused by a race condition in how user-supplied data is handled. The driver performs a second, unchecked read of a buffer after it has already been validated, introducing a time-of-check to time-of-use (TOCTOU) flaw. This allows modification of the input between the two accesses, leading the kernel to dereference attacker-controlled pointers and potentially execute arbitrary code in kernel mode, resulting in elevation of privileges to SYSTEM.
CVE-2025-30084: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-30084
Vulnerability Type: Time-of-check Time-of-use (TOCTOU) Race Condition
Tested On: Windows 11 23H2
Driver Version: ks.sys - 10.0.22621.2506
Vulnerability analysis
The vulnerability exists in the KspPropertyHandler() function, which handles the IOCTL call made to the ks.sys driver. The IOCTL uses the METHOD_NEITHER transfer type, which is considered unsafe. In this case, the user input (Type3InputBuffer) is directly copied to a newly allocated buffer (SystemBuffer).
NTSTATUS __fastcall KspPropertyHandler( PIRP Irp, unsigned int a2, struct _LIST_ENTRY *a3, __int64 (__fastcall *a4)(_QWORD, _QWORD, _QWORD), int a5, __int64 a6, unsigned int a7){ struct _LIST_ENTRY *v7; // r11 struct _IO_STACK_LOCATION *CurrentStackLocation; // rdi
[::]
memset(Irp->AssociatedIrp.SystemBuffer, 0, v12); memmove( (char *)Irp->AssociatedIrp.SystemBuffer + v12, CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, InputBufferLength); *(_DWORD *)((char *)Irp->AssociatedIrp.SystemBuffer + v12 + 20) = v16; // First copy of user input
[::]
if ( v24 <= 0x2000 ) { if ( v24 != 0x2000 ) { if ( v24 == 256 ) return 0; if ( v24 != 512 ) { if ( v24 == 2048 ) return SerializePropertySet(Irp, v22, (__int64)v7, v28); if ( v24 == 4096 ) return UnserializePropertySet(Irp, v22, (__int64)v7); // Vulnerable function call goto LABEL_99; }
[::]Inside the UnserializePropertySet() function, the user input (Type3InputBuffer) is copied again to a newly allocated region (InBuffer). This creates a double fetch scenario. If an attacker modifies the input in user space between the first and second fetch, it can result in inconsistent data being processed.
__int64 __fastcall UnserializePropertySet(IRP *irp, __int64 a2, __int64 a3){ _QWORD *SystemBuffer; // rdi struct _IO_STACK_LOCATION *CurrentStackLocation; // r13 unsigned int OutputBufferLength; // ebx _DWORD *InBuffer; // rsi int v9; // r14d char *v10; // rdi unsigned int v11; // ebx char *v12; // r15 unsigned int v13; // ebx char *OutBuffer; // rdi ULONG OutSize; // eax NTSTATUS v16; // r12d __int64 v17; // rax ULONG InSize; // [rsp+88h] [rbp+10h] ULONG BytesReturned; // [rsp+98h] [rbp+20h] BYREF
if ( *(_DWORD *)(a2 + 16) ) return 3221225485LL; SystemBuffer = irp->AssociatedIrp.SystemBuffer; CurrentStackLocation = irp->Tail.Overlay.CurrentStackLocation; InSize = CurrentStackLocation->Parameters.DeviceIoControl.InputBufferLength; OutputBufferLength = CurrentStackLocation->Parameters.DeviceIoControl.OutputBufferLength; InBuffer = ExAllocatePoolWithTag(NonPagedPoolNx, InSize, 0x7070534Bu); if ( !InBuffer ) return 3221225626LL; if ( OutputBufferLength < 0x14 ) {LABEL_23: ExFreePoolWithTag(InBuffer, 0); return 3221225990LL; } else { if ( **(_QWORD **)a3 != *SystemBuffer || *(_QWORD *)(*(_QWORD *)a3 + 8LL) != SystemBuffer[1] ) {LABEL_22: ExFreePoolWithTag(InBuffer, 0); return 3221225485LL; } memmove(InBuffer, CurrentStackLocation->Parameters.DeviceIoControl.Type3InputBuffer, InSize); // Double Fetch InBuffer[5] = 2; v9 = *((_DWORD *)SystemBuffer + 4); v10 = (char *)SystemBuffer + 20; v11 = OutputBufferLength - 20; while ( v11 && v9 ) { if ( v11 < 0x20 ) goto LABEL_23; v12 = v10; if ( *((_DWORD *)v10 + 5) ) goto LABEL_22; InBuffer[4] = *((_DWORD *)v10 + 6); v13 = v11 - 32; OutBuffer = v10 + 32; OutSize = *((_DWORD *)v12 + 7); if ( OutSize > v13 ) goto LABEL_23; v16 = KsSynchronousIoControlDevice( CurrentStackLocation->FileObject, 0, CurrentStackLocation->Parameters.DeviceIoControl.IoControlCode, InBuffer, InSize, OutBuffer, OutSize, &BytesReturned); if ( v16 < 0 ) { ExFreePoolWithTag(InBuffer, 0); return (unsigned int)v16; } v17 = *((unsigned int *)v12 + 7); if ( (unsigned int)v17 < v13 ) { v17 = ((_DWORD)v17 + 3) & 0xFFFFFFFC; *((_DWORD *)v12 + 7) = v17; if ( (unsigned int)v17 >= v13 ) goto LABEL_23; } v10 = &OutBuffer[v17]; v11 = v13 - v17; --v9; } ExFreePoolWithTag(InBuffer, 0); if ( v11 ) return 3221225476LL; else return v9 != 0 ? 0xC0000004 : 0; }}The exploitation methodology is similar to CVE-2024-35250, with a slight modification. In the previous exploit, KSPROPERTY→Set was set to KSPROPSETID_DrmAudioStream to invoke the vulnerable function call. To demonstrate this double-fetch vulnerability, a different Set value is used during the first copy, and just before the second copy occurs, it is changed back to KSPROPSETID_DrmAudioStream. This allows ksthunk.sys to invoke the target function (RtlClearAllBits) with the intended arguments.
Using any of the following property set GUIDs as the initial copy and then changing it back to KSPROPSETID_DrmAudioStream will lead to the same vulnerability. These GUIDs were identified through reverse engineering the KspPropertyHandler() function, where it checks the GUID that triggers a call to UnserializePropertySet(), as well as from the ks.h header file, as follows:
- 8C134960-51AD-11CF-878A-94F801C10000 - KSPROPSETID_Pin
- 720D4AC0-7533-11D0-A5D6-28DB04C10000 - KSPROPSETID_Topology
- 1464EDA5-6A8F-11D1-9AA7-00A0C9223196 - KSPROPSETID_General
Exploit
Tested on: Windows 11 23H2
Working POC: https://github.com/ghostbyt3/WinDriver-EXP/tree/main/CVE-2024-30084
PS C:\Users\h4x\Desktop> whoamidesktop-3rgmcon\h4xPS C:\Users\h4x\Desktop> .\CVE-2024-30084.exe[+] Successfully opened internal topology of an audio device![+] NT base address fffff8010fe00000[+] Found EPROCESS of the current process FFFFD70BF11E70C0[+] Found KTHREAD of the current thread FFFFD70BED9A4080[+] Found EPROCESS of the system.exe FFFFD70BEA104040[+] pFakeBitmapAddr = 0x0000000010000000[+] Beginning race condition...[+] Calling Kernel Streaming....[+] Stealing system's Token..[+] Replacing KTHREAD.PreviousMode as UserMode..[+] Spawning shell as SYSTEM...Microsoft Windows [Version 10.0.22631.3593](c) Microsoft Corporation. All rights reserved.
C:\Users\h4x\Desktop>whoamint authority\systemPatch Analysis
The patched version adds checks to ensure the KSPROPERTY→Set is not set to KSPROPSETID_DrmAudioStream. The first memmove/memcpy operation in KspPropertyHandler() has also been removed.
STATUS __fastcall KspPropertyHandler( PIRP Irp, unsigned int a2, __int64 a3, __int64 (__fastcall *a4)(_QWORD, _QWORD, _QWORD), unsigned int a5, __int64 a6, unsigned int a7){ __int64 v7; // rbx struct _IO_STACK_LOCATION *CurrentStackLocation; // r15 unsigned int Options; // r10d
[::]
if ( v23 == 4096 ) { LOBYTE(v20) = (unsigned int)Feature_2849679676__private_IsEnabledDeviceUsage() != 0; if ( !v20 || *(_QWORD *)v21 != *(_QWORD *)&KSPROPSETID_DrmAudioStream.Data1 || *(_QWORD *)(v21 + 8) != *(_QWORD *)KSPROPSETID_DrmAudioStream.Data4 ) // Fix { return UnserializePropertySet((__int64)Irp, v21, v7); }
[::]