Tcache bin
CTF pwn题堆入门 – Fast bin
CTF pwn题堆入门 – Unsorted bin
CTF pwn题堆入门 – Large bin
序言
无奈于pwn题中与堆相关的东西实在是比较多,加上到了2021年的现在,ctf比赛中一来就是堆题,还都是新版本libc,对我这种新手中的新手实在不太友好,以此写下这个系列文章,记录自己学习堆漏洞利用过程中的点滴,同时也是个总结吧。结合自己做题的理解,将堆攻击常见的手段和方式按照一定的规律记录下来。
本文章系列将分成五大块,即tcache bin --> fast bin --> unsorted bin --> small bin --> large bin。
概述
tcache相对于其余四种bin,是出现的最晚的,在libc2-26中才加入,但现在的题目一般都基于更新版本的libc,tcache肯定是必不可少的一环,而且是最开始的那一环,所以这里先对tcache机制进行一个总结。
Tcache全名为Thread Local Caching,它为每个线程创建一个缓存,里面包含了一些小堆块。无须对arena上锁既可以使用,所以采用这种机制后分配算法有不错的性能提升。每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中。对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。
另外tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。
攻击方式
这里先说一下何时会用到tcache,在有了tcache机制后,无论分配还是释放,操作64位下0x20和0x410大小的chunk,tcache都是首当其冲,直到达到其每种bin的上限7为止。还有一种情况就是fast bin或者small bin返回了一个堆块,且tcache对应大小的bin中未满的话,那么该fast bin或者samll bin链中的堆块会被加入到对应tcache bin中直至其上限。这里值得一提的是,在tcache机制出现后,由于tcache机制首当其冲,很多属于fastbin的安全检查机制都没有被应用,因此在tcache下实现和fastbin一样的攻击原理,相对而言还会更简单些。下面介绍和tcache机制相关的攻击方式,将会按照攻击目的进行分类。
绕过Tcache
如果你对tcache并不熟悉,或者这道题并不需要利用tcache机制才能实现攻击,那么我们只需要简单的绕过tcache机制就行了。
#include <stdio.h>
#include <stdlib.h>
int main()
{
long long *ptr[7];
long long *a = malloc(0x80);
// 申请7个,释放7个,填满tcache bin[0x90]
for (int i=0; i<7; i++)
ptr[i] = malloc(0x80);
for (int i=0; i<7; i++)
free(ptr[i]);
// 这里再释放a,就会放入到unsorted bin中
free(a);
printf("libc addr is %llx\n", (long long)a[0]);
return 0;
}
如上图所示,展示了如何绕过tcache,使用unsorted bin的性质,打印存放于unsorted bin中的libc地址。tcache机制无非就是增加了一层缓存,如果我们还是想使用fast bin/unsorted bin等的性质,那么需要将对应的tcache bin填满,然后再执行相应的操作就可以了。这里需要注意的是,上面代码只是填满了[0x90]这一条tcache bin链表,如果想要自己申请释放的0x20大小的chunk进入到fast bin,那么同样需要先填满tcache bin[0x20]。
分配堆到目的地址
在堆漏洞利用中,我们很多时候需要将堆块分配到我们想要的地址上去,比如分配到保存堆指针的bss中,或者直接分配到栈空间实现程序流程控制,亦或者还是分配到堆中把堆块重新分割。在tcache中有以下攻击方式可以实现(不加说明则为64位机器,libc-2.26);
tcache poisoning
#include<stdio.h>
#include<stdlib.h>
int main()
{
// 在fck处分配堆块
long long fck;
printf("fck addr is %p\n", &fck);
long long * ptr = malloc(0x80);
printf("malloc ptr addr is %p\n", ptr);
free(ptr);
// 只需修改fd指针,申请的大小和当前tcache bin大小相同即可
ptr[0] = (long long)&fck;
malloc(0x80);
printf("the second malloc addr is %p\n", malloc(0x80));
return 0;
}
上图展示了攻击结果,这里同样注意tcache bin中的fd