So the other day I came across a wonderful resource on learning Return Oriented Programming, an exploitation technique, called the "ROP Emporium". In this blog post, I'll outline how I went about solving the "ret2win" introductory challenge.
So, lets step through how I went about solving the 32 and 64 bit ret2win challenges. We will start with the 32 bit one, which is the easiest...
Opening it up in Binary Ninja, we start examining the main()
function, and notice it calls a function named pwnme()
... There is also a function named ret2win()
, which we will get to later.
So we follow onwards and examine the pwnme()
function, based on the name, we suspect strongly this is the function which is vulnerable.
As we can see, the pwnme()
function takes in user input using fgets()
, and stores it in a stack buffer of size 32. There is no check on how much crap we put into the buffer, so its plainly obvious we can do a stack buffer overflow here.
Examining the ret2win()
function, which is normally never called, we see it prints out the flag.txt file. This is obviously how we win. We must find a way to call this function!
So, our first port of call, is going to be figuring out how much junk we have to hit it with to get control over the EIP (Extended Instruction Pointer) in order to hijack the control flow. So, we open it up in gdb
, set some breakpoints on the main()
and pwnme()
functions, and spaff some junk into it to confirm that we do have control over registers from the stack buffer overflow.
Next, we want to see where exactly we control. So, being lazy, I created a string using Python that would tell me approximately where I have control over execution by using the following oneliner, and shoved that in as input.
python -c "print 'A'*32+'BBBB'+'CCCC'+'DDDD'+'EEEE'+'FFFF'+'GGGG'+'HHHH'+'IIII'"
The EBP register is smashed with 0x44444444, and EIP is smashed with 0x45454545. These correspond to "D" and "E", respectively. We want to overwrite EIP with a return address to code we want to execute, so we need to send 44 characters of junk (i.e. how many chars we sent before the "E" chars), followed by our return address... So our next goal is finding where we want to return to.
Luckily, we want to return to the "ret2win" function, and according to Binary Ninja, it lives at 0x8048659
. So we simply have to convert this to a little endian address, as we did with the 0xdeadbeef
value in Level 0 of Narnia, append it to our junk, and we should have a working exploit...
This leaves us with the following result, a simple, working exploit!
And now for x64...
The 64 bit challenge is equally quite simple. Since we know its the same thing, but on a 64 bit architecture, we go right into debugging it, and feeding it the long crash test string to find out where exactly we need to be to wreck it.
So we need 36 characters to smash the RBP register (based on "CCCC" or 0x43434343
value). Another 4 of junk padding, and then our offset to hijack instruction pointer and get code execution. Again, we know we want to return to the ret2win
function, which has an offset of 0x000000400811
. So we use a simple shell command to put it into little endian...
$ a=`printf %016x 0x000000400811 | tac -rs..`
$ echo $a
1108400000000000
Now, when we convert this to escaped hex bytes, we get the following string: \x11\x08\x40\x00\x00\x00\x00\x00
. So we append this to our buffer of 40 junk bytes and hopefully we gain control over execution flow...
And as you can see, we win! We successfully put together a single-gadget ROP exploit for both binaries in a reasonably short amount of time!
I'm going to continue on with the Narnia series of posts in the coming weeks, along with more of the ret2win challenges. I'll probably also begin posting writeups for the "Natas" web challenges soon.
If anyone has any comments/criticism or suggestions for improvement, etc, I'd be glad to have the feedback! Let me know in the comments, and perhaps have a go yourself at these types of challenge!