CVE-2024-38245 — Kernel Streaming Frame Buffer Misalignment → LookasideList Corruption

Last updated: 2026-04-12
Severity: High
Component: ks.sys (Kernel Streaming — KS Allocator)
Bug Class: Non-Power-of-Two Alignment Mask → LookasideList Pointer Corruption → Arbitrary Write
Privilege Escalation: User → SYSTEM
Patch: August 2024 Patch Tuesday

Vulnerability Summary

The KS Allocator’s DefAllocatorAlloc function accepts an alignment mask from the user-supplied KSALLOCATOR_FRAMING structure without validating it is a power-of-two-minus-one value. When a non-standard alignment mask (e.g., 7 → 8-byte alignment) is used, frame buffers are allocated with addresses ending in 0x8. When these buffers are freed into a LookasideList (which requires 0x10-byte alignment), the alignment correction corrupts linked list pointers — ultimately writing a user-controlled value to a user-chosen kernel address via a subtle pointer confusion chain.


Root Cause Analysis

KS Allocator Alignment Handling

char *DefAllocatorAlloc(POOL_TYPE PoolType, SIZE_T NumberOfBytes, ULONG Alignment) {
    if (Alignment >= FILE_OCTA_ALIGNMENT)  // only validates if >= 0xFFF
        FileAlignment = Alignment;
    // else: uses any alignment value including invalid ones!
    
    buffer = ExAllocatePoolWithTag(PoolType | 0x400, size, 'adSK');  // [1]
    if (buffer) {
        padding = (~FileAlignment & (buffer + FileAlignment + 4)) - buffer;
        buffer += padding;
        *(buffer - 1) = padding;  // [2] stores padding size as byte before buffer
    }
}

With alignment = 7 (8-byte alignment, not power-of-two-minus-1):

  • All returned buffer addresses end in 0x8 (e.g., 0xFFFF...1008, 0xFFFF...2008, …)
  • Padding size 0x08 stored as byte at *(buffer - 1)

LookasideList 0x10 Alignment Requirement

ExFreeToNPagedLookasideList aligns chunks before storing:

ExFreeToNPagedLookasideList(..., PSLIST_ENTRY Chunk) {
    NextChunk = ListHead->FreeChunk & 0xFFFFFFFFFFFFFFF0;  // align to 0x10
    Chunk->Next = NextChunk;   // ← stores ALIGNED address as Next pointer
    ListHead->FreeChunk = Chunk;
}

The Corruption Chain

Free 4 buffers A, B, C, D (all ending in 0x8):

Free orderBufferAddressNext stored as
1stA0x1008NULL
2ndB0x2008align(A) = 0x1000
3rdC0x3008align(B) = 0x2000
4thD0x4008align(C) = 0x3000

D’s Next = 0x3000: This points to the start of C’s pool chunk, which is the padding area (4 bytes) followed by the frame buffer. The 4 bytes at 0x3000 contain the padding size value 0x00000008.

As a 64-bit pointer: 0x3000 + 4 bytes read as pointer → 0x0000000800000000 (little-endian). This is a user-mode address!

Triggering the Chain

Allocation from corrupted LookasideList:

ExAllocateFromNPagedLookasideList:
    ReturnChunk = ListHead->FreeChunk & 0xFFFFFFFFFFFFFFF0;  // align = D at 0x4000
    ListHead->FreeChunk = ReturnChunk->Next;  // = D.Next = 0x3000 (aligned C)

After one allocation (returns D), list head = 0x3000. After consuming remaining entries:

  • 0x30000x0000000800000000 (via padding bytes = user-mode pointer)
  • List head becomes 0x0000000800000000

If attacker maps memory at 0x800000000 (user-mode) and crafts a fake LookasideList entry there with Next = target_kernel_address:

  • Next allocation from LookasideList returns target_kernel_address
  • When frame data is written to this allocation, target_kernel_address is written

Exploitation Technique

Step 1: Setup Misaligned Allocator

Create KS Allocator with KSALLOCATOR_FRAMING.FileAlignment = 7 (8-byte misalignment). Pre-allocate 4 frame buffers via KSSTATE_RUN.

Step 2: Trigger LookasideList Corruption

Trigger STOP → all 4 frame buffers freed to LookasideList. After freeing:

  • LookasideList.FreeChunk = D (at 0x4000)
  • D.Next = 0x3000 (C, aligned)
  • C.Next = 0x2000 (B, aligned)
  • B.Next = 0x1000 (A, aligned)
  • A.Next = 0x0000000800000000 (via padding corruption)

Step 3: Control LookasideList via User Memory

Map memory at 0x800000000. Write:

*(PVOID*)0x800000000 = target_kernel_address;
// This becomes the "Next" pointer after LookasideList traverses to 0x800000000

Step 4: Allocation Triggers Write

When KS allocates frame buffers for the next read operation:

  • LookasideList returns buffers from the corrupted list
  • Eventually returns target_kernel_address as a “frame buffer”
  • Worker thread writes frame data (device data) to target_kernel_address

Step 5: Restore LookasideList

After exploitation, restore LookasideList to a valid state — otherwise next allocation causes BSoD. This requires knowing what value was written and being able to restore the corrupted pointer.

EoP Target

Use NtQuerySystemInformation to find ETHREAD/EPROCESS addresses. Target token privilege bits for AAW. Alternative: use same SeDebugPrivilege/SeChangeNotifyPrivilege LUID technique (see CVE-2024-30090).


Key Primitives Used

  • User-controlled KSALLOCATOR_FRAMING alignment mask
  • LookasideList pointer corruption via misaligned free
  • User-mode address reachable from kernel (Windows lacks SMAP)
  • Arbitrary kernel write via corrupted frame buffer destination

Proof-of-Concept Notes

  • Works on Windows 11 23H2 (confirmed)
  • Requires webcam or device supporting KS Allocator
  • Physical memory must be mappable at 0x800000000 (user-mode VA)
  • Must restore LookasideList after exploitation

References

  • Angelboy (DEVCORE), “Frame by Frame, Kernel Streaming Keeps Giving Vulnerabilities”, devco.re, 2025-05-17 (OffensiveCon 2025)
  • MSRC: CVE-2024-38245
  • See also: Kernel Streaming, Cve 2024 38238
  • See also: Heap Grooming (LookasideList mechanics)