https://hefangyu3313.github.io/2025/11/18/tcache-attack/
tcache介绍
- 1.每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中,当申请大于7个就会放入fastbins,但是tcache优先级高于fastbins,tcache先被申请出来
- 2.对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。
- tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。
tcache关键代码(2.35)
相关结构和基础操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
uintptr_t key;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];//每条链的chuunk数
tcache_entry *entries[TCACHE_MAX_BINS];//每条链的入口
} tcache_perthread_struct;
|
tcache_perthread_struct是tcache的管理结构,counts里存放着每个tcache链上的chunk个数,entries是tcache链表的入口,与fastbinY差不多
tcache_perthread_struct分配在堆的头部,比较容易控制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/* Caller must ensure that we know tc_idx is valid and there's room
for more chunks. */
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache_key; //防止double free的key
e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); //将当前bin头部chunk的指针加密后赋给next
tcache->entries[tc_idx] = e; //将这个chunk存进相应索引的bin链表头部(更新bin头部)
++(tcache->counts[tc_idx]);
}
/* Caller must ensure that we know tc_idx is valid and there's
available chunks to remove. */
static __always_inline void *
tcache_get (size_t tc_idx) //从tcachebin取出chunk
{
tcache_entry *e = tcache->entries[tc_idx]; //根据计算好的索引取出链表头部的chunk
if (__glibc_unlikely (!aligned_OK (e))) //检查chunk对齐
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next); //解密并修改头部
--(tcache->counts[tc_idx]);
e->key = 0; //key位置置空
return (void *) e;
}
|
主要存在两个安全机制,一个是key来检测double free一个是safe linking(就是在tcache_put和tcache_get中对next指针内容的加密和解密操作)
double free check
2.27后增加
tcache_put在放入chunk后,加入了e->key=tcache
在free时会检测key是否等于tcache
tcache_get在拿出chunk后将key置为空
safe linking
glibc2.32新增的防御机制
其核心思想是将指针的地址右移12位再和指针本身异或,所以在修改next指针时,需要知道对应的堆地址