BinExploit 0x1 - ROP Chain
This post will be the first of many (hopefully) binary exploitation posts.
I am writing this so I can refer in the future and maybe someone will learn something from it too.
I will be using the binary from github.
Part 1 - Functions & Static Analysis
Loading the binary into gdb
and looking at the functions reveals 4 interesting functions
- flag
- vuln
- win_function1
- win_function2
Lets look into the 4 functions and what they do.
Looking at the decompiled code with ghidra shows shows that main just initialises the programme & calls the vuln function.
vuln essentially just asks for user input and the programme ends.
From the decompiled code, we can see that in order to solve this challenge, we have to run flag with win1=true, win2=true & flag argument must be as specified.
This means that we need to chain win_function1 -> win_function2 -> flag in order to succeed.
Part 2 - Buffer Overflow in vuln
Loading the binary into gdb and looking at the assembly reveals a gets
to receive user input.
The problem with gets is that it does not check the length of user input, thus resulting in potential buffer overflow and redirection of code flow.
We can modify the EIP by finding the point of overflow and change it to any address we want.
For this case, we want it to go to the win_function1 first.
We can do that by sending a padding till the point of overflow, then follow up by 0x080485e6 to redirect the code execution to win_function1.
By sending in 32 character, we can see that we overflowed the return address on the stack which is then used by the EIP.
As per the picture, we can see that we can control the EIP with the last 4 bytes of our 32 character input.
With that, we can redirect the code flow to the win_function1
As we can see now, the programme crashes as the return address is not set yet.
So how can we set the return address?
In order to do that, we must first understand how functions are called in assembly.
Before a function is called, the parameters for the function are pushed onto the stack. The function is then called.
When the call instruction is executed, the address of the (EIP + 1) is pushed onto the stack as a return address.
|
|
After that, EBP is pushed and EBP is set to the ESP and the ESP moves downwards to create space for local variables within the function.
|
|
When the function ends or when it exits, the ESP is shifted back up by how many address space it allocated, effectively destroying the local variable space. The EBP is then then restored with a pop instruction.
|
|
|
|
From the visualization above, we see that we can by padding the local var space, we can eventually overwrite the return address for vuln, achieving code redirection.
We can then overwrite the return address to redirect the code flow to win_function1
Part 3 - Chaining Redirection to win_function2
Now that we know how a function call works, let’s figure out why the programme crashes when it exits win_function1
Let’s set a breakpoint right before the EBP is pop-ed and take a look at the stack.
From the picture, we can see that when EBP is pop-ed, EBP will be set to 0x41414141 and the ESP will move up.
From the picture, we can see that when win_function1 reaches the ret instruction, 0x00000000 is at the top of the stack. This means that EIP will restore that address to continue the programme flow. 0x000000 is not a valid location for instructions and the programme crashes.
We now need to add on to our payload to see if we can overflow this return address to go to win_function2.
We can simply pad the end with “B” to see how much we need to pad in order to reach this return address.
We see that by adding on to our payload, we have already overwritten the return address. We can now change the “B” to 0x80485fd, the adress of win_function2.
Part 4 - Setting Arguments for win_function2 and Chaining to flag function
Let’s take a look at what win_function2 does before we move on.
Looking at the disassembly of win_function2, we can see that there is a
|
|
We can see that win_function2 requires the argument to match 0xbaaccaad. We can deduce that as function arguments are normally referenced as [ebp+0xN] in assembly.
We can set a breakpoint right before the compare to see where the argument is stored.
Currently, the argument passed is 0xffffca1c. Since our “A” padding is only 8 bytes away from the argument, we can overflow it to set it to 0xbaaccaad.
We have successfully overwritten the argument and we can continue to chain to the flag function.
By setting a breakpoint before the return instruction, we can see where the return address is stored and attempt to overwrite it.
At the top of the stack is our padding of “C” before we set the argument for win_function2. By changing the “C” to the address of flag, we can redirect the code execution there.
Part 5 - Setting Arguments for flag function & Getting Flag
Looking at the disassembly of the flag function, we can see that we need to set the function argument to 0xdeadbeef.
Let’s set a breakpoint before the compare and see where the argument is in the stack.
We can see that the current argument is 0x00000000. We can set it by continuing to pad the payload and overwrite the argument.
We have now overwritten the argument. Let’s see what happens now.
The Challenge has been solved!!
I hope that this article has made the understanding of the ROP Chaining attack easier as it was really a struggle for me to understand this and I hope that you have benefited from my suffering :)
Again, if anything was wrong in this article, please contact me so I can change and learn from it. (^o^)
See you in the next BinExploit!