I managed to get sent to BSides London a couple of weeks back, thanks to ProCheckUp. I was a bit hesitant at first, due to this being my first con and going on my own, but I thought, what have I got to lose? Turned out - the day was awesome! I had great conversations with others in the industry, thoroughly enjoyed listening to the talks, participating in the workshops and getting lots of free goodies. The main workshop I was looking forward to was on Return-Orientated Programming (ROP), presented by Bas from vulnhub. I’ve been exposed to binary exploitation before by partaking in CTF challenges. I’ve performed ret2libc attacks before, but never went far enough to do ROP.
We all received a copy of a VM which contained 3 challenges that had to be solved using ROP. This blog post will be my write up of the first challenge. Hopefully, if time permits, I’ll do a write up for challenges two and three. So enough background, let’s get started!
First off, I loaded the program up in GDB and disassembled the binary:
Binary has NX enabled, which means I needed to perform ret2libc or ROP to bypass this mitigation.
I saw at 0x0804825a the esp register was subtracted by 0x30 (decimal 48). Which is usually a sign that a buffer has been assigned. Looking through the rest of the code I spotted four functions, three that print to the stdout and one that receives input from stdin.
The next stage was to try and smash the buffer. To do this, peda-gdb has a useful function called “pattern_create” which creates random data to insert into the buffer. Then, depending on what EIP gets overwritten to, it will figure out how many bytes of data it takes to overwrite EIP. Neat, huh?
I decided to go for a length of 48, due to what I figured from the disassembly of the main function.
Python was used to print this data into a file (inp).
I opened the binary back in GDB and ran the program with inp file as input. This overwrote EIP to “0x41414641″ using pattern_offset and the “0x41414641″ address. GDB told me that the buffer was 44 bytes long.
I then used python to print 44 A’s and then DEADBEEF. I fed this through GDB and noted what EIP had been overwritten to. Success - I could overwrite the EIP correctly!
Now that I could control execution of the binary, I spent some time figuring out my game plan for using ROP to get a shell.
I would use the mprotect function to set an area of memory to executable, call the read function and then finally feed in shellcode to the read function to execute my shell.
To do this I noted the addresses of mprotect and read (ASLR was disabled, so these addresses stayed the same).
I created a skeleton python script for my exploit.
A quick run to see if it still overwrites EIP properly.
Looks good to me! Moving on… The next thing I needed in my exploit was the mprotect function. I looked up mprotect in man.
So I saw that mprotect takes three arguments: the address to change the protection of, the size of memory to change the protection of and finally, what protection to set on the memory selected.
Using vmmap in GDB I found an address to use for the first argument. For the second argument, I decided to go for a size of 2000, though for the final argument I had to dig deeper.
After a bit of looking around, I managed to find where the protection values were defined. I went with PROT_READ, PROT_WRITE & PROT_EXEC which is 0x7 (0x1+0x2+0x4).
I updated my exploit script as shown above. I set the address for mprotect and read, then setup, my mprotect function.
I executed the exploit in GDB and found that EIP got overwritten to deadbeef again. Then I used vmmap to see if the area of memory I had chosen to set as read, write & exec had worked. RWXP -cool, so my mprotect ROP chain worked. Now I needed to set up my read ROP chain.
However I first needed to find a rop gadget to pop mprotect and its arguments off the stack.
Using “ropgadget” in GDB gave me a list of ROP gadgets to choose from. As mprotect had three arguments, I went with: pop3ret.
I updated my exploit script to change the fake ret address to pop3ret and then appended the payload with “BBBB” which will be set as the new EIP.
Okay, all good! After running the updated exploit, we can see that EIP had been overwritten by “BBBB”. Next thing to do is open up the man for the read function.
So as we can see, read takes 3 arguments. The first is file descriptor, which we want set as stdin (so we can feed our shellcode in). The second is the address of the buffer (the one we set with mprotect). Finally, is the count of the bytes we want read in.
After some looking around, I found that the STDIN is defined as 0. Now that we have what we need for the read function, we can add it to the exploit.
The read rop chain is set up and a fake ret of deadbeef. Let’s give the exploit a run and see if it works…
All good! EIP gets overwritten to deadbeef. Now all that’s left to do with our exploit script is reuse our pop3ret gadget in place of the fake ret address and then set EIP to point to the executable buffer we created.
Now the exploit is done, it’s time to run it and see if it executes shellcode properly.
The shellcode above represents a breakpoint. As you can see above, the exploit worked because it hit a breakpoint. All that’s left to do now is insert some shellcode to launch /bin/sh. The shellcode I went with is:
\x6a\x0b\x58\x99\x52\x66\x68\x2d\x70\x89\xe1\x52\x6a\x68\x68\x2f\x62\x61\x73\x68\x2f\x62\x69\x6e\x89\xe3\x52\x51\x53\x89\xe1\xcd\x80
w00t w00t! So the shellcode gave us our shell with level1 privileges and allowed us to cat flag to move me on to the next challenge.
Thanks again to Bas, vulnhub, ProCheckUp and everyone at BSides! Can’t wait to come back again next year!
Categories