#本文根据wiki和hollk师傅的文章进行的学习#
Tcache dup:
tcache dup在原理上比较简单,利用的是tcache_put()函数没有进行任何安全检查的缺陷。
首先回顾一下tcache_put()函数的结构:
static void
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]);
}
可以发现在这个函数中没有对将要放进tcache的free chunk做任何的安全检查,在检查了tc_idx(也就是chunk在链表中的序号)后直接将其添加到tcache对应的链表之下。
由于对free chunk没有进行任何的安全性检查,甚至连fastbin中的double free检查都没有,所以我们可以通过篡改next指针等方法伪造循环链表多次调用分配同一个chunk(也就是double free和UAF的漏洞)
下面是一个tcache_dup的例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *a = malloc(8);
free(a);
free(a);
printf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));
return 0;
}
这是wiki上的例子,做了一些简化,但是主体流程是一样的。(注意glibc的版本不能超过2.27,在后面的版本中对tcache是加入了double free的检测的)
首先说一下这个程序的流程,它首先创建了一个指针a,并申请了一个8字节大小的chunk分配给它,之后double free了这个指针对应的chunk(因为指针a在第一次free后没有置空,所以可以直接进行double free),之后我们连续申请两次大小为8字节的chunk,可以看见系统分配给我们的chunk地址是相同的。
这里由于我没找到合适版本的glibc就不做实际演示了,这个手法现在的题目大部分情况下也用不上了。
Tcache house of spirit:
这种手法与fastbins中的house of spirit差不多,都是将一个伪造的chunk挂进链表中再将其分配出来完成对伪造chunk的那段内存的控制。
所以我们只要能伪造出一个size合适的堆块并将其挂进tcache的对应链表中就能将任意地址作为chunk释放。
下面使用wiki上的例子(做了一些简化)进行测试:
#include <stdio.h>
#include <stdlib.h>
int main()
{
malloc(1);
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region
printf("fake_chunk addr is %p\n", &fake_chunks[0]);
fake_chunks[1] = 0x40 // this is the size
a = &fake_chunks[2];
free(a);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);
return 0;
}
这个程序的流程是:首先创建一个1字节大小的堆块防止后续chunk与top chunk合并。然后定义了一个指针a和一个数组 fake_chunks,之后我们将这个将被作为伪造chunk的数组的第二个元素置为0x40(也就是伪造chunk的size位),再将这个数组的地址赋给a,在free掉a对应的地址空间后再申请一个0x30大小的chunk,最后观察这个chunk的地址。
首先将断点下载11行看一下这个fake_chunks的地址:
然后步进到完成对该数组的第二个元素完成修改后:
这里可以看见已经修改成功了,然后我们释放掉这个伪造的堆块:
可以看见这个地址已经被作为一个chunk进入到tcache中了,之后我们再申请一个0x30大小的chunk启用这段地址:
这里可以看见这个伪造chunk的内存段已经作为chunk被分配给我们了,这样我们也完成的一次tcache house of spirit的攻击了。