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> whoami
desktop-3rgmcon\h4x
PS 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>whoami
nt authority\system
Patch 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);
}
[::]