Memory-Safety & Exploitation
Software Security · Nutthakorn Chalaemwongwan
Today
- The C/C++ memory model & the stack
- Finding bugs with fuzzing
- Stack overflow → control hijack
- Mitigations + the memory-safe language shift
- 🎮 Game: Fuzzing Race → Pwn the Binary
Why this still matters
- C/C++ runs the world's critical infrastructure
- Memory bugs = ~70% of severe CVEs historically
- Now a national-policy issue (CISA/ONCD roadmaps)
The stack frame
gets/strcpy/uncheckedmemcpy→ overflow- Overwrite return address → redirect execution
Bug classes
- CWE-121 stack overflow · CWE-787 OOB write
- CWE-134 format string · CWE-242 dangerous function (
gets()) - (this week's binary: no UAF, no off-by-one — those are real bug classes but not in this lab's code)
Fuzzing — how bugs are found today
clang -g -fsanitize=address,fuzzer fuzz_harness.c -o fuzz && ./fuzz # libFuzzer
afl-fuzz -i seeds -o out -- ./vuln @@ # AFL++
- Coverage-guided mutation finds crashes fast
- Pair with sanitizers (ASan) for root cause
Exploiting a stack overflow
- Find offset to return address with a cyclic pattern — verify it, don't assume it: this build's offset is 72 bytes, but enabling the stack canary alone moves it to 80 (the canary word sits between buffer and return address)
- Overwrite RA → jump to
win() - Format string:
%x%x%xleak,%nwrite
Mitigations raise the bar
- FORTIFY_SOURCE — checks the copy itself, at call time (
__strcpy_chk) - Stack canaries — detect overwrite at function return, after the copy already happened
- ASLR / PIE — randomize addresses
- NX/DEP — blocks executing shellcode planted on the stack — irrelevant to this exploit, which returns into existing code (
win()), never injects shellcode - On this lab's hardened build, FORTIFY fires first —
*** buffer overflow detected ***: terminated. The canary never gets a chance to speak. Verify the order yourself before teaching it as "canary catches it" — that's the wrong answer this lab's own materials were rewritten to correct.
From strcpy to shell — where each mitigation cuts the chain
The real fix: memory-safe languages
- Rust / Go remove whole bug classes by design
- CISA "Secure by Design" + ONCD: move off C/C++ for new code
- Borrow checker / bounds checks = no overflow, no UAF
💥 Game — Fuzzing Race → Pwn the Binary
- Round 1 (Fuzzing Race): first team to crash the target wins
- Round 2 (Pwn): exploit the overflow / format string
- Round 3 (Defend): rebuild with canary+ASLR+PIE, then rewrite in Rust
Deliverable
📋 Worksheet 11 —
labs/week11-memory-safety-exploitation/worksheet.md(Part 3) · kickoff: in the toolbox container (labs/toolbox— Apple clang has no libFuzzer runtime):clang -g -fsanitize=address,fuzzer fuzz_harness.c -o fuzz && ./fuzz
- Fuzzing crash + exploit script
- Annotated Ghidra/gdb analysis
- Memory-safe (Rust) rewrite + why the bug is now impossible
- + Audit the AI / EiPE / Prompt Problem (see worksheet)
Key takeaways
- Fuzz to find, debug to understand, mitigate to slow attackers
- Mitigations ≠ cure — memory-safe languages are the cure
- This is where the industry is moving
Questions?
Next week: Software supply-chain security