Since the last article was written with the sole purpose of using one_gadget, it didn’t present the most straight-forward solution for the baby_rop challenge. In this article, I’m going to present the easiest solution that I know of, mainly because I would like to have a template for the next baby ROP challenge I encounter.
Mission Briefing
Just to recap, the challenge can be found on CyberEDU. The executable has NX enabled, but there’s no stack canary and PIE is disabled.
Finding RIP’s Offset
We already know the app is going to crash if we input a string that is 1024 characters long. The following script will send a cyclic pattern that will help us get RIP’s offset:
As expected, RIP is overwritten and the application crashes. Finding the register’s offset is one function call away:
A simple PoC to crash the application and overwrite RIP with ‘C’s can be found below:
The reason I also included those 8 ‘B’s before the ‘C’s is to highlight what that string overwrites:
The ‘B’s can be found in the RBP register. I somehow managed to forget that when I solved the challenge yesterday and modified RBP by finding a ‘pop rbp ; ret’ gadget. After someone pointed that out, I quickly edited the post to modify RBP by using this technique.
Leaking a LIBC Address
The first ROP chain was also covered in the first article. To recap, we are first going to call puts with puts@got as an argument and then call main again. The first thing that needs to be found is a ‘pop rdi ; ret’ gadget (call convention: function arguments are stored in RDI, RSI, RDX, RCX, R8, R9, etc.). I did that using rp++:
The next addresses we need to find are puts@plt (wich we are going to call) and puts@got (which contains the address of puts in libc). In this case, using objdump and grep will do the trick:
The main function is located at 0x40145C. The binary is stripped, so I had to open IDA in order to find it. If the binary was not stripped, ‘objdump -S pwn_baby_rop | grep main’ whould have been sufficient to find the function’s address. The 1st stage exploit code looks like this:
Finding the Server’s LIBC Version
There’s a >99% chance that the server is using another version of LIBC than my computer is. As I mentioned in the last article, this version can be found by running the 1st stage on the remote machine and searching the last 3 nibbles of puts’ address(in this case, 5a0) on this site. After downloading the .so file, we can tell the program to load it instead of our system’s by changing
to
To test wether this worked or not, I ran the script again:
The address of puts ends in 5a0 now, so we can move on to the last step.
RCE? Nope.
The simples method to get RCE now is to call system(“/bin/sh”). The first thing we need to do is calculate LIBC’s base address. The site I linked above also provides the offsets of some helpful functions. According to that list, puts is located at 0x0875a0, system at 0x055410 and a “/bin/sh” string at 0x1b75aa:
Another problem I wrote about is that system() is going to try to write some data to the stack by using the RBP register. We can find a memory adrees that is both readable and writeable by using gdb’s vmmap command:
Memory block 0x00404000-0x00405000 is a prefect candidate, so I will use 0x00404500 for RBP. The code for the second stage looks like this:
However, the exploit seems to crash the program:
Aligning the Stack
After attaching gb to the process, we can see the instrucion that crashed the program:
This StackOverflow thread does a good job at explaining the cause of this problem. Basically, LIBC expects the stack to be 16-bit aligned when a function is called and uses this property to optimize some portions of its code. The solution is very simple: call a ret instruction. We can find a ret gadget in the main program using rp++:
The final script looks like this:
Running it against the remote server will result in a shell:
The End
That wasn’t very hard, was it? I hope you’ve learned something new from this article (I certainly have!). The python scripts I used can be found in this repo.