Tcache介绍
tcache是libc2.26之后引入的一种新机制,与fastbin类似,是一个LIFO的单链表,每条链上最多有7个chunk,free的时候先放入tcache,tcache满了再放入fastbin,unsorted bin,libc2.29之前不会检查double free。malloc的时候先去tcache找,其相关结构体如下:
1 | typedef struct tcache_entry |
tcache_perthread_struct
结构体是用来管理tcache链表的。TCACHE_MAX_BINS值是64,表示有64个链表,counts中一个元素表示对应链表中有多少个chunk,entries中的元素就是tcache链表。tcachebin和fastbin都是通过chunk的fd字段来作为链表的指针,tcachebin中的链表指针指向的下一个chunk的fd字段,fastbin中的链表指针指向的是下一个chunk的prev_size字段
Tcache利用
Tcache的利用主要分为以下几种:
- tcache poisoning
简单来说就是覆盖tcache_entry结构体中的next域,不经过任何伪造chunk即可分配到另外地址 - tcache dup
类似于fastbin的double free,就是多次释放同一个tcache,形成环状链表 - tcache perthread corruption
控制tcache_perthread_struct结构体 - tcache house of spirit
free内存后,使得栈上的一块地址进入tcache链表,这样再次分配的时候就能把这块地址分配出来
LCTF2018 PWN easy_heap
这是一道note类型的题目,有mymalloc、myfree、myputs功能,最多分配10个chunk,mymalloc中的read_content子函数中有一个off-by-one漏洞,NULL单字节溢出:
1 | unsigned __int64 __fastcall read_content(_BYTE *buf, int len) |
利用思路:先free填充满tcache,再free三个chunk,会进入unsorted bin,并存在合并操作,最后free的chunk的prev_size为0x200,可以用于后续的overlapping。(前三个for循环)不能直接用malloc构造下一个堆块的prev_size,因为0x200转为字节’\x00\x02\x00\x00\x00\x00\x00\x00’会被’\x00’截断。前三个for循环之后bins的结构是这样的:
tcachebin:满
unsortbin:A
1 | +-----+ 低地址 |
A的fd和bk都指向main_arena+96,C的prev_size为0x200,第四个for循环,先分配了tcache里的chunk,再分配A、B、C,第五个for循环先向tcache填充了6个chunk,接着将B填入tcache,A填入unsortbin,分配B并单字节溢出C的prev_inuse位,将tcache填满,再free C到unsortbin发生overlapping,这样B既在分配列表中,也在unsortbin中。bins结构是这样的:
tcachebin:满
unsortbin:A
1 | +-----+ 低地址 |
再次分配8个chunk,先将tcache中的分配完,再分配一个A,bins结构是这样的:
tcachebin:空
unsortbin:B
1 | +-----+ 低地址 |
此时B的fd和bk指向main_arena+96,而且B在分配列表中,打印B的值即可泄露main_arena+96的值,main_arena在libc中的偏移存放在libc文件的malloc_trim()函数中:
1 | __int64 __fastcall malloc_trim(__int64 a1) |
到这就可以计算出libc的偏移,将B分配出来,在分配列表中就有两个B,可以进行tcache dup,再分配B将其fd改为__free_hook的got地址,再分配B,其fd就在tcache中了,也就是__free_hook的got地址在tcache中了,将其分配出来并在其中填入one_gadget,最后调用free就可以触发one_gadget了。
完整exp:
1 | from pwn import * |