Under the Hood
How Buffer Overflow Corrupts Memory?
When we talk about "overflowing a buffer," we're really talking about writing data into adjacent memory (memory that the programmer didn’t intend for us to touch.)
In C, local variables (like buffer
and flag
) are stored on the stack, and they’re placed one after another in memory. If your code doesn't check the size of input (like when using gets()
), extra data spills beyond buffer
and can overwrite whatever comes next in this case, the flag
variable.
This is exactly why understanding how the stack is laid out during program execution is so important. Once you see the memory structure, the magic of buffer overflows starts making sense.

Here's a simplified view:
Higher Memory Addresses
+----------------------+
| Return Address | ← where the function should return after finishing
|----------------------|
| Saved Frame Pointer |
|----------------------|
| Local Variables | ← this is where your `buffer` and `flag` live
+----------------------+
Lower Memory Addresses
Understanding the Stack Frame Layout
When a function is called in C (like main()
), the system creates a stack frame a dedicated section of memory used to manage that function’s execution. This frame includes:
Return Address : This is the address of the instruction the CPU should return to after the function finishes.
Saved Frame Pointer : This helps the program restore the previous function’s stack state after finishing.It’s part of how the stack keeps track of "where it was" before the current function call.
Local Variables : These are the variables you define inside your function like
char buffer[8];
andint flag = 0;
How Program Uses Memory :
When main()
starts running, it creates a small space on the stack for local variables like buffer
and flag
. Here’s a visual:
Higher Memory Addresses
+----------------------+ ← Return Address
| Return Address |
+----------------------+
| Saved Frame Pointer |
+----------------------+
| flag (4 bytes) | ← This should be 0 initially
+----------------------+
| buffer[8] (8 bytes)| ← User input goes here
+----------------------+
Lower Memory Addresses
🐞 Dissecting the Overflow with GDB
Now that we understand how the stack is laid out and how a buffer overflow can spill into adjacent memory, let’s observe it live using a debugger. We’ll use GDB (GNU Debugger) to watch how the memory changes before and after the overflow.


What Does disas main
Mean in GDB?
disas main
Mean in GDB?When you run your program inside GDB (the GNU Debugger), you can use the command:
disas main
This stands for “disassemble main” and it tells GDB to show you the low-level machine instructions that your computer actually runs when it executes your main()
function.
Think of it like this:
You're taking off the high-level "C programming glasses" and looking at what your program really becomes underneath raw CPU instructions.
Understanding the machine instructions
1. Stack Frame Setup (Standard Boilerplate)
0x401146 <+0>: push rbp
0x401147 <+1>: mov rbp, rsp
0x40114a <+4>: sub rsp, 0x10
What’s happening here?
This is just standard function setup. It creates a new stack frame by saving the previous base pointer and allocating 16 bytes of space for local variables.
That 16 bytes? That’s where our buffer[8]
and flag
live. 👀
It’s like opening a new notebook page to do a small task. You mark the old page to come back to later (
push rbp
), then leave space to write notes for this task (sub rsp, 0x10
).
Flag Initialization
0x40114e <+8>: mov DWORD PTR [rbp-0x4], 0x0
This line sets the variable flag = 0.
And now we can see exactly where in memory that flag is stored: [rbp-0x4] — just 4 bytes below the base pointer.
Lets set breakpoint at main and run the program and check the address of buffer and flag

The base address of the buffer
is 0x7fffffffdc94
, and the address of the flag
variable is 0x7fffffffdc9c
. The difference between them is exactly 8 bytes, which matches the size of our buffer
. This confirms that flag
is stored immediately after the buffer in memory meaning any input longer than 8 bytes will start to overwrite the flag
variable.
This is the crux of how the overflow works. Since the gets()
function does not perform bounds checking, it will continue writing whatever input it receives into memory past the end of the buffer and into any adjacent variables. In this case, that adjacent variable is our flag
, which was initialized to 0. When we provide 8 characters to fill the buffer and 4 more bytes representing the integer value 1
, those extra 4 bytes spill into the memory location reserved for flag
.
This seemingly innocent mistake changes the logic of the program. The check if(flag == 1)
is now true, not because we assigned 1
to flag
, but because we overwrote it via a buffer overflow. The program behaves differently simply due to the layout of memory and lack of input validation.
Understanding this basic overflow lays the groundwork for more complex exploitation techniques, such as overwriting return addresses, injecting shellcode, or manipulating function pointers. But even in this simple example, we see how dangerous and powerful buffer overflows can be when proper safeguards are not in place.
Last updated