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);
}
[::]