heap总结
堆溢出查找
calloc与malloc与realloc
calloc会清空堆块,malloc不会,通过堆溢出,修改insue位为IS_MAPPED可以让calloc不清空堆块
realloc 的操作并不是像字面意义上那么简单,其内部会根据不同的情况进行不同操作
- 当 realloc(ptr,size) 的 size 不等于 ptr 的 size 时
- 如果申请 size > 原来 size
- 如果 chunk 与 top chunk 相邻,直接扩展这个 chunk 到新 size 大小
- 如果 chunk 与 top chunk 不相邻,相当于 free(ptr),malloc(new_size)
- 如果申请 size < 原来 size
- 如果相差不足以容得下一个最小 chunk(64 位下 32 个字节,32 位下 16 个字节),则保持不变
- 如果相差可以容得下一个最小 chunk,则切割原 chunk 为两部分,free 掉后一部分
- 如果申请 size > 原来 size
- 当 realloc(ptr,size) 的 size 等于 0 时,相当于 free(ptr)
- 当 realloc(ptr,size) 的 size 等于 ptr 的 size,不进行任何操作
利用realloc这个特性,可以free二次,造成double free
危险函数
scanf, gets,strcpy
自写的输入函数, 注意结尾处理,有加\x00可能有off-by-null
源码分析
- malloc过程
- free的过程
malloc
- 首先查看有没有hook函数,有则执行,无继续 (攻击点)
- 获取arena
- 调用_int_malloc分配内存,分配失败重新找一个arena,分配成功在结束时候则需要解锁
- 检测
- 返回堆块
fastbin
- 首先查看其所在链表,即在0x20–0x80哪一条链表上
- 若在的话,则遍历该链表,不在则转到smallbin
- 若找到空闲chunk,则检测其大小,若正确则分配
由于采用的单链表,同时用的头插法插入,所以策略为LIFO
可能利用点::
- 遍历链表过程
smallbin
- 获取small bin索引
- 获取bin
- 求smallbin最后一个chunk
- 若不存在一个,则初始化,这里会执行malloc_consolidate,会将fastbin合并成unsortedbin
- 存在,则检测倒数第二个是否指向当前chunk,若是,设置标志位,然后取出
可能利用点:
- malloc_consolidate 申请大量fastbin,让其合并成unsortedbin
largebin
- 获取索引
- 存在fastbin则合并fastbin先
可能利用点:
malloc_consolidate 申请大量fastbin,让其合并成unsortedbin
unsortedbin
策略FIFO,根据bk遍历
先考虑unsortedbin然后考虑remainder, small request例外
如果用户的请求为 small bin chunk,那么我们首先考虑 last remainder,如果 last remainder 是 unsorted bin 中的唯一一块的话, 并且 last remainder 的大小分割后还可以作为一个 chunk
sysmalloc
- mmap
- morecore(可以攻击这个函数, 当malloc_hook和free_hook都无法使用时候)
free
- 检测hook函数(攻击点)
- mmap标志特殊处理, 若不是调用_int_free处理
报错1,检测size是否大于最小的chunk
1 | // 大小没有最小的chunk大,或者说,大小不是MALLOC_ALIGNMENT的整数倍 |
fastbin检查
- 下一个chunk的大小不能小于两倍的SIZE_SZ
- double free检查
合并:
- 前向合并
- 后向合并
与top_chunk合并,靠近的话
tcache
相关结构体
tcache_entry
1 | /* We overlay this structure on the user-data portion of a chunk when |
tcache_entry
用于链接空闲的 chunk 结构体,其中的 next
指针指向下一个大小相同的 chunk。
需要注意的是这里的 next 指向 chunk 的 user data,而 fastbin 的 fd 指向 chunk 开头的地址。
而且,tcache_entry 会复用空闲 chunk 的 user data 部分。
tcache_perthread_struct
1 | /* There is one of these for each thread, which contains the |
每个 thread 都会维护一个 tcache_prethread_struct
,它是整个 tcache 的管理结构,一共有 TCACHE_MAX_BINS
个计数器和 TCACHE_MAX_BINS
项 tcache_entry,其中
tcache_entry
用单向链表的方式链接了相同大小的处于空闲状态(free 后)的 chunk,这一点上和 fastbin 很像。counts
记录了tcache_entry
链上空闲 chunk 的数目,每条链上最多可以有 7 个 chunk。
用图表示大概是:
基本工作方式
- 第一次 malloc 时,会先 malloc 一块内存用来存放
tcache_prethread_struct
。 - free 内存,且 size 小于 small bin size 时
- tcache 之前会放到 fastbin 或者 unsorted bin 中
- tcache 后:
- 先放到对应的 tcache 中,直到 tcache 被填满(默认是 7 个)
- tcache 被填满之后,再次 free 的内存和之前一样被放到 fastbin 或者 unsorted bin 中
- tcache 中的 chunk 不会合并(不取消 inuse bit)
- malloc 内存,且 size 在 tcache 范围内
- 先从 tcache 取 chunk,直到 tcache 为空
- tcache 为空后,从 bin 中找
- tcache 为空时,如果
fastbin/smallbin/unsorted bin
中有 size 符合的 chunk,会先把fastbin/smallbin/unsorted bin
中的 chunk 放到 tcache 中,直到填满。之后再从 tcache 中取;因此 chunk 在 bin 中和 tcache 中的顺序会反过来
tcache_put()
1 | /* Caller must ensure that we know tc_idx is valid and there's room |
这里采用的是头插法插入, 每次直接从链表头插入节点
tcache_get()
看一下 tcache_get()
1 | /* Caller must ensure that we know tc_idx is valid and there's |
tcache_get()
就是获得 chunk 的过程了。可以看出这个过程还是很简单的,从 tcache->entries[tc_idx]
中获得第一个 chunk,tcache->counts
减一,几乎没有任何保护。
这里也是直接从头部取出
tcache_init()
此libc版本为2.27,2.29将tcache结构改掉了,他不是用char来存储数值了,而是用两个字节
1 |
|
我就以这份代码来调试
以一份调试结果来说明吧
这里看到,首先有一个大堆块,我们称其为tcache_perthread_struct
这个块的大小在64位系统中为
641+ 64 *8 = =0x40\9 = 0x240再加个堆头为250,标志为1
前面的便是记录链表的长度,后面的均为链表的头结点,,分别对应0x20, 0x30,0x40….0x410,总共64条链
总结
- tcache是一个后进先出的模式,他是通过头插法插入节点,同时也直接从头部取出节点, 可以把它当做栈的模式,每次压入栈,然后从栈里取出节点
- 第一个tcache链表头大小为0x20,总共有64个链表,每次递增0x10, tcache最大为0x410,超过410便不进入tcache了,也就是说malloc(0x400) 释放后还进入tcache, malloc(0x410)释放后便不进入了,因为超过了最大的限制大小
- unsortedbin攻击在2.27版本变成了main_arena+96? 待确认
堆溢出的攻击方式
- off-by-one
- unlink
- fastbin attack
- tcache attack
- overlap
- chunk extends
- uaf
- largebin attack
- unsortedbin(大数值改写,改写循环条件,改写global_max_fast)
- house of系列
off-by-one
- off-by-null
- off-by-one
通常利用其造成堆块重叠进行攻击
- off-by-one过后, free掉,可以获得更大的堆块,比如本身为0x21,覆盖成0x90, 这样可以进unsorted bin了
- off-by-one造成unlink攻击, 覆盖下一chunk的pre_size和size标志位为0, 同时需要bypass检测, 伪造下一chunk的下一个chunk不能小于最小的chunk
- off-by-null可以选择特定大小堆块进行free, 比如0x101,0x201这种,刚好只修改标志位
- off-by-one构成overlap, 利用前向合并, 覆盖掉pre_size和标志位过后, free掉, 合并过后造成overlap了
overlap
uaf
- uaf造成堆块信息泄露,
- free掉后,没清空堆块数据内容,我们可以拿到fd指针, 输出后便可泄露
- uaf过后可以修改fd指针, 修改后我们可以伪造地点, 让其malloc到指定地点处, 比如chunk-0x10处, 就能修改pre_size跟size了,当然要过掉检测. 随后便是堆溢出常规手法了, unlink, overlap等等
unlink
双链表解链,这个bin必须得非fastbin,通常将其覆盖成目标地址-0x10和-0x18处,同时该目标地址存了一个指向当前堆块用户空间的指针
这里注意可以利用前向合并跟后向合并
fd = target-0x10
- 首先,第一步要过掉unlink的size检测,覆盖chunk3的pre_size为fake_chunk大小
- 其次chunk3的insue位要为0,标志前面一个堆块未在使用当中
- 然后关键点就是伪造fd跟bk了
- 在第一点中我将ptr设置为global+0x10意思就是第二块堆块地址,这就是存放p的地方
- unlink第一步 FD = p->fd = ptr-0x18
- unlink第二步 BK=p->bk = ptr-0x10
- unlink第三步 判断FD->bk == p && BK->fd == p ?
- 过了检验后
- FD->bk = * (ptr-0x18 + 0x18 )= BK = ptr -0x10
- BK->fd = * (ptr-0x10+0x10) = FD = ptr-0x18
最终结果就是*ptr = ptr-0x18,而ptr是0x0000000000602150故最终就是将global+0x10处的值改为0x602138
然后我们在编辑第二块的时候实际上就是编辑0x602138处,也就是global-0x8处
fastbin attack
- double free
- house of sprit 伪造前后堆块
- arbitrary alloc(通常用来打malloc_hook,错位伪造)
house of spirt检测
要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即
- fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
- fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
- fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
- fake chunk 的 next chunk 的大小不能小于
2 * SIZE_SZ
,同时也不能大于av->system_mem
。 - fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
double free
Fastbin Double Free 能够成功利用主要有两部分的原因
- fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
- fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
利用过程
- 首先构造三个堆块
- free(1), free(2), free(1)
- 造成double free, 这时在malloc
Alloc to Stack
这个技术利用挺难的, 因为大部分堆题都开启了全保护, 而栈地址无法泄露, 到栈里干事很难, 如果我能泄露栈,并且栈中有特定位置可以构造, 我们malloc到栈中,常规ROP就行
Arbitrary Alloc
- 打malloc_hook常用手法
- main_arena附近有0x5x开头的堆块,进行错位伪造, 申请0x5x,修改main_arena->top, 然后就可以改top了,这里有概率失败,因为不一定是0x56,可能为0x55, 不过作为 fastbin attack 的 size 不能 为 0x55
- 利用unsorted bin attack将值写入free_hook附近,这样就可以错位伪造了,注意这里写入的是 *(bk+0x10)
- 分配到bss, 伪造_IO_FILE_plus,然后修改vtable为get_system
有一个小坑要注意,在 __free_hook-0x30
开始 的 0x30
个字节 是 _IO_stdfile_*_lock
区域,用于 std*
类文件的锁操作,这个区域的内存会被经常清零。
所以 unsorted bin attack
应该往上面一点, 比如 libc.symbols[‘__free_hook’] - 0x50
还有一点就是在进行 unsorted bin attack
以后 , unsorted bin
链表就被破坏了,所以 就只能通过 fastbin
或者 smallbin
进行内存的分配,所以我们应该先劫持 fastbin
的 fd
到 目标位置,然后触发 unsorted bin attack
写入 size
, 最后进行 fastbin attack
,修改 __free_hook
unsorted bin attack
- 利用其改循环数值
- 改global_max_fast
tcache attack
也是挺多攻击方式,最常用也是double free
house of 系列
本文作者:NoOne
本文地址: https://noonegroup.xyz/posts/6a7f4fcc/
版权声明:转载请注明出处!