堆入门5.3—Tcache

概念

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));
}

file

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

发表评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据