# Tcache
Tcache 是 glibc 2.26 之后引入的机制,与 glibc 2.23 版本下堆利用方式有一定区别,产生了新的利用方式。
# 0x1 相关结构体
# tcache_entry
1 | typedef struct tcache_entry |
tcache_entry
用来链接空闲的 tcache chunk,其中的 next
指针指向下一个大小相同的空闲 tcache chunk。
注意:这里的 next 指针指向的是 user data 部分,也就是说指向的是 chunk head+0x10 地址处
而 fastbin 则指向的是 chunk head
# tcache_perthread_struct
tcache_perthread_struct
是 tcache 的管理结构,他:
- 收藏了所有的
tcache_entry
- 记录了空闲 tcache 个数,也就是
tcache_entry
的个数
这个管理结构会在第一次调用 malloc 时,先 malloc 一块内存用来存放 tcache_perthread_struct
。因此做 glibc >=2.26 的题时,gdb 调试的时候第一个堆块即为 tcache_perthread_struct
。
# 0x2 基本知识点
- free 的堆块,当内存小于 small bin size 时 (0x400),会被优先置入 tcache bin 链表,当填满七个后,才会填入 fastbin/unsortedbin 链表
- malloc 时,优先从 tcache bin 中寻找是否有合适大小的 bin。
# 0x3 常见利用方式
有
tcache poisoning
tcache dup
tcache perthread corruption
tcache house of spirit
tcache stashing unlink attack
等。
具体利用方式,不再赘述。可以去 CTF-wiki 上看看。
CTF-wiki-tcache
# 0x4 个人总结
学习完 CTF-wiki 上对于 tcache bin 的利用方式后,我个人在做题中也遇到一些林林总总的问题,也 get 到了一些知识点。
# 4.1 tcache double free 机制绕过
# 4.1.1 补充知识点
tcache 为空时,如果 fastbin/smallbin/unsorted bin
中有 size 符合的 chunk,会先把 fastbin/smallbin/unsorted bin
中的 chunk 放到 tcache 中,直到填满。之后再从 tcache 中取。
# 绕过方式
在 glibc 较低版本下,我们可以直接利用 tcache dup
,free 掉一个相同的 tcache 堆块两次。比 fastbin double free 要好用得多。
经过我 patchelf 之后发现, libc2.27-3ubuntu1
及之前均可直接使用 tcache dup
,当版本大于等于 libc2.27-3ubuntu1_
后不可直接 double free。
原因是加入了对 tcache bin double free 的检测,至于检测的源代码我就不贴了,感兴趣的可以上 CTF-wiki 上看。
讲讲绕过的办法:
可以通过 fastbin double free 触发 stash,构造 tcache 任意地址写
具体流程:
- 先 free 掉多个相同大小堆块 (size <=0x80),填满 tcache bin 链表
- 继续 free 掉相同大小的堆块,进入 fastbin,构造
fastbin double free
- 将 tcache bin 链表中的 chunk 全部 malloc 回来
- 再 malloc 一次,即可触发 stash,构造任意地址写
步骤一二之后,我们的空闲堆块链表结构应该是这样的:(画的有点丑)
然后经过步骤三之后,tcache 链表清空,再 malloc 时,会从先从 tcache bin 链表中寻找,发现并没有合适大小的堆块,然后到 fastbin 链表中找到合适大小的堆块,将 fastbin 链表中的所有堆块放进 tcache 中:
然后取走 p1,malloc 时伪造 fastbin p1 的 fd 指针,可以达到任意地址写的目的:
# 4.2 tcache bin 的一些特性
# 特性一
calloc 时不会去 tcache bin 中寻找。
# 例题 gyctf_2020_signin
这道题主要利用了两个知识点:
- 在分配 fastbin 中的 chunk 时若还有其他相同大小的 fastbin_chunk 则把它们全部放入 tcache 中。(前面补充的知识点)
- calloc 不会分配 tcache 空闲链表中的堆块。
直接贴我的 exp 了,有兴趣的可以去 BUU 上做做:
1 | from pwn import * |
# 特性二
tcache 中的 chunk 不会合并
Ptmalloc2 中,为了整理内存碎片,通常会进行 unlink 操作,但在 tcache 中,chunk 之间不会合并。
# 例题 hitcon_2018_children_tcache
做这道题之前我还不知道 tcache 之中的 chunk 不会合并,于是将堆结构 构造成这样之后:
我就很纳闷为什么 free 掉 chunk2 不会触发 unlink 合并 chunk1。
后来查资料知道了 tcache 中的 chunk 不会合并。
那就只好合并两个 unsorted bin 了。
这道题有几个注意事项:
- 注意 free 之后 malloc 回来的堆块的 index,容易搞错。
- 有 OFF BY NULL 漏洞,但是因为 free 堆块时会用 memset 干扰,所以只能多次利用 OFF BY NULL 将 pre_size 写对。
- 主要使用夹心饼攻击,用两个 unsorted bin 夹一个较小的 chunk,构造 chunk overlapping
我的 exp:
1 | from pwn import * |
# 4.3 打 tcache_perthread_struct 结构体
tcache_perthread_struct
结构体里有着
①Tcache 的 next 指针
②空闲堆块数。
控制该结构体即可:
- malloc 到任意地址
- 改变空闲堆块数,free 可得到 unsortedbin
# 4.4 tcache bin 下标
具体可参照 glibc 源码,是将 Tcache bin 下标看作无符号整数的。
因此我们可以构造出 circle list 后,add 三次,此时 tcache bin 下标变为负数。
此时由于下标是无符号整数,转换过来会得到一个很大的数,自然大于 7,因此此时再 free 堆块即可进入 unsortedbin 范围。