libc版本检查机制
前言
本篇本章是以libc-2.23.so为基础,去对比之后版本的差异问题
libc-2.27.so
Tcache
tcache是在libc-2.27.so引进的一种新机制
tcache_entry
1 |
|
tcache_perthread_struct
1 |
|
tcache_prethread_struct
是整个 tcache 的管理结构,其中有 64 项 entries。每个 entries 管理了若干个大小相同的 chunk,用单向链表 (tcache_entry
) 的方式连接释放的 chunk,这一点上和 fastbin 很像- 每个 thread 都会维护一个
tcache_prethread_struct
tcache_prethread_struct
中的counts
记录entries
中每一条链上 chunk 的数目,每条链上最多可以有 7 个 chunk- tcache_entry用于链接 chunk 结构体,其中的next指针指向下一个大小相同的 chunk
- 这里与 fastbin 不同的是 fastbin 的 fd 指向 chunk 开头的地址,而 tcache 的 next 指向 user data 的地方,即 chunk header 之后
简单来说:就是类似fastbin一样的东西,每条链上最多可以有 7 个 chunk,free堆块的时候优先放入tcache中,满了才放入fastbin,unsorted bin,malloc的时候优先去tcache找(tcache的范围是 [0x20, 0x410],超过这个大小的就会放入unsorted bin)
tcache dup:因为前几个版本的 tcache bin
是缺乏校验机制的,即使对tcache bin chunk
重复释放,也不会引发任何异常。比fastbin chunk
的约束更少,一来不检查size域,二来也不检查是否重复释放
tcache_perthread_struct
这个结构体是可以释放的,并且可以将它释放到unsorted bin
中去(前提是先修改0x250大小堆块的count为7),然后分配这个unsorted bin chunk
,可以控制任意地址分配堆内存。
高版本Tcache
一、在libc-2.29.so及以上的版本往tcache结构体添加了一个key
来防止double free,判断条件就是tcache_entry的key指针(被释放堆块的bk指针位置上填入tcache的地址)是否等于tcache bin的地址
1 |
|
绕过:利用UAF或者溢出等等,修改被释放堆块next指针
以及Stash机制:
1 |
|
当一个线程申请0x50大小的chunk时,如果tcache没有,那么就会进入分配区进行处理,如果对应bin中存在0x50的chunk,除了取出并返回之外,ptmalloc会认为这个线程在将来还需要相同的大小的chunk,因此就会把对应bin中0x50的chunk尽可能的放入tcache的链表中去
这么做会存在一些问题,对于比较典型的 fastbin double free 产生了一个很有趣的影响:
首先需要先释放7个chunk,填满tcache,然后Free(C7) Free(C8) Free(C7),在fastbin中构造出环
下一步,为了分配到fastbin,需要先申请7个,让Tcache为空,再次申请时就会使用fastbin中的C7,这一步是整个手法的精华。取出C7后,Stash会把fastbin链表中的chunk全部放入Tcache中,而C7又是被我们分配到的堆块,是可控的,这就导致我们不需要伪造size字段,获得了一个真正的任意写。
二、在 libc-2.32.so
版本中新加入了一个 key 会对 tcache next 的内容进行异或
1 |
|
1 |
|
PROTECT_PTR:对 pos 右移了 12 位(去除了末尾的 3 位十六进制信息),再异或ptr。
即:被释放堆块fd=(被释放堆块fd所在地址>>12)^被释放堆块的前一个堆块fd所在地址。
而这里的 key 就是储存内容的指针(在代码中叫做 pos),在放入的时候让内容与这个 key 进行异或再储存,在取出的时候让内容与这个 key 进行异或再取出。而得益于这个秘钥就是储存内容的指针,所以无需使用其他空间来放置这个 key 的内容,只需要保存异或之后的内容,在解密时只需 PROTECT_PTR (&ptr, ptr) 这样操作即可。
需要注意的是,当 tcache 中只有一个元素的时候,也就是在放入这个元素的过程中,tcache->entries[tc_idx] == 0,在这个时候放入元素的时候会异或 0,也就是在 e->next 位置存放的内容正好就是 key 的信息,因为 key 异或 0 还是秘钥。而且就算之后加入了其他的元素,这个元素始终还是在链表的尾部,所以内容不会发生变化
绕过:
- 通过 0 异或秘钥还是秘钥的这个特性,当 tcache 链上只有一个指针的时候,我们就可以通过 show 函数来 leak 出秘钥的信息,有了秘钥的信息之后,我们就可以伪造秘钥信息了
- 可以通过 largebin 来泄露堆地址,由于 key 是当前指针 >> 12,所以我们可以确保在 4096 字节内这个 key 都是正确的
利用
一、泄露堆基址
构造两个相同 size 的堆块 a 和 b,我们先 free (a) 让他进入到 tcache 中,再 free (b) 也让他进入到 tcache 中。这时候,在堆块 b 的 fd 位置就存在着堆块 a 的地址,我们 leak 出来就能够得到堆地址
libc-2.29.so
1 |
|
对于
unsortbin
的解链添加了验证链完整性的检查,让unsortbin attack
失效绕过:
1.largebin中的chunk->fd_nextsize=0;
2.largebin中的chunk->bk_nextsize可控制;
3.unsortedbin里的chunk大于largebin,并且如果进入largebin,是同一个index。
Tcache Stashing Unlink Attack
类unlink手法,高版本一样可用,至少目前我知道的:到 libc-2.32 是没问题的
利用条件
1.smallbin
中可以控制大小为size块的bk指针
2.tcache
中大小为size块的个数为6
3.申请堆块是calloc
过程
- 释放6个0x100的chunk到tcache bin中
- 构造两个0x100的small bin(利用Unsorted bin或Large bin切割得到)
- 修改后插入的small bin的 bk 指针为目标地址-0x10,且保持fd指针不变
- 用calloc分配0x100的chunk
结果
- 在目标地址上写入原本small bin上的 bk 指针内容
libc-2.32.so
1 |
|
上面是glibc2.32下的fastbin源码,同样存在跟tcache一样的保护机制,会对 fd 指针进行异或处理
1 |
|
由于这个检测的存在,我们 tcache 申请的地址似乎要做到 0x10 对齐(x64)
libc-2.34.so
该版本删除了各种 hook 函数,所以要更换思路,一般可以通过 FSOP 攻击输出流的函数虚表
- 本文作者:ShouCheng
- 本文链接:http://shoucheng3.github.io/2021/08/30/2021-08-30-libc%E7%89%88%E6%9C%AC%E6%A3%80%E6%9F%A5%E6%9C%BA%E5%88%B6/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!