When to use / when not to use
- Use: you have a located vulnerability (out-of-bounds read/write / crash / dangerous input) and want to escalate it to RCE / arbitrary read-write—the exploit development stage handed off from vulnerability analysis ([[re-crash-triage]] / [[re-vuln]])
- Use: ROP chain construction—gadget search, stack layout, ret2csu / SROP / ret2dlresolve
- Use: heap exploitation—fastbin / tcache attacks, unsorted bin leaks, double free family
- Use: seccomp sandbox bypass (ORW: open / read / write chain)
- Don't use: entry-level pwn (ret2win / simple ret2libc / format string) → [[re-pwn]]
- Don't use: you only have a crash and haven't located it yet (→ [[re-crash-triage]]); you haven't crashed yet and are looking for bugs (→ [[re-fuzzing]])
- Note: exploit verification is dynamic execution and runs in a sandbox by default ([[re-analyze/platform-tips]] highest principle, see [[re-sandbox]]);Plan before writing(pitfall 4), combine to bypass step-by-step verification
Tool preparation
See [[re-analyze/platform-tips]]—exploit verification is sandboxed by default; static preparation (gadget search / planning) can skip the sandbox, dynamic verification goes into the sandbox.
pwntools (pip install, see [[re-pwn]]) — the main workhorse for exploit scripts
pip install pwntools(official PyPI, current stable release 4.15.0) —Python version compatibility is governed by the "Tool preparation" section of [[re-pwn]](the version matrix is maintained in only one place, to avoid drift between two places)- Distro package: Debian/Ubuntu
sudo apt install python3-pwntools、Fedorasudo dnf install python3-pwntools、Archsudo pacman -S python-pwntools(official package in the Extra repository) - Python 3.12+ (Ubuntu 24.04 ships 3.12) directly pip installing reports PEP 668
externally-managed-environment—countermeasure: venv (python3 -m venv ~/venvs/pwn && source ~/venvs/pwn/bin/activate) or distro package or--break-system-packages - Verify:
pwn --version/python3 -c "import pwn; print(pwn.version)"
ropper / ROPgadget — gadget search
- ropper:
pip install ropper(official PyPI); Archsudo pacman -S ropper(official package in Extra); Debian/Ubuntu/Fedora have no official package → pip - ROPgadget:
pip install ROPgadget(official PyPI); Debian/Ubuntusudo apt install python3-ropgadget、Fedorasudo dnf install python3-ROPGadget、Archsudo pacman -S ropgadget(all are official packages) - Verify:
ropper --version;ROPgadget --binary ./target | head; common searches:ROPgadget --binary ./target | grep 'pop rdi'、ropper -f ./target --search "pop rdi"
gdb + gef/pwndbg (see [[re-gdb]]) — stack/heap layout verification
- gdb: Debian/Ubuntu
sudo apt install gdb、Fedorasudo dnf install gdb、Archsudo pacman -S gdb、macOSbrew install gdb; for 32-bit targets on Debian/Ubuntu additionally installgdb-multiarch+libc6-i386 - For heap exploitation debugging, installing pwndbg is recommended:
heap/bins(tcachebins / fastbins / unsorted) commands directly show chunk layout and bin linked lists (gef also hasheap bins, slightly weaker functionality); verify: in gdbheap binsgives output - pwndbg built-in
checksec(see [[re-pwn]] Tool preparation)
angr (optional, see [[re-angr]]) — automation of overflow size / path constraints
pip install angr——Python version compatibility is governed by the "Tool preparation" section of [[re-angr]](the version matrix is maintained in only one place, to avoid drift between two places)- Use: overflow offset is uncertain (symbolic input to solve for the constraint that overwrites the return address), exploit path validation logic is complex
- Verify:
python3 -c "import angr; print(angr.__version__)"
Operation steps
Execute in order; for each step's artifacts (exploit surface conclusions, gadget list, chain layout, PoC) record the evidence path + sha256 (for the evidence preservation method see [[re-triage]]), for citation in the report.
-
Exploitability analysis (crash point / controllable data):
- Input: minimal PoC / vulnerability report from [[re-crash-triage]] (crash point + input channel) — confirmControllable byte range and offset(
cyclicLocate the rip offset at crash time, see [[re-pwn]] step 2) pwn checksec --file ./target: NX / PIE / Canary / RELRO — each determines one constraint: stack non-executable → cannot directly run shellcode; PIE → need to leak base address; RELRO full → GOT not writable (use function pointer / stack address / libc hook instead)- seccomp check:
seccomp-tools dump ./target(Gem:gem install seccomp-tools, official channel; Arch official repositories do not have this package — only AUR has itseccomp-tools, just use gem directly) or statically findprctl/seccompcall — if bannedexecvethen the get-shell route is invalid, switch to an ORW chain (step 4) - Record: crash offset, mitigation matrix, seccomp rules, exploitable primitives (arbitrary write / stack overflow / heap out-of-bounds)
- Input: minimal PoC / vulnerability report from [[re-crash-triage]] (crash point + input channel) — confirmControllable byte range and offset(
-
ROP chain (gadget search, stack layout):
ROPgadget --binary ./target | grep -E 'pop rdi|pop rsi|pop rdx' # 64 位传参 gadget ROPgadget --binary ./target | grep 'ret' | head # 对齐用 ret ropper -f ./target --search "syscall" # SROP / shellcode 跳板- Layout template (64-bit, leak-first then execute two-stage):
padding + pop_rdi_ret + arg1 + func_addr + [对齐 ret] + 下一段... - Argument order: rdi → rsi → rdx (SysV ABI); if rdx gadget is missing use ret2csu(
__libc_csu_initinsidepop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret+ a mov gadget) or ret2dlresolve - 16-byte alignment:
callwhen$rsp % 16 == 0only then satisfiesmovaps(newer glibc versionssystem/printfused internally), if unaligned prepend oneret - Stack layout output: chain annotation diagram (offset + gadget address + arguments) + computed offset table
- Layout template (64-bit, leak-first then execute two-stage):
-
Heap exploitation (fastbin / tcache attack patterns):
- First check the glibc version (
ldd ./target | grep libc) to determine the attack surface (pitfall 3):- tcache(glibc ≥ 2.26): tcache poisoning — after double free modify
fdto near the target address → next allocation gets an arbitrary address (2.32+ pointers are XORed by safe-linking, pitfall 3); UAF can modify fd more quickly - fastbin (glibc < 2.26 / old challenges): fastbin dup — double free the same chunk twice → allocate the same address twice consecutively → forge arbitrary write on the heap
- unsorted bin leak: free a large chunk (> fastbin limit) into unsorted bin, fd/bk point to main_arena —
show/format read leak libc base (libc_base = leaked - main_arena_offset)
- tcache(glibc ≥ 2.26): tcache poisoning — after double free modify
- Use pwndbg to verify chunk by chunk:
(gdb) heap # 看 chunk 布局与地址 (gdb) bins # 看各 bin 链表(tcachebins/fastbins/unsorted) - Pattern list: double free → tcache dup; UAF → fd overwrite / leak; off-by-one (single-byte overflow) → modify next chunk size to create overlapping chunks; house of series (newer-version exploitation) mark complexity before attempting
- Goal landing: arbitrary address allocation → modify
__free_hook(≤2.33)/__malloc_hook/ writable GOT entry / stack return address → trigger get shell or arbitrary read/write
- First check the glibc version (
-
Sandbox / mitigation bypass (seccomp):
- If step 1 finds banned
execve:ORW chain— ROP calls in sequenceopen(flag_path, 0)→read(fd, buf, n)→write(1, buf, n):ROPgadget --binary ./target | grep -E 'syscall|pop rdi|pop rsi|pop rdx' # 或 libc 里的 gadget - When no ready-made
syscallgadget is found use libc's (leak libc base first) or SROP (rt_sigreturnconstruct register frame) - Mitigation bypass ideas: PIE → leak base first (format string / heap leak / puts leak, see [[re-pwn]] steps 3-4); Canary → leak then overflow (payload = pad + canary + pad + ret, canary low 1 byte is
\x00Can be truncated directly); RELRO full → don't attack GOT, switch to stack /__free_hook/environleak stack address - Combination order discipline: first leak (canary / libc base / heap base) → then overwrite (pitfall 4)
- If step 1 finds banned
-
PoC verification:
from pwn import * context.binary = './target'; context.log_level = 'debug' p = process('./target') # 沙箱内本地;远程: remote(host, port) p.sendline(payload) p.interactive()- Verification discipline:Verify each segment of the chain step by step(run the leak segment first, and only after the leaked value matches do you run the overwrite segment) — if the whole chain fails at once, you can't locate which segment is wrong (pitfall 4); in gdb, break at the chain entry and go through them one by one
sito confirm the jumps and stack layout - Get it working locally → record the PoC (file + sha256 + run output) → final remote verification; the PoC hash and vulnerability characteristics can go into [[re-ioc]]
- Verification discipline:Verify each segment of the chain step by step(run the leak segment first, and only after the leaked value matches do you run the overwrite segment) — if the whole chain fails at once, you can't locate which segment is wrong (pitfall 4); in gdb, break at the chain entry and go through them one by one
Kernel exploitation
Same framework as userland: fuzz ([[re-fuzzing]]) → crash ([[re-crash-triage]]) → exploitation (this path).
- Privilege escalation primitives: modprobe_path overwrite (triggers the kernel to execute an arbitrary path), cred structure overwrite (set uid/gid to 0), io_uring / BPF subsystem attack surface
- Heap spraying and object layout: heap spraying strategies (occupying with same-size objects), object overlap (forging objects after UAF), SLUB allocator behavior (per-CPU cache)
- Kernel UAF exploitation path: vulnerability trigger (dangling reference) → object reuse (occupation/forgery) → control flow hijack (function pointer / ops table overwrite)
- Environment: debug kernel (KASAN on, KASLR off or bypassed), gdb/kgdb breakpoints, modular testing (read-only analysis + sandbox verification)
Cross-domain combination
- [[re-vuln]]: this skill is referenced by the vulnerability discovery gateway — after steps 4-5 of the gateway (reverse locate → report), exploitable vulnerabilities enter this skill for exploit development (the closing of the loop for "from crash to PoC" in the vulnerability discovery domain)
- [[re-crash-triage]]: crash analysis / minimal PoC input is handed off to this skill (the input for the first step of exploit surface analysis); the PoC produced by this skill in turn verifies vulnerability reachability
- [[re-pwn]]: entry-level connection — stack overflow / format string / ret2libc basics are extended in this skill to complex ROP and heap exploitation
- [[re-ctf]]: advanced pwn challenges (heap challenges / high-difficulty ROP challenges) are dispatched to this skill via the gateway
- [[re-angr]] (optional): solving constraints for overflow offsets, automating exploit path validation
- [[re-gdb]]: dynamic verification of stack / heap layout (pwndbg
heap/bins); [[re-binary-core]] base for locating primitives and backdoors - [[re-sandbox]] / [[re-analyze/platform-tips]]: exploit verification runs inside a sandbox by default (highest principle); remote targets are used only for final verification
Common pitfalls and traps
- Gadget addresses are affected by ASLR / PIE (leak first): Symptom — writing the chain using static addresses (
objdumpthe gadget / function addresses that were output), it occasionally succeeds locally but always fails remotely, or the base calculated after leaking doesn't match; Cause — when PIE is enabled, the binary base address changes with ASLR (the low 12 bits of the in-page offset stay the same), and the same applies to the libc base; static addresses are only valid when there is no PIE (or the base has already been calculated); Countermeasure —leak first: the first segment of the chain leaks first (puts/printf on GOT or a heap/format leak),libc_base = leaked - offset、binary_base = leaked - offset, and all subsequent gadget/function addresses = base + static offset (the low 12 bits are directly or-ed in); after each leak, recalculate before sending the overwrite segment, don't reuse the previous round's base - tcache doubly linked list checks (safe-linking XOR / key check): Symptom——on glibc 2.32+, classic tcache poisoning crashes (
malloc(): unaligned tcache chunk detected) or double free directly aborts (free(): double free detected in tcache 2); Cause——since 2.32, the tcache fd is XORed by safe-linking (PROTECT_PTR(pos, ptr) = ptr ^ (pos >> 12), pos isthe storage address of that chunk's own fd field(for a regular chunk this ischunk_addr + 0x10), not heap_base); since 2.29, double free checks the key field: when freeing into tcache, key is written as the tcache struct address, and when freeing the same chunk again, if key is still equal to the tcache pointer it willtraverse the entire bin looking for duplicates (on a hit it directlymalloc_printerraborts)——"inserting a free of another chunk in between" does not clear the first chunk's key field, so it cannot be bypassed; Countermeasure——before writing a new fd, firstleak the heap addressthen encode:stored_fd = new_fd ^ (fd_field_addr >> 12)(the fd read via UAF is an already decrypted pointer, butthe new value must be re-encoded——PROTECT_PTRapplies to every write; only writing it back unchanged avoids encoding); there are only two ways to bypass the key check for double free: ① UAF-overwrite the key field (change it to a non-tcache pointer value) ② change the size so the chunk falls into adifferent bin(the key check only looks at the target bin's chain head; falling into another bin does not trigger it) - glibc version differences (check changes): Symptom——a heap exploit that works locally (e.g. glibc 2.31) behaves completely differently on the remote challenge (2.35 / 2.27): tcache does not exist / count limits differ /
__free_hookis gone / safe-linking appears; Cause——the heap manager tightens checks every version: 2.26 introduced tcache, 2.29 added the key check, 2.32 added safe-linking, 2.34 removed__malloc_hook/__free_hook(2.34+ uses__libc_mallocpointers or environment hijacking), from 2.37global_max_fastnarrowed touint8_t(the fastbin limit tightened accordingly), tcache initialization switched to__getrandom_nocancel; Countermeasure——ldd ./target | grep libcfirst confirm the version, choose the attack surface by version (checklist in step 3); when the local environment and remote are inconsistent, usepatchelf --set-interpreter ./ld-2.35.so --replace-needed libc.so.6 ./libc-2.35.soto swap libc for local reproduction; for 2.34+ switch targets (__malloc_hookdoes not exist → attackexitfunction pointers /setcontext/ vtable ideas); for each check tightening point (key / safe-linking / count) make a checklist and verify step by step - Bypass combinations are complex (plan before writing): Symptom——when multiple protections stack (NX+PIE+Canary+seccomp+high-version glibc), the script gets messier the more you edit it, it crashes at every turn, and after debugging for ages you find it was one small piece of logic that was wrong; Cause——each protection is an independent constraint, and combined they make a long dependency chain (leak canary → leak libc → leak heap → ORW chain); one wrong step ruins everything, and writing while trying has no global plan; Countermeasure——write the exploit plan table before writing code: list clearly (1) which primitives are needed (stack overflow / UAF / format write) (2) what needs to be leaked, the leak order and dependencies (canary must be before the overwrite, libc base must be before calling libc functions) (3) the entry and exit of each chain segment; thenimplement step by step and verify step by step: get the leak segment working on its own (values match) → get the overwrite segment working on its own (gdb confirms the return address is hit) → combine the chains; use gdb breakpoints segment by segment
si; after each change, first confirm the previous step still works - Alignment issues (movaps / stack alignment): Symptom——when the ROP chain reaches
systemorprintfJust a segmentation fault; gdb shows the instruction is atmovapscrashes; the first half of the chain is clearly all correct. The reason—the x86-64 ABI requirescallthe stack to be 16-byte aligned; GCC-generated libc functions (printf/system/execve) internallymovapsassume alignment, and misalignment directly causes SIGSEGV. Countermeasure—in the gadget chain, before the target function, add aretgadget(retso that$rsp += 8adjusts alignment); or choose an aligned version of the gadget (pop rdi; ret+ an even number of padding bytes); in 64-bit, check this before every libc function call