概念
tcache是libc2.26之后引进的一种新机制,类似 Fastbin ,每条链可以存7个 chunk ,free chunk 会先进入 tcache 链,满了之后才进入其它 bin ,malloc chunk 同样先在 tcache 里检索。
typedef struct tcache_entry
{
struct tcache_entry *next; #单链表结构的开头
} tcache_entry;
#tcache_perthread_struct 用来管理tcache链表
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS]; #数组对应64个tcache链表,每一个元素指对应tcache链表的元素个数
tcache_entry *entries[TCACHE_MAX_BINS]; #指针数组,每一个指针指向对应tcache_entry结构体
} tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
区别在于:
- tcache 和 fastbin 都是通过 chunk 的 fd 位置来作为链表的指针
- tcache 中的链表指针指向的上一个 free chunk 的 fd 字段,fastbin 中的链表指针指向的是上一个 free chunk 的 prev_size 字段
使用
两个函数
#tcache_put chunk 进入 tcache;在 _int_free 前调用
static void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e; #存入 chunk
++(tcache->counts[tc_idx]); #数量 +1
}
#tcache_get 从 tcache 中申请出 chunk;在 _int_malloc 前调用
static void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next; #把上一个 free 的 chunk 放进去,同时意味着当前 chunk 退出该链。
--(tcache->counts[tc_idx]); #数量 -1
return (void *) e;
}
tcache 同样也是单链,从 tcache_get 函数可以看出,仅仅检查 tcache->entries[tc_idx] = e->next,不检查size,因此利用起来更容易。
补充
在内存分配的 malloc 函数中有多处,会将内存块移入 tcache 中:
-
申请的内存块符合 fastbin 大小时并且在 fastbin 内找到可用的空闲块时,会把该 fastbin 链上的其他内存块放入 tcache 中补足七个。
-
申请的内存块符合 smallbin 大小时并且在 smallbin 内找到可用的空闲块时,会把该 smallbin 链上的其他内存块放入tcache 中,同上。
-
当在 unsorted bin 链上循环处理时,当找到大小合适的链时,并不直接返回,而是先放到 tcache 中,继续处理。
tcache_perthread_struct结构,一般是在heapbase+0x10(0x8)的位置。对应tcache的数目是char类型。
常见利用方式
Double Free ——tcache dup
原来的double free利用,我们需要构成*a->b->a这种形式的free'd链,而在tcache中,由于不会检查top,直接可以构成a->a这种free'd链。利用更方便。
原来的double free利用,我们需要绕过检测:
free(chunk1)
free(chunk2)
free(chunk1)
而在tcache中,不会检查top,可以直接 free 两次:
free(chunk1)
free(chunk1)
Tcahe_house_of_spirit
与原来的house_of_spirit类似。free掉伪造的chunk,再次malloc获得可操作的地址。但是同样的,这里更简单,free的时候不会对size做前后堆块的安全检查,所以只需要size满足对齐就可以成功free掉伪造的chunk。
#借用一下别人的例子
#include <stdio.h>
#include <stdlib.h>
void main()
{
malloc(1);
unsigned long long *a;
unsigned long long fake_chunks[10];
fprintf(stderr, "fake_chunks[1] 在 %p\n", &fake_chunks[1]);
fprintf(stderr, "fake_chunks[1] 改成 0x40 \n");
fake_chunks[1] = 0x40;#写入size
fprintf(stderr, "把 fake_chunks[2] 的地址赋给 a, %p.\n", &fake_chunks[2]);
a = &fake_chunks[2];
fprintf(stderr, "free 掉 a\n");
free(a);
fprintf(stderr, "再去 malloc(0x30),在可以看到申请来的结果在: %p\n", malloc(0x30));
}
tcache_overlapping_chunks
和 house of spirit 是一个原因,由于 size 的不安全检查,我们可以修改将被 free 的 chunk 的 size 改为一个较大的值(将别的 chunk 包含进来),再次分配就会得到一个包含了另一个 chunk 的大 chunk ,就能改写 free chunk 的内容了。同样的道理,也可以改写 pre_size 向前 overlapping。
tcache_poisoning
通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。
需注意,tcache dup和poisoning其实都要求可以use after free,也就是free并没有置null。
绕过
这个可以直接申请0x408以上的,超出 tcache 的范围到 unsortedbin
引用
https://blog.csdn.net/qq_38154820/article/details/106330125
https://www.jianshu.com/p/3ef98e86a913
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/tcache-attack/#0x01-tcache-overview