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
0x08stored 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 order | Buffer | Address | Next stored as |
|---|---|---|---|
| 1st | A | 0x1008 | NULL |
| 2nd | B | 0x2008 | align(A) = 0x1000 |
| 3rd | C | 0x3008 | align(B) = 0x2000 |
| 4th | D | 0x4008 | align(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:
0x3000→0x0000000800000000(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
LookasideListreturnstarget_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 (at0x4000)- 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_addressas 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)
