Reverse engineering tells you a binary is buggy. Exploitation is the separate craft of turning that bug into a controlled primitive and then into code execution, against the mitigations the target actually ships. The work is a chain of conversions — bug to primitive, primitive to leak, leak to control — and each link is chosen by what the binary's protections allow, not by rote.
When to Use
- You have a crash or a known bug class in an ELF/PE and need to assess exploitability and build a working exploit
- Constructing a ROP/JOP chain, a ret2libc, or a heap-grooming sequence
- Defeating a specific mitigation set (ASLR, NX/DEP, canary, PIE, RELRO) on a lab or CTF target
- Working an authorized binary-exploitation task or a CTF pwn challenge
When NOT to Use
- Recovering what the binary does — disassembly, decompilation, algorithm
recovery — is
analyzing-binaries; do that first if the bug is not yet understood. - Unpacking or de-virtualizing a protected binary before you can even see
the code — use
unpacking-protected-binaries. - Web, deserialization, or injection bugs — those are their own skills
(
testing-web-applications,exploiting-deserialization); this skill is native memory corruption only. - Kernel/driver exploitation specifically — the primitives differ enough (no libc, SMEP/SMAP/KASLR, syscall context) that it is out of scope here; the userland method still informs it.
- Fuzzing to find the bug — that is a discovery step; this skill starts once you have a crash.
Enumerate the Mitigations First
The exploit you can build is dictated by what is turned on. Read them before
planning anything (checksec, or pwntools' checksec/ELF):
| Mitigation | What it blocks | The usual answer |
|---|---|---|
| NX / DEP | Executing your shellcode on the stack/heap | ROP / ret2libc — reuse existing executable code |
| ASLR | Hardcoding addresses | An info leak to recover a base, then compute offsets |
| Stack canary | Naive stack-return overwrite | Leak the canary, overwrite something else, or brute a forking server |
| PIE | Hardcoding the binary's own addresses | Leak a binary address; until then only relative/partial overwrites |
| Partial RELRO | Little — GOT is still writable | GOT overwrite is on the table |
| Full RELRO | GOT overwrite | Target __malloc_hook/__free_hook (pre-2.34), or a ROP-only path |
The two questions that order the work: do I have an info leak (ASLR/PIE), and where can I write (RELRO). Everything else follows from those.
Bug Class to Primitive
Name what you have before reaching for a technique:
- Stack buffer overflow → direct control of saved return address; the cleanest path to a ROP chain, canary permitting.
- Heap overflow / off-by-one → corrupt adjacent chunk metadata or an adjacent object's pointers; primitive depends on the heap layout you can groom.
- Use-after-free → reallocate the freed slot with attacker data so a stale pointer is used under your control; classic route to a fake vtable/object.
- Type confusion → treat one type's fields as another's, often yielding a read/write or a controlled call directly.
- Format string →
%nfor a targeted write and%p/%sfor a leak; often both a leak and a write from one bug. - Integer overflow / OOB → usually a means to one of the above (a short-computed size producing a heap overflow).
The goal of this step is one of three primitives: control flow hijack, an arbitrary read, or an arbitrary write. Get to a primitive, then build.
ROP and Code Reuse
With NX on, you execute the program's own bytes in an order you choose:
- Find gadgets with ROPgadget or ropper; look for the
pop rdi; ret-style register controls that set up a call. - ret2libc — return into
system("/bin/sh")once you have a libc base; the most direct chain when libc is available and leaked. - ret2csu to control
rdx/rsiwhen no clean gadget exists, using__libc_csu_init's tail on pre-2.34 binaries. - SROP (sigreturn-oriented) when gadgets are scarce but you can stage a
fake signal frame and reach a
sigreturn. - ret2dlresolve to call a function by forcing the dynamic linker to resolve it, useful with no leak and partial RELRO.
- one_gadget to find a single libc address that execs a shell, collapsing the final stage when its constraints hold.
Match the leaked libc to a build with libc-database so the offsets are right —
a chain built against the wrong libc fails silently.
Heap Exploitation (glibc ptmalloc)
Heap work is layout work: you arrange allocations so a corruption lands on something useful. The technique depends on the glibc version, because the allocator's checks have tightened steadily.
- tcache poisoning (2.26+) — the highest-leverage modern primitive: corrupt
a freed tcache chunk's
nextto return an arbitrary pointer from the nextmalloc. On 2.32+ the pointer is mangled by safe-linking (XORed with the address it is stored at, shifted right by 12), so you need a heap leak to forge it, and 2.34 removed the__free_hook/__malloc_hooktargets that used to end the chain. - fastbin dup — double-free into the fastbin to return the same chunk twice; watch the tcache-first behaviour on modern glibc.
- unsorted-bin leak — a freed chunk in the unsorted bin holds a
main_arenapointer; reading it leaks a libc address to defeat ASLR. - House of * techniques (Force, Orange, Einherjar, and newer) — each abuses a specific allocator invariant for an arbitrary write when the simple primitives are blocked; reach for them only when the layout demands it.
Drive all of this in pwndbg or GEF — heap, bins, and tcache commands
show the state so you groom against reality, not a mental model.
The Loop
Build the exploit incrementally with pwntools, and verify each conversion
before adding the next: reach the crash, prove control of the instruction
pointer, land a leak and confirm the computed base, then the write, then the
shell. When a stage misbehaves, attach with gdb.attach() at exactly that
point rather than staring at the whole chain. A working local exploit that
fails remotely is almost always an environment gap — libc version, stack
alignment (a stray ret for movaps), or an env-dependent leak.
Scope and Authorization
Exploit development belongs on binaries you own, CTF targets, lab VMs, or
software you are explicitly engaged to test — the lab_only egress mode is the
default here, since a working exploit run against production is an intrusion,
not research. Two specific edges:
- A 0-day in third-party software puts you in coordinated disclosure, not publication. Build the proof, report it through the vendor's channel, and do not drop a weaponized exploit publicly ahead of a fix.
- Never test an exploit against a system you do not control to "see if it works." A memory-corruption exploit that lands crashes the service when it does not; that is an availability impact on someone else's estate.
Rationalizations to Reject
- "It crashes on EIP/RIP, so it's exploitable." Control of the instruction pointer is the start, not the finish. Modern mitigations mean a crash may be a dead end without a leak; assess reachability of a primitive before claiming exploitability.
- "It works locally, ship it." Remote libc, ASLR entropy, and stack alignment differ. Match the target libc and test against the real environment before reporting it as working.
- "Just spray shellcode on the stack." NX has been default for over a decade. If you are writing shellcode to the stack and jumping to it, confirm NX is actually off before spending time on it.
- "one_gadget will handle the last step." Its constraints (specific registers null, specific stack state) frequently do not hold. Check them; keep a ret2libc as the fallback.
- "The heap technique from the writeup will just work." Allocator checks are version-specific — tcache keys, count checks, safe-linking. Confirm the target glibc version and that the technique still applies to it.
ATT&CK Coverage
Generated from secskills-core/ttp-index.json — edit that file, then run
python3 scripts/sync_attack.py --write. Re-verify IDs against the
current ATT&CK release before citing them in a report.
Execution (TA0002)
- T1203 Exploitation for Client Execution — see also
performing-social-engineering,analyzing-malware
Privilege Escalation (TA0004)
- T1068 Exploitation for Privilege Escalation — see also
escalating-linux-privileges,escalating-windows-privileges
Lateral Movement (TA0008)
- T1210 Exploitation of Remote Services — see also
enumerating-network-services
Detection content for any of these: engineering-detections. Proactive search: hunting-threats. Post-compromise: responding-to-incidents.
References
analyzing-binaries— recovering the code and understanding the bug firstunpacking-protected-binaries— when the binary must be unpacked/de-virtualizedreviewing-cryptography— when the target is a crypto primitive, not memorymaintaining-engagement-state— egress modes and the lab-only default here