All posts
Ethical Hacking

Buffer Overflows in 2025: Stack Smashing, ASLR, and Bypassing Modern Defenses

From classic stack smashing to stack canaries, NX, ASLR, and the return-oriented programming techniques that bypass them — a defender's mental model.

SKSushan Khadka
March 1, 2026 (6mo ago)5 min read
Buffer Overflows in 2025: Stack Smashing, ASLR, and Bypassing Modern Defenses — article by Sushan Khadka (namelessnerd)

The buffer overflow is forty years old and still shipping in production C. Understanding why it persists — and why each defense only raised the bar instead of closing the door — is the difference between patching symptoms and writing safe code.

Buffer Overflows in 2025: Stack Smashing, ASLR, and Bypassing Modern Defenses - diagram by Sushan Khadka (namelessnerd)

The classic bug

A function reserves a fixed buffer on the stack. Right above it sits the saved return address — the pointer the CPU jumps to when the function ends. When code copies input without checking its length, an oversized write spills past the buffer and overwrites that return address. Now the attacker controls where execution goes next. No magic, just an unbounded copy writing into memory that was never meant to hold user data.

void greet(char *name) {
    char buf[64];
    strcpy(buf, name);   // no bounds check — 65+ bytes overflow buf
    printf("Hello, %s\n", buf);
}

strcpy copies until it hits a null byte, with no idea how big buf is. Hand it 64 bytes of filler plus a new 8-byte address and the return pointer becomes whatever you chose. The function returns — straight into attacker-controlled territory. This is stack smashing, and strcpy, gets, sprintf, and unchecked memcpy have all been the delivery vehicle.

Why C lets this happen

The root cause is a language design decision, not a mistake in any one program. C arrays are just pointers with no length attached; the language performs no bounds checking because that check would cost cycles, and C's founding bargain was maximum performance and control. That bargain was right for 1972 and is a liability in 2025 — it means the programmer is solely responsible for never writing past the end of a buffer, across millions of lines, forever. Humans are not good at "never," which is why the bug class refuses to die.

The mitigation ladder

Each defense answered the previous attack, and each was answered in turn. It is genuinely a decades-long arms race:

How attackers adapt

NX killed code injection, so attackers stopped injecting code. Return-oriented programming (ROP) stitches together short snippets of existing executable code — "gadgets," each ending in a ret — that already live in the binary and its libraries. By overwriting the stack with a sequence of gadget addresses, the attacker builds an arbitrary payload out of the program's own instructions, never introducing a single new byte of code. NX sees only legitimate, executable, already-present instructions and waves it through.

That leaves ASLR as the real obstacle, because ROP needs to know the addresses of its gadgets. This is why modern exploitation is really a hunt for an information leak — a format-string bug, an uninitialized read, an out-of-bounds read — that reveals one real runtime address. From that single leak the attacker recomputes the whole library's layout (everything shifts by the same offset) and the ROP chain snaps back into place. The lesson worth internalizing: defenses compose, but one leak can unravel several at once. A read primitive is not "low severity" when it re-enables a write primitive.

The real fix

Mitigations are speed bumps; they assume the bug exists and try to limit the damage. Useful, but every one of them has a documented bypass, because they treat the symptom. The durable answer is to not have the bug — to make the entire class impossible rather than survivable.

gcc -fstack-protector-strong -D_FORTIFY_SOURCE=2 -pie -fPIE app.c

-fstack-protector-strong inserts canaries, _FORTIFY_SOURCE=2 swaps risky libc calls for bounds-checked variants where the size is known, and -pie -fPIE makes the main executable itself position-independent so ASLR can randomize it too.

None of this makes C safe. It makes it less unsafe — and buys time until the code can move to a language where the whole category simply doesn't exist.

The takeaway

Read more posts