This is the continuation of my previous post where I explained how memory is organised. In this post, we will get taste of a buffer overflow attack and look how technique popularly known as smashing the stack works.
Note: Download the vulnerable linux VM with all the modern security features (e.g. ASLR, DEP, optimized compilers) disabled and try compiling and running all the codes mentioned in this series. The exploits may not work on modern systems due to various attack mitigation techniques.
It looks good, isn’t it?
Lets look at a very simple C program.
printf("Enter your name : ");
if(modified != 0)
printf("End of Program\n");
The program accepts an input of 10 character long from the user. There is the modified variable which is initialized with ‘0’. Throughout the course of the program, the modified variable is untouched so the if condition will never satisfy. We always expect to get the output as “End of Program“. For most of the programmers, the source code looks good, but this is where the problem starts. This program is the classic case of buffer overflow attack where the if condition can be bypassed and we can print “Unreachable location“. It may sound weird, but in this post we will look into how this is possible and understand the underlying concepts.
Looking under the hood
Lets load the program into the GDB and analyze it. Refer to my blog for various gdb commands.
From the source code, we can figure out the if condition in the assembly language. Now lets put the break point at this location using break *0x080484b7 This will help in analyzing the memory(stack) content after the scanf instruction is executed. We can locate where our provided input is placed on the stack. This will also help us to identify the location of ‘modified‘ variable on the stack.
Next, lets run the program and provide the 10 character input as expected by the program. From the cmpl instruction, we come to know the location where ‘modified‘ variable is placed on the stack. It is present at the offset ‘0x1c‘ or 28 bytes away from the top of the stack (esp). To confirm this, lets print the content of this memory address. It must be ‘0’ because the ‘modified‘ variable was initialized to ‘0’. Use the command x/wx $esp+0x1c to see the memory content of that location. The result is as expected
After providing 10 ‘A‘ (AAAAAAAAAA) as an input, lets examine the stack.
10 ‘A‘ are clearly visible in hex format. After our buffer (‘name‘ variable), we can see the our ‘modified‘ variable is stored. It is because character array size was 10 bytes and we have consumed all of it with our input.
Simple Buffer Overflow
Since the character array size is 10 bytes, lets try providing the input of size more than the size of buffer, say 11 bytes. Lets rerun the program and this time we will provide the input as 11 ‘A‘ and see the stack content.
Now we can see that we have overflowed the buffer and the ‘modified‘ variable is over written and it is no longer ‘0’. This will alter the if condition and will enter into the unreachable section of the code. We have successfully reached the unreachable piece of code and printed “Unreachable Location” !!
Note: The memory layout used here is Little Endian, which means the memory is filled from lower bit to higher bit (right to left). It is very important to know the endiannes of the machine while writing the payload for buffer overflow vulnerability. Download the check-endian.c from my github repository, compile it and run to check for endiannes of the machine.
What went wrong?
The problem with the above code was the way in which scanf function was used. The code
scanf("%s", name); allowed the user to input unrestricted amount of character and there was no bound checking. This led to the buffer overflow vulnerability, where the user was able to input more that the capacity of the buffer (10 bytes) and wrote on the part of the memory (modified variable) where it not authorized to. As a secure coding practice is very important to use the bound checks, in this case
scanf("%10s", name); which will accept only first 10 bytes from the user input and ignore rest. This will prevent the corruption of the memory.
Below are the few unsafe C functions which are vulnerable to buffer overflow attacks and should be avoided. Even if these functions are used, a proper bound check i.e validation of the length of user input must be done.
This was the a very simple buffer overflow attack where the memory was corrupted by providing the input of size more than that of the buffer. I hope this article was informative. Share this if you found it useful. Subscribe to the mailing list (on the right sidebar) to get updates of my latest post. Feel free to leave your comments and feedback.
Happy Learning 🙂
The author is a security enthusiast with interest in web application security, cloud-native application development and Kubernetes.