Tcache机制及漏洞利用方法

2019-09-14  本文已影响0人  Fish_o0O

0x00 写在前面

Tcache机制是在libc-2.26中引入的一个新的堆管理机制。掐指一算,libc-2.26发布距今应该也有一两年了。由于各种不可控因素,到近期才将其提上日程。

0x01 What's New

首先得介绍在libc-2.26中新引进的tcache_perthread_structtcache_entry两个结构体。

#define TCACHE_MAX_BINS 64
typedef struct tcache_perthread_struct{
    char counts[TCACHE_MAX_BINS];
    tcache_entry *entries[TCACHE_MAX_BINS];
}tcache_perthread_struct;
typedef struct tcache_entry{
    struct tcache_entry *next;
}tcache_entry;

从源码中不难看出,tcache_perthread_structTcache机制中起管理作用,在默认情况下,sizeof(tcache_perthread_struct) == 0x240,即可以对低于0x400大小的堆块进行管理。其中tcache_pthread_struct.counts[i]64bit系统中对应大小为8 * i堆块的Tcachebin的数量,最大可为7tcache_pthread_struct.tcache_entry[i]则指向该大小对应的第一个Tcachebinfd的位置。

  • 举个栗子
    为对这两个结构体有直观的印象,下面的例子中释放了2个大小为0x201个大小为0x20的堆块。
    堆块分布
    其中第一个0x250的堆块是在第一次申请内存时,会分配一个空间用于存放tcache_pthread_struct(0x240 + 堆头 == 0x250)
    tcache_pthread_struct
    上图中则展示了第一个堆块,即tcache_perthread_struct中存放的数据。
    图中红色方框内的数据,即对应结构体中的count,共0x40字节,每字节对应相应大小Tcachebin中的个数。如绿色方框中对应size0x20大小的Tcachebin中有2个空闲堆块,而黄色方框中则对应size0x30大小的Tcachebin中有1个空闲堆块。
    图中蓝色方框内的数据,即对应结构体中的tcache_entry,共0x200字节(0x40 * 8 == 0x200),每一个指针对应相应大小Tcachebin中第一个堆块的入口地址。如绿色箭头对应size0x20大小的Tcachebin的入口地址,二黄色箭头则对应size0x30大小的Tcachebin的入口地址。

0x02 What's Tcachebin

从宏观来看,Tcachebin的各项操作与Fastbin大同小异,如FILO(先进后出)的单循环链表、精确分配(不切割)、free后为防止合并后一个堆块的inuse位不置0等。
但在细节上仍存在些许差异,如Fastbinfd是指向链表中下一个堆块的堆头,而Tcachebinfd则是直接指向链表中下一个堆块的fd。除此之外,在从Tcachebin中申请回内存块时,并没有特定的代码去检验该内存块的大小是否与这条Tcachebin所管理的大小相吻合!
以上两点差异意味着在Tcachebin中利用类似Fastbin Attack的技巧时,不需要再去找到合适的地址伪造size位,不需要再去计算堆头到data区域的偏移,而是指哪儿打哪儿(fd伪造到哪里,之后写的就是哪里)。

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;
    ++(tcache->counts[tc_idx]);
}

总的来说,就是将free的堆块插入Tcachebin的前端,将fd指向前一个堆块,并将对应的tcache_entry指向当前堆块,再将count+1

tcache_get(size_t tc_idx)
{
    tcache_entry *e = tcache->entries[tc_idx];
    assert(tc_idx < TCACHE_MAX_BINS);
    assert(tcache->counts[tc_idx] > 0);
    tcache->entries[tc_idx] = e->next;
    --(tcache->counts[tc_idx]);
    return (void*)e;
}

若对应大小的Tcachebin为空,则会从对应大小的bin中去寻找(寻找顺序同之前版本)。在这种情况下,即Tcachebin未满时,却从Fastbin/Smallbin中取出堆块,则会将链上的其他堆块都链入Tcachebin中。其具体算法是首先将Fastbin/Smallbin中取出的堆块指针进行保存,并判断该大小对应的Tcachebin是否未满,若未满则将其之后的堆块按照Fastbin/Smallbin的分配顺序将堆块链入Tcachebin中,直到对应大小的Tcachebin放满或Fastbin/Smallbin的链为空,最后将之前取出的堆块指针返回给用户使用。由于是按照Fastbin/Smallbin的分配顺序将堆块放入Tcachebin中,因此不难判断,最从Tcachebin中申请的堆块顺序是与正常从Fastbin/Smallbin中申请堆块顺序时反向的。

  • 关于将Fastbin/Smallbin中堆块放入Tcachebin中的操作
    针对这个Tcachebin未满,放入堆块的操作,之前在网上看到大部分的描述都是先把大小相同的堆块从Fastbin/Smallbin中放入Tcachebin后再进行分配,但在进行调试的时候发现跟这个描述略有出入,不是很理解,于是通过阅读源码可以非常清楚地看到整个操作的逻辑。
    Fastbin:3594行保存了Fastbin中即将分配的指针,3608-3631行将其后的堆块按Fastbin的申请顺序(FILO)放入Tcachebin3632行将第3594行保存的指针返回给用户使用。
    Smallbin:3652行保存了Smallbin中即将分配的指针,3664-3689行将其后的堆块按Smallbin的申请顺序(FIFO)放入Tcachebin3690行将第3652行保存的指针放回给用户使用。
    下面以Fastbin为例,贴出简化后的代码:
if(nb <= get_max_fast())                                            //申请范围在Fastbin之内
{
   idx = fastbin_index(nb);                                       //找到对应大小的Fastbin索引
   mfastbinptr *fb = &fastbin(av , idx);                 //通过索引找到入口
   victim = *fb;                                                         //注意这里,保存了第一个即将分配堆块的指针
   if(victim != NULL)                                                //如果Fastbin中有堆块
   {
       *fb = victim->fd;                                            //此时fb为即将分配堆块的fd
       ............
#if USE_TCACHE
       ............
       while(tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = *fb) != NULL)
       {                                                                      //直到Tcachebin放满,或Fastbin取空为止
           *fb = tc_victim->fd;                                  //循环遍历
           ............
           tcache_put(tc_victim , tc_idx);                //将该堆块放入Tcachebin中
       }
#endif
       void *p = chunk2mem(victim);                  //注意这里的victim为最开始保存的即将分配的堆块、
       return p;                                                      //返回给用户使用
   }
}

0x03 How To Pwn

由于这方面的题目接触的不算特别多,目前就只从大佬的博客上了解了一些基础的利用方法,可能以后会遇到再进行更详细和深入的归纳总结吧。

上一篇下一篇

猜你喜欢

热点阅读