# Exploiting Memory Corruption

> Develop working exploits from memory-corruption bugs in native binaries — turning a stack/heap overflow, use-after-free, or type confusion into control flow, an arbitrary read/write, and a shell, and defeating ASLR, NX, stack canaries, PIE, and RELRO along the way. Use when you have a crashing input or a known bug class in an ELF/PE and need a primitive, when building a ROP chain with pwntools and ROPgadget, or when working a CTF pwn challenge or authorized binary exploitation.

- Skill: `trilwu/exploiting-memory-corruption` (Agent Skill)
- Install (CLI): `npx skillmds add trilwu/exploiting-memory-corruption`
- Raw SKILL.md: https://api.skillmd.com/api/skills/trilwu/exploiting-memory-corruption/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: trilwu (https://skillmd.com/u/trilwu)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/trilwu/exploiting-memory-corruption

---


# Exploiting Memory Corruption

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** → `%n` for a targeted write and `%p`/`%s` for 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`/`rsi` when 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 `next` to return an arbitrary pointer from the next
  `malloc`. 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_hook` targets 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_arena`
  pointer; 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.

<!-- attack:start -->

## 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](https://attack.mitre.org/techniques/T1203/) Exploitation for Client Execution — see also `performing-social-engineering`, `analyzing-malware`

**Privilege Escalation** (TA0004)

- [T1068](https://attack.mitre.org/techniques/T1068/) Exploitation for Privilege Escalation — see also `escalating-linux-privileges`, `escalating-windows-privileges`

**Lateral Movement** (TA0008)

- [T1210](https://attack.mitre.org/techniques/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`.

<!-- attack:end -->

## References

- `analyzing-binaries` — recovering the code and understanding the bug first
- `unpacking-protected-binaries` — when the binary must be unpacked/de-virtualized
- `reviewing-cryptography` — when the target is a crypto primitive, not memory
- `maintaining-engagement-state` — egress modes and the lab-only default here

