You follow a buffer overflow walkthrough step by step. Same vulnerable C code with a fixed char buffer[64] and a strcpy() that never checks length, same objdump command to find the offset, same shellcode. You overflow the buffer, you point the return address exactly where the tutorial says to point it, and instead of a shell you get a segfault, or a "stack smashing detected" message and a killed process. You didn't type anything wrong. The tutorial predates protections your compiler now turns on by default. The exploit was never broken. The target changed under it.
That confusion is worth sitting with instead of copy-pasting a workaround, because understanding why it broke is most of what modern binary exploitation actually is.
Here's what a stack buffer overflow is actually doing underneath the shellcode. When a function is called, the stack holds its local variables, the saved base pointer of the calling function, and the return address the CPU jumps back to once the function finishes. char buffer[64] sits below those in memory. A copy function with no bounds checking writes past the end of that 64-byte space and keeps going, straight through the saved base pointer and into the return address itself. Overflow it with the right amount of padding and the exact bytes of an address you control, and when the function returns, the CPU doesn't go back to the caller. It jumps wherever you told it to.
That's the entire idea a lot of older tutorials teach, and it's still correct. What's different is what stands between you and it on a normal modern binary:
A stack canary is a random value placed right before the saved return address at function entry, checked against its original value right before the function returns. Overflow through it and the check fails before your fake return address is ever used, that's the "stack smashing detected" message.
NX (the no-execute bit, also called DEP) marks the stack as non-executable, so even if you land your own shellcode on it, the CPU refuses to run instructions living there.
ASLR randomizes where the stack, heap, and shared libraries load on every run, so a hardcoded address from an older tutorial almost never points at anything real on your machine.
PIE (position-independent executables) applies that same randomization to the binary's own code, closing off jumping to a fixed address inside the program itself.
None of these individually make exploitation impossible. They make the naive version of the exploit specifically stop working, which is exactly the version most beginner tutorials teach because it's the clearest way to show the underlying bug. The honest way to learn this is to compile your own vulnerable program with these protections explicitly disabled first (-fno-stack-protector -z execstack -no-pie on gcc), get the classic exploit working exactly as the older material describes, then turn each protection back on one at a time and watch specifically what it breaks. That's the difference between memorizing a payload and understanding what a mitigation actually mitigates.
Once you're past that stage, the real work starts: bypassing canaries through information leaks instead of brute force, chaining ROP gadgets to execute code without ever writing to the stack, and moving from stack overflows into heap exploitation, where the bugs are different in kind, not just harder versions of the same one.
That progression, from "why did my segfault happen" to actually building a working exploit against a hardened modern binary, is what Codelivly's Exploit Development Book PDF: Memory Corruption Guide walks through end to end, mitigation by mitigation, instead of leaving you to figure out why the old tutorial stopped working on your own.
Top comments (0)