Week 11 · worksheet

Worksheet 11 — Memory-Safety & Exploitation (3 hrs)

← all weeks · readme · slides

Worksheet 11 — Memory-Safety & Exploitation (3 hrs)

Course: Software Security (KOSEN69) · Week 11 Aligned: CWE-121 (stack-based buffer overflow) · CWE-787 (out-of-bounds write) · CWE-134 (externally-controlled format string) · CWE-242 (dangerous gets) Signature game: 💥 Fuzzing Race → Pwn the Binary Ethics note: The binaries built from vuln.c are intentionally exploitable and win() spawns /bin/sh. Build and run them only inside the isolated toolbox container (or a VM) — never on your host. Never run these on a machine you care about, and never aim these techniques at software you are not authorized to test.

Part 1 — Student Information

NameStudent IDDateGroup

Part 2 — Lecture Questions

  1. Draw the x86-64 stack frame for parse_input() in vuln.c (buf[64], saved RBP, saved return address). Where does strcpy start writing and which word must the exploit overwrite?
  2. Why is gets() (CWE-242) impossible to use safely, and why did C11 remove it from the standard library?
  3. Explain CWE-134: why is printf(user_input) dangerous but printf("%s", user_input) safe? What can %n do?
  4. Name the four mitigations in the hardened build (make vuln-hardened) — stack canary, PIE/ASLR, NX/DEP, FORTIFY — and say which one each defeats: ret2win, shellcode-on-stack, return-address overwrite detection.
  5. The CISA/ONCD roadmaps push memory-safe languages. Using safe.rs, explain why CWE-121 is impossible in safe Rust, not merely harder.

Part 3 — Hands-on Lab (145 min)

Learning goals: Find the overflow with a coverage-guided fuzzer, exploit it (ret2win), watch mitigations break the exploit, then port the routine to safe Rust.

Prerequisites: the toolbox container (bundles clang+libFuzzer, gdb, objdump, python3) — Apple clang has no libFuzzer and gdb is painful on macOS, so do this week in the container. (pip install pwntools and rustc inside as needed; an isolated Linux VM also works.)

Environment setup

# from the repo root, build the toolbox once, then open a shell IN this week's folder:
docker build -t softsec-toolbox labs/toolbox
docker run -it --rm --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
    -v "$PWD/labs/week11-memory-safety-exploitation":/work -w /work softsec-toolbox
# --- inside the container ---
make vuln                                          # teaching build: mitigations OFF
make vuln-hardened                                 # mitigations ON
clang -g -fsanitize=address,fuzzer fuzz_harness.c -o fuzz   # libFuzzer + ASan
setarch `uname -m` -R ./vuln                       # disable ASLR for repeatable addresses

What to submit per task: the exact command(s) + crash/exploit output, a screenshot, and a 2–3 sentence mitigation note tied to the CWE.

Task 0 — Onboarding (15 min)

Run make syntax (parses both C files even on macOS) and confirm make vuln / make vuln-hardened build. Read the bug-class banner in vuln.c. Deliverable: build output + the list of CWEs vuln.c claims to demonstrate.

Task 1 — Fuzzing Race: find the crash (35 min)

Goal: Make libFuzzer + AddressSanitizer crash on the strcpy overflow in parse_input() — CWE-121/787. Steps:

  1. ./fuzz -runs=100000 (or just ./fuzz).
  2. Read the ASan report — note stack-buffer-overflow inside parse_input and the saved crash-<hash> file.
  3. Replay it: ./fuzz crash-<hash>.

Deliverable: the ASan crash summary + the byte length of the crashing input + mitigation note.

Task 2 — Pwn the Binary: ret2win (40 min)

Goal: Overwrite the saved return address so the binary returns into win() and prints FLAG{stack_smashing_for_fun_and_education} — CWE-121. Steps:

  1. Find win()'s address: objdump -d vuln | grep '<win>:' (or gdb -batch -ex 'p win' ./vuln).
  2. Verify the offset with a cyclic pattern (do not trust 72 blindly): feed cyclic(200), crash in gdb, cyclic_find the value in RIP.
  3. Edit exploit_skeleton.py: set WIN_ADDR to the address from step 1, confirm OFFSET.
  4. python3 exploit_skeleton.py (pwntools path) — or python3 exploit_skeleton.py | ./vuln.

