Exploiting a buffer overflow vulnerability is very creative and a bit difficult to understand as it takes many different parts of computer technology knowledge to understand and pull off an attack. But after mastering, it's such a powerfull skill, as there are still programs with that kind of vulnerability. In addtion it lets you better understand how computers and programs work.
Let's explore this topic together!
We're gonna start by briefly examining the memory layout of a C program, especially the stack since this is a place where most of the buffer overflows occurs.
Kernel - Section storing command line parameters that we pass to the program and environment variables.
Stack - Stack holds local variables, function parameters and return addresses for each of your functions. We will come back to it in more detail just in a minute.
Heap - Dynamically allocated memory for large chunks of data. The heap grows upwards in memory (from lower to higher memory addresses) as more and more memory is required.
Data - Place for initialized and unitialized variables.
Text - This is the section where the code of the executable stored. It is often read only cause you don't want to be messing around with that ;)
%ESP - Extended Stack Pointer register which purpose is to let you know where on the stack you are. As the stack grows downward in memory (from higher address values to lower address values) the %ESP register points to the lowest memory address.
%EBP - Extended Base Stack Pointer pointing to the base address of stack. Local variables are accessed by subtracting offsets from %EBP and function parameters are accessed by adding offsets to it.
%EIP - Extended Instruction Pointer contains the address of the next instruction to read on the program. It points always to the Text segment.
Here you can take a look how the stack with few parameters can look like, together with the registers.
Now let's look at this simple code.
Assume that our %EIP is pointing to the func call in main. Now the following steps would be taken:
- Firstly a function call is found, push parameters on the stack from right to left (param_2 first, then param_1)
- Now we need to know where to return after func is completed, so push the address of the next instruction on the stack.
- Find the address of func and set %EIP to that value. Next we're moving to the func()
- As we are in the function we need to update %EBP. But before that, we save it onto the stack, so we can later come back to main()
- Set %EBP to be equal to %ESP. %EBP now points to current stack pointer.
- Push local variables onto the stack. %ESP is being changed in this step.
- After func gets over we need to reset the previous stack frame. So set %ESP back to %EBP. Then pop the earlier %EBP from stack, store it back in %EBP. That way pointer register points back to where it pointed in main().
- Pop the return address from stack and set %EIP to it. The control flow comes back to main, just after the func function call.
Now as we know the concept behind the memory of running program, let's take a look at vulnerability called buffer overflow.
A buffer overflow happens when you assign more data than can fit into the buffer and overwriting the code beyond memory address resulting in program crash.However the problem is that somewhere beyond your buffer is the return address and if you manage to load byte code of your program you may be able to execute it. It may result with privilage escalation, where you byte code is intended to open the shell, as well as an ability to run functions that were not intended to run at that moment.
Now let's look at this simple example:
We've got this simple C program that asks for a password from user and if the password is correct then it provides you with a a secret.
To compile it we can use this built in gcc compiler.
With -fno-stack-protector argument that will disable the stack protection.
Version to compile 32 bit binaries:
You may need to install 32 bit utilities in order to compile 32 bit binaries. This command worked for me:
Now you can run it by typing this command. As you see it asks us for a password and then if the password is correct it provides us with a secret.
But what if we enter a string that is more than 15 characters long? Let's see.
Even though we entered wrong password it gave us a secret. This is because attacker supplied an input of length greater than what buffer can hold and it overwrote the memory of integer ‘pass’. So despite of a wrong password, the value of ‘pass’ became non zero and since then the secret was given.
If you're a C programmer and want to avoid buffer overflows there are few tips that can help your work:
[*] Use fgets() instead of gets()
[*] Use strncmp() instead of strcmp(), strncpy() instead of strcpy() and so on.
[*] Make sure that the memory auditing is done properly
That's the logic behind basic buffer overflows, as you may know there are more advanced cases like injecting machine code that can provide us with root shell and so on. We will explore this together in the next part of reverse engineering series.
As always I hope you enjoyed exploring this topic with me and keep tuned for the next part about shellcodes!
Keep learning and stay safe!