glibc2.29 堆溢出tcache的利用方式及原理
Dancing With Heap 与堆共舞
二进制学习之旅
参考资料:
ctf-pwn https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/tcache_attack/#tcache-poisoning
glibc wiki https://sourceware.org/glibc/wiki/MallocInternals
glibc document http://www.gnu.org/software/libc/documentation.html
glibc2.29 source http://ftp.gnu.org/gnu/glibc/glibc-2.29.tar.gz
写在前面:
tcache 和 fastbin在free中并没有被清除inuse标志,所以他们被认为是处于使用状态,不会被合并,普通chunk被添加到unsorted bin,直到malloc时有机会使用它们,才会放入normal bin
libc经常更新,安全小白刚开始入门pwn了解堆溢出的资料有很多比较过时,于是查阅资料,写博客记录,分享知识给其他需要的同学。建议阅读libc最新源码以了解关于堆分配器的新机制,或是利用方式。
机器的测试结果,与利用方式均受机器字长的影响,关于malloc源码的概述和特殊结构体以及宏可以参考我的其他相关博客
tcache poisoning
tcache机制允许,将空闲的chunk以链表的形式缓存在线程各自tcache的bin中。下一次malloc时可以优先在tcache中寻找符合的chunk并提取出来。他缺少充分的安全检查,如果有机会构造内部chunk数据结构的特殊字段,我们可以有机会获得任意想要的地址。
###tcache_entry
typedef struct tcache_entry
{
//指向chunk当中的用户内存区,第一个变量是一个指针,指向tcache的下一个chunk,
//就是单链表访问
struct tcache_entry *next;
/* 这个字段是用来检测双重free释放的 */
struct tcache_perthread_struct *key;
} tcache_entry;
在malloc中对tcache几乎没有什么检查,如果能给有机会覆写tcache中的next字段并且将next字段,覆写为任意指定的的地址,malloc就会直接返回这个地址的指针给应用程序,之后便可以恶意利用这个地址的内容,写入也好,读取也好。
来稍微看下malloc中tcache流程
void *
__libc_malloc (size_t bytes)
{
.......
#if USE_TCACHE
//检查bytes合法性,并获取请求的标准chunk大小
checked_request2size (bytes, tbytes);
//获得tcache对应请求大小bin的索引
size_t tc_idx = csize2tidx (tbytes);
//如果没有初始化tcache,那么我们初始化tcache
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
//检查tcache给定索引是否有chunk满足条件,有就直接取出来
//索引大小不能超过index,并且tcache存在,并且tcache对应索引有块,初始化时不为0
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);//获取tcache
}
DIAG_POP_NEEDS_COMMENT;
#endif
.......
}
来看下获取tcache的宏
static __always_inline void *
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
//idx防止越界
assert (tc_idx < TCACHE_MAX_BINS);
//确实有块
assert (tcache->entries[tc_idx] > 0);
//取出第一个块
tcache->entries[tc_idx] = e->next;
//计数减少
--(tcache->counts[tc_idx]);
//key设置为null
e->key = NULL;
//返回chunk
return (void *) e;
}
这个宏只做了请求索引越界,确定bin中有块的检测,malloc中也做了请求大小检测,请求索引检测,没有别的操作,检测完后他就简单从tcache对应大小的entry中取出头部第一个chunk,返回他的地址给应用程序。
再来看一下free中是怎么处理添加chunk到tcache的
不同于malloc_libc_free对外的接口不直接处理tcache,而是在_int_free这个模块内部例程中处理有关tcache的操作
static void//arena chunk ptr lock state
_int_free (mstate av, mchunkptr p,