Deliverable: the verified OFFSET, your WIN_ADDR, and the printed flag / [+] win() reached line.

Task 3 — Hardened rebuild: watch the exploit break (25 min)

Goal: Observe modern mitigations defeating the same payload. Steps:

  1. Run your Task 2 payload against ./vuln-hardened.
  2. Record the failure mode: *** buffer overflow detected ***: terminated (FORTIFY_SOURCE's __strcpy_chk, which on this build catches the overflow before the stack canary's epilogue check ever runs), or a SIGSEGV at a randomized address (PIE/ASLR makes WIN_ADDR wrong).

Deliverable: the crash message + a note explaining which mitigation fired and why the fixed WIN_ADDR no longer works.

Task 4 — Rust rewrite: defend / fix it (30 min)

Goal: Show the bug class disappears in safe Rust using safe.rs. Steps:

  1. rustc safe.rs && ./safe, then python3 -c "print('A'*5000)" | ./safe — note it never overflows or crashes.
  2. Feed format-string metacharacters: printf '%%x %%n' | ./safe — note the program just reports the character count with no crash and no stack leak; the input is treated as ordinary data (CWE-134 impossible: println!'s format string must be a compile-time literal, so println!(line) would not even compile).
  3. From the inline comments, write why String/&str + bounds checks make CWE-121/787 impossible.

Deliverable: the safe output for both inputs + a 3–4 sentence explanation tying it to the CISA memory-safe roadmap.

Part 4 — Reflection

  1. Mapping: table — bug | function in vuln.c | CWE | mitigation that stops it (canary / PIE / NX / Rust).
  2. Real breach: pick a memory-safety incident (Morris Worm fingerd overflow, or Heartbleed CVE-2014-0160). Which CWE applies, and would a stack canary or a Rust rewrite have prevented it?
  3. Best mitigation: compare "harden the C build" vs. "rewrite in Rust." Which gives durable risk reduction for new code, and what is the cost?

Grading rubric (100)

CriterionWeight
Lecture questions (Part 2)20
Exploitation + evidence (Tasks 1–3: fuzz crash, ret2win flag, hardened break)40
Defense (Task 4: Rust rewrite + explanation)25
Reflection (Part 4: mapping, breach, best mitigation)15
Total100

Evidence & Integrity (required)

  • Identity proof: every screenshot/diagram must show your **whoami / login email / student ID and a timestamp**. Generic or borrowed evidence is not accepted.
  • Personalized flag (if this lab issues one): ____________________

Flags are unique per student — submitting another student's flag is a violation. See SUBMISSION.md.

  • Explain in your own words (graded on your reasoning, not copied text):
  1. What did you do, and why did the vulnerability work?
  2. Why does your fix actually stop it — and what could still break it?

🤖 Audit the AI (required)

AI is a power tool you must distrust — you are graded on your critique, not the AI's answer.

  1. Ask an AI assistant to exploit or fix this week's vulnerability. Paste its full answer.
  2. Find what's wrong or risky in it — insecure code, a subtly incomplete fix, a hallucinated API/function/CVE, a missed edge case, or wrong reasoning. Quote the exact line(s).
  3. Produce the correct, verified version yourself and explain in 2–3 sentences why the AI's output was insufficient.

Disclose your AI use in the Part 1 table. This task counts toward your Defense + Reflection score.


🧠 Comprehension & Prompt (required)

A. Explain in Plain English (EiPE). In 2–3 sentences, in your own words, describe what this week's vulnerable code/endpoint actually does and why it is exploitable — explain the mechanism, don't dump jargon.

B. Prompt Problem. Write a single prompt that makes an AI produce a correct, secure fix for one finding. Run it: does the exploit now fail? If not, refine the prompt and try again. Submit the final prompt + the verified result. Graded on the prompt's precision and your verification — this trains problem decomposition and AI literacy (Denny et al. 2024).