Heap Exploitation-tcache
Heap Exploitation - tcache
tcache是glibc2.26(ubuntu17.10)之后引入的一种技术,目的是堆管理的性能,在检测机制上有些变化,也新增了一些利用方式。
TCache全名为Thread Local Caching,它会为每个线程创建一个缓存,里面包含一些小堆块,无需对arena上锁即可使用,提升了分配性能。
数据结构
glibc在编译时使用USE_TCACHE条件开启tcache机制,默认开启
|
每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk。在64位系统上以16字节递增,从24到1032字节。32位系统上以8字节递增,从12到512字节。tcache bin只用于存放非large的chunk。
tcache引入了两个新的结构体,tcache_entry和tcache_perthread_struct
typedef struct tcache_entry |
tcache_perthread_struct位于堆开头的位置,说明其本身也是一个堆块,大小为0x250。它是整个 tcache 的管理结构,一共有 TCACHE_MAX_BINS
个计数器和 TCACHE_MAX_BINS
项 。
tcache_entry其中包括数组entries用于放置64个bin的地址,数组counts则存放每个bin中的chunk数量,每个被放入bins的chunk都会在其用户数据中包含一个tcache_entry(即fd指针),链接了相同大小的空闲的下一个chunk的用户数据,类似于fast bins,从而构成单链表。需要注意的是这里的 next 指向 chunk 的 user data,而 fastbin 的 fd 指向 chunk 开头的地址。而且,tcache_entry 会复用空闲 chunk 的 user data 部分。
初始化操作
在_int_malloc中,第一次malloc时会进入到MAYBE_INIT_TCACHE (),其中 MAYBE_INIT_TCACHE () 在 tcache 为空(即第一次 malloc)时调用了 tcache_init()
初始化操作如下:
static void tcache_init(void) |
tcache的tcache_perthread_struct结构是由_int_malloc来分配并通过memset清零来进行初始化的。tcache_init()成功返回后,tcache_perthread_struct 就被成功建立了。
申请内存
向tcache放入chunk
_libc_free中并没有变化,int_free中有一些修改,在fastbin的操作之前执行,当大小符合要求,即为non-large chunk并且对应的bin还未满7个就调用tcache_put将该块放入tcache
static void |
调用_int_malloc时,有三处会触发将chunk放入tcache的操作
fast bin中返回了一个chunk,对应的fast bin中的其他chunk会被放进对应的tcache bin中,直到满或者fast bin中为空为止。需要注意的是,chunks在tcache bin中和fast bins中的顺序相反
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (pp = *fb) != NULL)
{
REMOVE_FB (fb, tc_victim, pp);
if (tc_victim != 0)
{
tcache_put (tc_victim, tc_idx);
}
}
}
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}small bins中的情况和fast bins中类似,双链表中的剩余chunk会被填充到tcache bin中,直到small bins空或者tcache bin满为止
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
tcache_put (tc_victim, tc_idx);
}
}
}在unsorted bin中,当用户所需的chunk大小与unsorted chunk一致,那么首先会将该chunk放入对应的tcache中并跳过该次循环,对于unsorted bin而言,与用户需求大小相等的块会被优先放入tcache中
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
从tcache取出chunk
在_int_malloc之前,当用户需要的chunk大小不为large并且tcache已经完成初始化,并且tcache中有相应的chunk块,调用tcache_get()从tcache取出chunk。注意取出chunk是在fast bins之前,也就是说,tcache是最高一级的缓存措施。
void * |
在binning code中,如果在tcache中放入的chunk到了上限就会直接返回最后一个chunk,默认情况下是没有限制的。
|
bining code结束后,如果没有直接返回,那么如果至少有一个符合要求的chunk被找到,则返回最后一个。
|
另外,tcache中的chunk不会被合并,prev_inuse始终不为0。
安全性分析
函数tcahce_put()和tcache_get()函数分别用于从单链表中放入和取出chunk。
tcache_get()
/* Caller must ensure that we know tc_idx is valid and there's |
从tcache中取出chunk的函数,几乎没有任何保护。
tcahe_put()
/* Caller must ensure that we know tc_idx is valid and there's room |
没有将P位置零并且几乎没有任何保护。
通过以上可以看出这两个函数都假设调用者对参数进行了有效性检查,但是由于tcache的操作在free和malloc都处于很靠前的位置,所以很多的有效性检查没有起到作用,这样做有效地提升了效率,但是安全性降低,反而使得堆利用的方法更多了。
tcache_get()函数中,assert (tcache->entries[tc_idx] > 0);
此处本意是检查tcahce bin中chunk的数量是否大于0,但是counts可以发生整数溢出变成负数。该问题在glibc2.28得到修复。
CVE-2017-17426
libc-2.26的tcache机制发现了安全漏洞,由于_libc_malloc()使用了request2size()来将请求大小转换为实际大小,而该函数没有进行整数溢出检查。所以如果请求一个很大的堆块(接近SIZE_MAX),那么就会导致整数溢出,从而导致malloc错误地返回tcache bin中的堆块。
|
$ gcc cve201717426.c |
使用glibc2.26时,第二次malloc返回了第一次free的堆块,使用glibc2.27时,返回NULL,问题已经被修复。
修复方法,使用cecksed_request2size()函数替换request2size()函数,实现对整数溢出的检查
二次释放检查
libc-2.28增加了对tache中二次释放的检查,方法是在tcache_entry结构中增加一哥标志key,用于表示chunk是否已经在tcache bin中
index 6d7a6a8..f730d7a 100644 (file) |
目前为止,只看到了在 free 操作的时候的 check ,似乎没有对 get 进行新的 check。
参考
CTF竞赛权威指南-pwn篇