Good day guys, Today we will work on Fusion 5. As far as I know, the only other solution for it online was done by my friend Ariel Koren in his blog. Ariel and myself have worked on it in the same time, and what is so cool in this field is that our solutions are completely different from one another. After you are done with reading my solution, go check out his solution to see different way to solve the same problems 🙂
The challenge ahead
As with the previews levels of this Fusion series, again we are faced with a server that receives connections, and again we need to find and exploit a few bugs in order to get a working Remote-Code-Execution (RCE). Again we face Position Independent Executable (PIE), Address Space Layout Randomization (ASLR), none-executable-stack and heap, and source fortification.
After reading the source, we know that we are dealing with a server that has 5 commands, we will have to find a bug in at least one of them in order to get our RCE. The commands are:
addreg - Registers a server in the db senddb - Sends the db to a chosen host and port checkname - recives a server name and tells if it is indexed in the db quit - Ends the session of our connection isup - Tries to connect to all of the hosts in the db sends each of them its db entry.
Each of those commands is executable by a “task” we know that because we see reference to the function “taskcreate” in the source. This is really bad news, for anyone who doesn’t know what is a task. I can tell that the definition is kinda fuzzy but usually when someone talks about tasks they mean user-mode-thread. If it is the case in here, we should know that:
- Every task has its own stack.
- All of the tasks share the same address space.
- If we crash one task the whole program will crash.
If we are right about how the program works with tasks, After we will find a buffer overflow and crash the program, Our work will be 100 times harder than in the previews challenges in this series. Spoiler-alert, the internet is filled with solutions to Fusion:0, 1 ,2 ,3 ,4. Not for Fusion 5. But we are getting ahead of our selves.
Writing a working client
I write my client which is quit simple, I pulled my hair about why I don’t get response from this line:
printf("registration added successfully");
I connected with a debugger and made sure my input for addreg is working, and the printf is being called (It is optimized into puts). and then I saw
Which teaches us that we are not getting the output of stdout. That makes sense because the server supports multiple clients on the same process. And file descriptors are unique per process.
#! /usr/bin/env python import socket import time PORT = 20005 HOST = "VM" def setup_session(source_port = None): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) return s def send_command(s, command, wait_for_output): command += "\n" s.send(command, len(command)) time.sleep(0.1) if wait_for_output: return s.recv(100) def send_checkname_command(s, text, read_output = True): return send_command(s, "checkname " + text, read_output) def send_addreg_command(s, text, read_output = True): return send_command(s, "addreg " + text, read_output) def main(): s = setup_session() print send_checkname_command(s, "Name") send_addreg_command(s, "Name 0 18.104.22.168", False)
I run this client and get this output:
$ python fusion5.py ** welcome to level05 ** Name is not indexed already None
It is kind of working and I move on to search for the buffer overflow.
The Buffer overflow
After carefully reading the source I noticed he function senddb. The passes over all of the lines in the database (actually array of struct registrations) copies all of the non-empty entries in this array to a buffer and sends the buffer to a given server. What was weird in my eyes is that the buffer is in the size of 512 bytes, while every entry in the array is 6 bytes. 512 is not divisible by 6 (because it is not divisible by 3). which means something here is fishy. multiplying the size of the database (128) by the size of each entry(6) gives us 768 bytes. which means that we can overflow our buffer by 256 bytes. This should be pretty good. now let’s see how can we control the data in this array.
In order to add an entry into the array, we need to use the addreg command. This command receives a host name, ip and flags (short number), and adds this name into the database with its flags, the index in the database that would be used for this entry is a hash that is calculated on the host name. After thinking about it, we can “break” the hash by implementing the hash function in python, and finding a suitable name for every entry in the hash table (Simply by brute-forcing names.)
I did it, and now I can make sure the database struct is full with whatever I want in every index. This way I can control the data that I overwrite the struct with and I even managed to crash the program.
But, considering it more carefully, I can’t really write what every I want into this struct (and later the stack overflow) because of those 2 line inside addreg function:
it means that if the flags part of the entry can not have any bit that are not in 0x00e0 on. At first I thought I can maybe work out something with it, but it really complicates my work. basically it means I really control only 4 out of every 6 bytes of the overflow. After I banged my head against the wall trying to think of how do I continue from here.
On one hand, the previous levels all had some kind of pseudo-cryptographoic challenge. So it makes sense that I need to use this trick with the hash table. On the other hand, we don’t have the address of the main binary, we don’t have the address of libc- or any other lib for that matter. We don’t have the stack address, and we can’t brute-force address because the program will crash and that’s it.
Fuck it, We need another buffer overflow
We go back to the source code, and we try to find another buffer overflow. We find out that the function get_and_hash receives a buffer, max_size for that buffer and a separator. it copies the buffer into a local stack buffer until reaching the separator, but without considering max_size. Well max_size is only tested to make sure its not bigger than the length of the local buffer. The input buffer is not validated against max_size. As if the function assumes that no one will pass a buffer longer than max_size. Guess who passed a buffer longer than max size? That’s right, I did 🙂
This function is called from the command checkname on the name to be checked and returns a hash for this name. Unless of course we overflow it. I call this function from the main in my client while being connected with the debugger:
print send_checkname_command(s, "a" * 50)
Meanwhile in gdb:
Program received signal SIGSEGV, Segmentation fault. 0x61616161 in ?? () 1: x/5i $eip Disabling display 1 to avoid infinite recursion. => 0x61616161: Cannot access memory at address 0x61616161 (gdb) i r eax 0x11 17 ecx 0x61 97 edx 0x2c50 11344 ebx 0xb783811c -1216118500 esp 0xb8d38c10 0xb8d38c10 ebp 0x61616161 0x61616161 esi 0x61616161 1633771873 edi 0x61616161 1633771873 eip 0x61616161 0x61616161 eflags 0x10296 [ PF AF SF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
We have a buffer overflow and we control most of the registers. Want to know how do we exploit it? Want to know how do we overcome ASLR? Read the next post in this series 🙂