tcache七星剑法:序言——基础知识与版本变迁

tcache七星剑法:序言——基础知识与版本变迁

tcache,从glibc 2.27开始加入到glibc的堆管理器当中。作为一个独特的机制,tcache它以一己之力改变了堆题的格局。而随着版本变迁,tcache的安全问题逐步被修复,使得tcache的利用愈加困难。

之所以决定搞这个系列,目的就是总结一些有难度的tcache题目,借以加深对于tcache机制和相关利用技巧的理解

这个系列,名为“tcache七星剑法”。七星,自然是指一个tcache链表里面最多放七个堆块()

(这和Jmp.Cliff是个中二病晚期没有任何关系!!)

前置内容

这个系列并非是一篇关于tcache机制利用的新手教学,这篇文章在这里的作用是帮大家一起回顾一下相关的基础知识,并没有详细的调试讲解。如果你是刚刚入门堆攻击的新手,请移步ctf wiki等新手教学文章。

基础知识

由于现在题目都是64位的题目了,因此我们就以64位为例,32位不作专门讨论。

讲基础知识,我们先从2.27版本学起。

tcache机制涉及到两个结构体:

//glibc 2.27初期版本
typedef struct tcache_entry
{
    struct tcache_entry *next;
} tcache_entry;

typedef struct tcache_perthread_struct
{
    char counts[TCACHE_MAX_BINS];
    tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

tcache,以单链表的结构保存某一大小范围内被释放的堆块,这一结构类似于fastbin,tcache_entry相当于chunk,next指针就相当于fd。不过,next指针指向的是chunk的mem开头而非chunk的起始位置,相比fastbin的fd,它往下偏移了0x10字节。

tcache的每个单向链表有容量上限,上限为7

tcache_perthread_struct,这个结构有两部分:counts和entries。

counts部分为计数器,保存对应的tcache链表中当前的堆块数目。向tcache中放入堆块时会有上界7检查,从tcache取堆块时,最初2.27版本是没有下界0的检查的,也就是说即便counts中对应条目为0也可能取到堆块。

entries部分为指针数组,其结构类似于malloc_state的fastbinY结构。这里保存每一个链表中的最开头的指针,每个链表中堆块大小相同,且链表头在数组中按照从大到小顺序排列。tcache和fastbin一样也是采用FILO的策略,最后进入tcache_entry的会插在链表头部,取堆块时会从头部取出。

tcache_perthread_struct也是一个堆块,它是堆段里第一个堆块,位于堆段起点。它的大小是0x250,前0x10是堆块头,接下来0x40是counts,剩下的部分保存entries指针。tcache保存的chunk(不是mem)大小范围是0x20到0x410。

tcache_perthread_struct也是可以被free的(。◝ᴗ◜。)

从tcache中取出堆块的检查较为宽松,不同于fastbin需要对于size域做出严格的检查,从tcache取出堆块时只要保证这个地址是一个可写的区间就行了。

tcache在最开始的时候没有double free的检查,可以直接进行double free,这也导致了tcache最初的安全性极差。

在tcache的大小范围内,malloc/free堆块时,tcache有着更高的优先级,不过calloc会无视tcache去取堆块。

释放堆块时,必须先填满对应tcache,才能填入fastbin或unsorted bin

申请堆块时,必须先清空对应tcache。如果接下来在fastbin或者small bin中找到了对应堆块,就要把bin里面剩下的堆块倒进tcache里面(直到填满)。fastbin是从头部取,因此就沿着fd向后数七个堆块(fd为0会停止);而small bin是从尾部取,因此是沿着bk向后数七个堆块(遇到bin会截止),如果tcache被填满了,就会立刻终止填充。

版本变迁

tcache的相关利用技巧不再赘述,事实上,tcache题目往往千变万化,而网络上所有攻击技巧的总结也都是基本的招式,实战中见题拆题组合使用才是王道。

2.27后期

2.27后期,glibc设计者加入key字段,防止double free。因此tcache_entry的结构发生了变化

typedef struct tcache_entry
{
  struct tcache_entry *next;
  /* This field exists to detect double frees.  */
  struct tcache_perthread_struct *key;
} tcache_entry;

key一般会指向tcache_perthread_struct的mem,如果要free的chunk的key为这个值,就会触发检查,程序会遍历链表,检查这个tcache是不是已经在里面了。

这是一个优秀的剪枝,几乎不会影响性能,非double free情况下触发这个检查的概率极低,而double free又很容易被检查出来。

先前版本我们往往会通过直接double free的方式去构造成环来劫持fd,这个改进即堵死了这条路。

需要double free的时候,一定要先破坏key指针,使其成为一个其他的值

2.31

2.31的变化有两个,首先是tcache_perthread_struct的counts数组的元素类型由char变成了uint16_t

typedef struct tcache_perthread_struct
{
  uint16_t counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

这个操作的用意,大概是为了防止一些把堆块分配到tcache_perthread_struct上面,然后同时控制一对counts和entries吧......不过这只是猜测

此外,关于counts,在malloc时加入了一个新的检查,即要求counts[idx]必须要大于0。这就意味着我们伪造fd试图从tcache中分配堆块到非法地址时,一定要确保tcache的counts仍然大于0。

2.32

加入了指针异或防护,在存储next指针时,不再直接存储地址,而是存储( pos >> 12 ) ^ ptr这个结构,pos代表指针地址,而ptr代表在该next指针“想”指向的地址。

2.34

key域的值从tcache_perthread_struct变成了一个随机数。

开始的结束

接下来会有一些典的不能再典的题,我会一一总结下来,并且给它们起个好名字。。。

posted @ 2023-04-27 18:44  Jmp·Cliff  阅读(721)  评论(5编辑  收藏  举报