Safeguard
AppSec

Buffer Overflow Exploits: A Practical Example

A buffer overflow exploit example, walked through step by step, showing exactly how writing past the end of a fixed-size buffer can turn a simple C function into arbitrary code execution.

Safeguard Research Team
Research
7 min read

A buffer overflow exploit works by writing more data into a fixed-size memory buffer than it was allocated to hold, letting the excess data overwrite adjacent memory — including, in the classic case, a function's return address — so the program jumps to and executes code the attacker chose instead of continuing normally. It's one of the oldest vulnerability classes in software security and still one of the most consequential, because it converts a simple, easy-to-write mistake — copying data without checking its length — into full control over a program's execution. This post walks through a concrete, practical example of how a stack-based buffer overflow works, from the vulnerable code to the resulting exploitation.

What does a vulnerable buffer overflow function look like?

A classic vulnerable function looks almost boringly simple: it copies attacker-controlled input into a fixed-size local buffer using a function that doesn't check the input's length against the buffer's size. Consider a C function that declares char buffer[64]; and then calls strcpy(buffer, user_input); where user_input comes from a network request or file the attacker controls. strcpy() keeps copying bytes until it hits a null terminator in the source string — it has no idea buffer is only 64 bytes, and it will happily write the 65th, 200th, or 1000th byte straight past the end of that allocation if the input is that long. On the stack, local variables sit adjacent to bookkeeping data the CPU needs to resume execution correctly after the function returns — most importantly, the saved return address that tells the processor where to jump back to. If user_input is long enough, the overflow doesn't just corrupt other local variables; it overwrites that saved return address with attacker-supplied bytes.

How does overwriting the return address lead to code execution?

Overwriting the return address leads to code execution because, when the vulnerable function finishes and executes its ret instruction, the CPU doesn't jump back to the legitimate caller — it jumps to whatever address the attacker wrote there instead. In the simplest classic version of this attack, the attacker crafts their input so that the overflow contains: padding bytes to fill up to the return address, a new return address that points back into the buffer itself (or somewhere else the attacker's bytes ended up in memory), and actual machine code — traditionally called "shellcode" — placed where that new return address points. When the function returns, the processor loads that attacker-chosen address into the instruction pointer and starts executing the attacker's shellcode with all the privileges the vulnerable process already had. Classic shellcode often spawned a shell (hence the name), giving the attacker interactive command execution on the compromised host, though modern payloads can do anything the process's privilege level allows — read files, open network connections, or serve as a foothold for further exploitation.

Why don't buffer overflows work this simply against modern software anymore?

Buffer overflows are harder to exploit today because modern compilers and operating systems layer several defenses on top of the raw vulnerability, though none of them make the underlying bug itself go away. Stack canaries place a known, randomized value between local buffers and the saved return address, and check it hasn't changed before the function returns — a naive overflow that overwrites the return address linearly will also corrupt the canary and get caught. Address Space Layout Randomization (ASLR) randomizes where code, the stack, and libraries are loaded in memory each run, making it much harder for an attacker to predict a usable address to jump to. Non-executable stack/heap protections (often called DEP or NX) mark memory regions holding data as non-executable, so even if an attacker gets shellcode into memory, the processor refuses to run it directly from the stack, forcing more complex bypass techniques like return-oriented programming (ROP), which chains together small existing code fragments already present in the binary instead of injecting new code. These mitigations raise the bar significantly — turning a straightforward overflow into a mitigation-bypass research problem — but they don't eliminate the vulnerability class, which is why buffer overflows still appear in modern CVEs, particularly in C/C++ code, embedded systems, and native libraries that parse untrusted input like image, font, or network protocol data.

How do you prevent buffer overflow vulnerabilities?

You prevent buffer overflow vulnerabilities primarily by never trusting input length and using bounds-checked operations, or by choosing a language that makes the bug structurally impossible. In C and C++, replace unbounded functions like strcpy(), sprintf(), and gets() with their length-limited equivalents (strncpy(), snprintf()) and always validate that a copy operation's length can't exceed the destination buffer's actual size — and compile with stack-protector flags and ASLR/DEP enabled as a baseline, not an afterthought. At the language level, memory-safe languages (Rust, Go, Java, C#, Python) eliminate this entire vulnerability class by design, since they perform automatic bounds checking on array and buffer access rather than relying on the developer to get it right every time — which is why security-conscious rewrites of historically C-heavy infrastructure (parts of the Linux kernel, browser components) have increasingly moved toward Rust specifically for this reason. Where legacy C/C++ code can't be rewritten, static analysis tools that specifically model buffer sizes and data flow can catch unsafe copy patterns before they ship, and fuzzing (feeding a program large volumes of malformed input automatically) remains one of the most effective ways to discover buffer overflows in real-world parsers and protocol handlers that manual code review tends to miss.

FAQ

What's the difference between a stack overflow and a heap overflow?

A stack-based buffer overflow (the classic example) overwrites data on the call stack, potentially including the saved return address. A heap-based buffer overflow corrupts memory management structures or adjacent allocated objects on the heap instead, often leading to different exploitation techniques like corrupting function pointers stored in heap objects.

Are buffer overflows still a real risk in 2026, or is this a solved problem?

They're still a real risk, particularly in C/C++ codebases, embedded and IoT firmware, and native libraries that parse untrusted input. Modern mitigations (ASLR, stack canaries, DEP) make exploitation harder, not impossible, and new buffer overflow CVEs are still disclosed regularly.

Do memory-safe languages like Rust or Java eliminate buffer overflows entirely?

They eliminate the classic form by performing automatic bounds checking on memory access, which is a major reason security-focused projects have migrated performance-critical, attacker-facing code to Rust. Bugs can still occur in unsafe blocks or in native code these languages call into.

What's the simplest fix for a strcpy()-style vulnerability?

Replace it with a length-bounded equivalent (strncpy(), snprintf(), or a safe string library) and explicitly validate that the source data can't exceed the destination buffer's size before the copy happens.

How Safeguard Helps

Safeguard's SAST engine models data flow through unbounded copy operations in C, C++, and other memory-unsafe languages, flagging buffer overflow patterns — unchecked strcpy()/memcpy()-style calls fed by attacker-influenced input — before they reach production. For compiled and native components already in your supply chain, SCA tracks known buffer overflow CVEs in the libraries you depend on, so a vulnerable native dependency doesn't sit unnoticed just because the vulnerable code isn't yours.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.