The most basic binexp challenge
A ret2win is simply a binary where there is a win()
function (or equivalent); once you successfully redirect execution there, you complete the challenge.
To carry this out, we have to leverage what we learnt in the introduction, but in a predictable manner - we have to overwrite EIP, but to a specific value of our choice.
To do this, what do we need to know? Well, a couple things:
The padding until we begin to overwrite the return pointer (EIP)
What value we want to overwrite EIP to
When I say "overwrite EIP", I mean overwrite the saved return pointer that gets popped into EIP. The EIP register is not located on the stack, so it is not overwritten directly.
This can be found using simple trial and error; if we send a variable numbers of characters, we can use the Segmentation Fault
message, in combination with radare2, to tell when we overwrote EIP. There is a better way to do it than simple brute force (we'll cover this in the next post), but it'll do for now.
You may get a segmentation fault for reasons other than overwriting EIP; use a debugger to make sure the padding is correct.
We get an offset of 52 bytes.
Now we need to find the address of the flag()
function in the binary. This is simple.
afl
stands for Analyse Functions List
The flag()
function is at 0x080491c3
.
The final piece of the puzzle is to work out how we can send the address we want. If you think back to the introduction, the A
s that we sent became 0x41
- which is the ASCII code of A
. So the solution is simple - let's just find the characters with ascii codes 0x08
, 0x04
, 0x91
and 0xc3
.
This is a lot simpler than you might think, because we can specify them in python as hex:
And that makes it much easier.
Now we know the padding and the value, let's exploit the binary! We can use pwntools
to interface with the binary (check out the pwntools posts for a more in-depth look).
If you run this, there is one small problem: it won't work. Why? Let's check with a debugger. We'll put in a pause()
to give us time to attach radare2
onto the process.
Now let's run the script with python3 exploit.py
and then open up a new terminal window.
By providing the PID of the process, radare2 hooks onto it. Let's break at the return of unsafe()
and read the value of the return pointer.
0xc3910408
- look familiar? It's the address we were trying to send over, except the bytes have been reversed, and the reason for this reversal is endianness. Big-endian systems store the most significant byte (the byte with the largest value) at the smallest memory address, and this is how we sent them. Little-endian does the opposite (for a reason), and most binaries you will come across are little-endian. As far as we're concerned, the byte are stored in reverse order in little-endian executables.
radare2
comes with a nice tool called rabin2
for binary analysis:
So our binary is little-endian.
The fix is simple - reverse the address (you can also remove the pause()
)
If you run this now, it will work:
And wham, you've called the flag()
function! Congrats!
Unsurprisingly, you're not the first person to have thought "could they possibly make endianness simpler" - luckily, pwntools has a built-in p32()
function ready for use!
becomes
Much simpler, right?
The only caveat is that it returns bytes
rather than a string, so you have to make the padding a byte string:
Otherwise you will get a