Let’s start by running the code below to see what it does.
Notice in the code above that “arbitrary_code” function is not being called anywhere.
Let’s compile this code as shown below:
gcc -ggdb -m32 -mpreferred-stack-boundary=2 -fno-stack-protector bf_vuln.c -o bf_vuln.exe
Figure 1: code being compiled.
Note while compiling we are already warned that the “gets” function is dangerous and should not be used.
Now that the code is compiled let’s execute it.
Figure 2: First run with valid input
Above we see our code executed with PID 4298. We also provided the word “Running” as input and this was printed back to the screen.
Now if we were to run this one more time and put more than 8 bytes, we see we get a “Segmentation Fault”. Basically we caused the program to crash.
Figure 3: Input greater than buffer size
Now what did we do to cause the crash?
Before we move on, let’s step back and understand the stack and for our purposes I will keep it extremely, extremely, extremely simple. There are lots of good documentation out there on the stack but the obvious ones for Intel Architecture would be the Intel® 64 and IA-32 Architectures Software Developer’s Manual.
Figure 4: Stack Structure: Source: Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 1: Basic Architecture
- The stack grows down in memory (towards lesser addresses) when items are pushed on the stack and shrinks up (towards greater addresses) when the items are popped from the stack
- The operating system is responsible for creating stack
- Only one stack - the current stack - is available at a time
- The current stack is the one contained in the segment referenced by the Stack Segment (SS) register
- The stack is typically divided into frames.
- Each stack frame can then contain local variables, parameters to be passed to another procedure, and procedure linking information.
- The stack-frame base pointer (contained in the EBP register) identifies a fixed reference point within the stack frame for the called procedure.
- To use the stack-frame base pointer, the called procedure typically copies the
contents of the ESP register into the EBP register prior to pushing any local variables on the stack.
- Prior to branching to the first instruction of the called procedure, the CALL instruction pushes the address in the EIP register onto the current stack.
- This address is then called the return-instruction pointer and it points to the
instruction where execution of the calling procedure should resume following a return from the called procedure
Ok that’s enough about the for what we would like to do. Now let’s move on.
Now as stated above, the objective in most buffer overflow is to run arbitrary code and in order to do this we need to control the EIP (Instruction Pointer). Additionally, as stated above, the EIP is pushed unto the stack into the return instruction pointer. So if we can overwrite the return pointer with our code, we may be able to perform some arbitrary action.
Let’s revisit our program once again, this time to see where in memory our stack is. This step is not absolutely needed for what we want to do. However, it helps to understand things a bit more.
Figure 5: Memory layout of the stack.
Once we executed our code we saw we got PID “4467”. Looking at the memory “maps” of PID 4467 on the “proc” file system, we see that the stack falls between memory address “fffdd000” and “ffffe000”. Remember we stated above that the stack grows from high memory to low memory. This would mean that the “top” of our stack is “ffffe000” while the bottom would be “fffdd000”.
Let’s move on to loading this program up in the GNU Debugger (GDB) by executing “gdb ./bf_vuln.exe -q”.
Next let’s look at the code using “list” so as to set a breakpoint on line 28.
Figure 6: Code listing and breakpoint being set.
You may have noticed that the listing above, does not show our arbitrary code. We will use that code later though.
Having set the breakpoint, let’s now “run” the program and examine our EBP and ESP registers along with our stack from these perspectives.
First up EBP
Figure 7: GDB output showing EBP and Return Pointer.
If we consider our stack structure diagram from above in figure 4, we know that before the EBP we have the Return Pointer. This is demonstrated above as in EBP has the value of “0xffffcfb8” while the return pointer has the value of “0x08048511”. We can also validate these another way for EBP we can say “print $ebp” and this would show the same “0xffffcfb8”. For the return pointer, we can disassemble the memory address “0x08048511” and we get the following.
Figure 8: Return pointer being validated.
The above image shows that memory address “0x08048511” maps to the next command to be executed after “buffer_overflow” has been called and completed. In this case we are moving “0” into eax which represents what is shown in our code above where we have “return 0”.
At the end of it all, what we really want to do is control the return pointer as to determine what the code does when the value at this address is executed. Our objective then will have to be to overwrite this address.
Let move on and look at things now from the perspective of ESP (stack pointer).
Figure 9: Stack from ESP perspective
From above the offsets to the left confirms that our stack is growing from high memory to low memory. Also if we look at those offsets, we will see that they fall within the memory range showed above in figure 5. Also note that figure 9 above also shows our 8 byte buffer. Do note we are looking at more entries on the stack. In the figure 8 we are examining 4 hex words (x/4xw) from the EBP perspective. In figure 9, we are examining 8 hex words (x/8xw) from the ESP perspective.
At this point what we want to do is fill up our 8 byte buffer with so much information that it overwrites the return pointer and crash. Looking at the image above, we see 8 bytes buffer, 4 bytes EBP and 4 bytes return pointer. So this should mean if we use 16 As (Hex 41), we should be able to overwrite the return pointer with 4As. Let’s test that theory by going back to GDB.
First let’s Step “s” through the program so that it asks for our input.
Figure 10: Buffer filled up and return pointer overwritten with As (Hex 41)
As shown in image 10, we were able to overwrite the return pointer with 0x41414141.
Once we continue with this program it would crash as the memory address would be invalid. However, we’ve manage to learn how much bytes it takes for us to overwrite the return pointer. This information we will use to execute our arbitrary code. Before we exit GDB let’s see where “arbitrary_code” is found in memory. To determine this let’s disassemble this function using “disassemble arbitrary_code”.
Figure 11: arbitrary_code functions starts at memory address “0x080484ad”
Let’s try to use this memory address along with everything else we learned before to our advantage. For us to execute our arbitrary code let use “echo”.
The following one liner will allow us to take advantage of the buffer overflow.
echo -e "AAAAAAAAAAAA\xad\x84\x04\x08" | ./bf_vuln.exe.
What the above line does is fill in our 8 bytes buffer with 8 As then fill in our 4byte EBP with 4 As, then we provide the 4 byte memory address of our “arbitrary_code” to overwrite the return pointer. You may notice the order in which I’ve written the hex value of “arbitrary_code” is the reverse of the way the disassemble presented it. This is because Intel architecture stores data in memory in little endian format.
Figure 12: “arbitrary_code” executed and results shown from our code above.
So we’ve exploited the buffer overflow to run “arbitrary_code”, now how do we prevent this.
Defenses against Buffer Overflow:
First up you should avoid using vulnerable functions and always check your expected input verses what was actually provided by your users. However, there are many different protections mechanisms available. These are typically used in conjunction rather than in isolation.
Data Execution Prevention (DEP):
”DEP is a Windows feature that enables the system to mark one or more pages of memory as non-executable. Marking memory regions as non-executable means that code cannot be run from that region of memory, which makes it harder for exploits involving buffer overruns to succeed.”
A canary is a value placed on the stack before the saved return address and is an is an effective deterrent against arbitrary code execution.
There are 4 types of canaries
– Random Canary - A 32-bit pseudorandom value generated by
the /dev/random or /dev/urandom devices on a Linux operating system.
– Random XOR Canary - provides slightly more protection by performing an
XOR operation on the random canary value with the stored control data.
– Null Canary - The canary value is set to 0x00000000, which is chosen based
upon the fact that most string functions terminate on a null value and should not be able to overwrite the return address.
– Terminator Canary - The canary value is set to a combination of Null, CR, LF and 0xFF. These values act as string terminators in most string functions and account
for functions that do not simply terminate on nulls
such as gets().
Address Space Layout Randomization (ASLR):
ASLR randomizes the memory locations used by system files and other programs, making it much harder for an attacker to correctly guess the location of a given process. Consider our example above where we knew the exact location of the return pointer on the stack. It is quite possible that if we were using ASLR, this location would not have been so easily predictable.
Control Flow Guard (CFG) is a highly-optimized platform security feature was created to combat memory corruption vulnerabilities. It places tight restrictions on where an application can execute code from, thus making it much harder for exploits to execute arbitrary code. CFG extends other mitigations techniques such as /GS, DEP, and ASLR.
As stated above, these defenses are typically used together rather than in isolation. For example on Windows 10, the following can be seen for the “winogon.exe” process.
That’s all folks. Probably one of my longer posts but I believe it was worth it. If you are reading this and think I have miss or miss-represented something, please let me know so that I can fix it.
Gustavo’s Journey to the stack, part 1
SecurityTube Buffer Overflow Megaprimer
Wikipedia Buffer Overflow
Wikipedia Stack Buffer Overflow
COEN 152 Computer Forensics Buffer Overflow Attack
How Security Flaws Work: The buffer overflow
Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade
Buffer Overflows: Secure Programming
Buffer Overflows: Vulnerabilities and attacks
Smashing the Stack for Fun and Profit
SANS Reading Room: Buffer Overflows for Dummies
MIT Open Courseware: Buffer Overflow Exploits and Defenses
Tenouk: A Stack-based buffer overflow
Exploit DB: 64 Bits Linux Stack Based Buffer Overflow
Happy Coding: Print Process ID, etc
Intel® 64 and IA-32 Architectures Software Developer’s Manual
iDefense: A Comparison of Buffer Overflow Prevention Implementations and Weaknesses
Protecting Your Software: Exploit Mitigations in Windows
Stack Smashing as of Today: A State-of-the-Art Overview on Buffer Overflow Protections on Linux_x86_64
Exploit DB: Smashing the stack on modern Linux systems
Control Flow Guard
Exploring Control Flow Guard in Windows 10