Stack Buffer Overflows in 2024: ASLR, DEP/NX, Canaries, and Modern Mitigations
Cybersecurity
Where stack buffer overflows stand in 2024, the layered mitigations that make them harder, and the bypass techniques that still apply with pwntools and gdb-peda.
By Arjun Raghavan, Security & Systems Lead, BIPI · December 25, 2024 · 11 min read
Stack buffer overflows are not dead. They are layered behind ASLR, DEP, stack canaries, FORTIFY_SOURCE, CFG, shadow stacks, and PIE, but every year ships another CVE that pops despite all of it. Understanding the layers is the prerequisite to chaining bypasses.
The Classic Crash
char buf[64]; gets(buf); is the textbook case. Send 72 bytes, smash the saved RIP, jump to your shellcode on the stack. That has not worked on default Linux configs in 20 years, but vulnerable code that reaches the same primitive still ships, especially in network daemons, embedded firmware, and OT control protocols.
Mitigations in Layers
- Stack canaries (-fstack-protector): random value before saved RIP, must match on function return
- DEP/NX: stack pages marked non-executable, shellcode-on-stack does not run
- ASLR: code, heap, stack, and libraries randomized per process, addresses change every run
- PIE (-fPIE -pie): the binary itself is position-independent, full address space randomization
- RELRO (full): GOT marked read-only after init, GOT overwrite primitives die
- FORTIFY_SOURCE: compile-time bounds checks on memcpy, strcpy, sprintf, etc.
- Intel CET / shadow stack: hardware shadow stack on Tiger Lake+ blocks ROP returns
- Microsoft CFG and XFG: indirect call target validation on Windows
Defeating Canaries
Canaries are 8 bytes on x86_64 with a NULL byte in the LSB. Byte-by-byte leak via a printf format string or via fork-child differential timing (a child that crashes versus returns reveals one byte at a time, since forked children share the parent canary). Once leaked, splice into your overflow payload.
Defeating ASLR
Need an info leak. Format strings, OOB reads, uninitialized stack reads, and partial overwrites (offset within page) all work. In remote services, leak a libc address from a printed buffer, calculate base via known offset, then build ROP. Tools like one_gadget find magic libc offsets that drop a shell with minimal stack alignment.
- pwntools ELF and libc objects compute offsets automatically, e.set_address(leak) gives instant base
- one_gadget /lib/x86_64-linux-gnu/libc.so.6 lists single-jump RCE candidates
- If RELRO is partial, GOT overwrite of __free_hook or __malloc_hook still works on glibc 2.33 and earlier
- glibc 2.34+ removed hooks, attackers shifted to FILE struct exploitation (House of Apple)
Real 2024 CVEs
- CVE-2024-6387 regreSSHion, sshd stack pre-auth overflow allowing pre-auth RCE on glibc systems
- CVE-2024-23897 Jenkins arbitrary file read via args4j stack mishandling
- Multiple OT and embedded CVEs in Siemens, Schneider, and Phoenix Contact controllers
regreSSHion proved that with enough patience, a stack overflow plus a heap grooming chain still pops the most-hardened daemon on the most-hardened distro.
Workflow with pwntools
- Triage with checksec to enumerate mitigations: canary, NX, PIE, RELRO
- Reverse with Ghidra or IDA to identify the vulnerable function and offset to saved RIP
- Build a cyclic pattern with cyclic(200), crash, read RSP, find offset via cyclic_find
- Get an info leak primitive, calculate ASLR base, locate gadgets with ROPgadget or pwntools ROP
- Construct ROP chain to either system('/bin/sh') or mprotect+shellcode if NX flips
- Test locally, then remote with p = remote(host, port)
Shadow Stack and CET
On Intel CET-enabled hardware with kernel support, every CALL pushes RIP to both the regular and shadow stack. RET pops from both and compares. ROP chains that change return addresses fail at the first ret. Bypass requires either disabling CET via prctl (if process can), or pivot to JOP/COP using indirect jumps and calls, which CET enforces via ENDBR landing pads. ENDBR can be bypassed when an indirect call lands on an attacker-influenced register.
Defenses for Developers
- Always compile with -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIE -pie -Wl,-z,relro,-z,now
- Replace gets, strcpy, sprintf, scanf with bounded variants or std::string
- Run with shadow stack enabled on CET-capable CPUs, set glibc tunables for hardening
- Audit with AFL++, libFuzzer, and a coverage map before shipping any C/C++ network code
Stack overflows persist because legacy code persists. Knowing how every mitigation works and where it fails is the difference between a crash and a shell.
Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.