【学习记录】Week12(三):House of Corrosion——global_max_fast 的越界狂飙

【学习记录】Week12(三):House of Corrosion——global_max_fast 的越界狂飙

写在前面:在上一篇中,我们学习了 House of Pig,见识了如何利用 Tcache 和 FSOP 结合来劫持控制流。但无论是 Pig 还是 Apple,往往都需要解决一个前置问题:如何将伪造的堆地址写入_IO_list_all或其他 libc 全局指针中?在 glibc 2.29+ 中,Unsorted Bin Attack 被封堵,Largebin Attack 虽然可用但条件苛刻。今天,我们将介绍一种极其暴力且强大的技术——House of Corrosion。它通过腐蚀 glibc 的全局变量global_max_fast,打破 fastbin 的大小限制,利用数组越界实现大范围的任意地址写堆地址,是现代 FSOP 攻击的完美“弹药装填机”。

📑 目录

  1. 核心原理:global_max_fast与 Fastbin 数组越界
  2. 数学推导:如何精准计算越界偏移
  3. 利用条件与流程:从腐蚀到劫持
  4. 实战场景:将堆地址写入_IO_list_all
  5. 总结与下篇预告

1. 核心原理:global_max_fast与 Fastbin 数组越界

在 glibc 中,global_max_fast是一个全局变量,它定义了 Fastbin 能够管理的最大 chunk 大小(在 64 位默认环境下,通常是0x80,即 0x20 到 0x80 大小的 chunk 进 fastbin)。

当执行free(chunk)时,如果chunk->size <= global_max_fast,glibc 会将其放入 fastbin 链表。Fastbin 链表是一个数组fastbinsY[],位于main_arena结构体的起始部分。放入链表的操作本质上是:

idx = fastbin_index(chunk_size); // 计算索引 chunk->fd = fastbinsY[idx]; // 链表头插 fastbinsY[idx] = chunk; // 更新链表头

House of Corrosion 的核心思想
如果利用漏洞(如 Tcache Poisoning 或 Unsorted Bin Attack 的变体)将global_max_fast改写为一个极大的值(例如0xFFFF或一个很大的 libc 地址),那么 glibc 会认为所有大小的 chunk 都是 fastbin
此时,如果我们伪造一个大小为0x300(或任意巨大大小)的 chunk 并释放它,glibc 会计算出它的索引idx,然后执行fastbinsY[idx] = chunk
由于原本的fastbinsY数组并没有这么大,这个巨大的idx会导致数组越界写入
我们释放的 chunk 的地址,会被写入到main_arena之后很远的内存中。如果我们精心计算chunk_size,让越界的&fastbinsY[idx]正好落在_IO_list_allstdouttcache_perthread_struct等关键全局变量上,就能实现任意地址写堆地址

2. 数学推导:如何精准计算越界偏移

这是 House of Corrosion 最核心的部分。假设目标是将堆块chunk_addr写入目标地址target_addr

步骤 1:确定 fastbinsY 基址
在 64 位系统中,fastbinsY位于main_arena的头部偏移0x10处(因为main_arena前面有mutexflags各 4 字节,然后是fastbinsY[10])。
我们可以通过泄露的 Libc 地址计算出fastbinsY_addr = libc_base + main_arena_offset + 0x10

步骤 2:计算索引 idx
我们希望&fastbinsY[idx] == target_addr
因为fastbinsY是一个mfastbinptr数组(指针数组,每个元素 8 字节)。
所以:
target_addr = fastbinsY_addr + idx * 8
推导出:
idx = (target_addr - fastbinsY_addr) / 8

步骤 3:计算伪造的 chunk_size
在 glibc 中,fastbin 索引计算公式为(64位):
idx = (chunk_size >> 4) - 2
反推chunk_size
chunk_size = (idx + 2) << 4

总结公式
fake_chunk_size = ((target_addr - fastbinsY_addr) / 8 + 2) << 4

只要我们将一个 chunk 的 size 字段伪造为fake_chunk_size并释放它,它的地址就会被写入到target_addr

3. 利用条件与流程

3.1 利用条件

  1. 写原语:能够修改global_max_fast的值(通常利用任意地址写,将其改为极大值,如0xFFFF)。或者在某些特殊场景下,利用漏洞直接修改main_arena附近的内存。
  2. 堆地址泄露:需要知道释放的 chunk 的地址。
  3. Libc 地址泄露:用于计算fastbinsY_addrtarget_addr
  4. 可控的 chunk:拥有一个 chunk,可以修改其 size 字段为fake_chunk_size,并且该 chunk 的前向指针(fd,即 chunk 的 user_data 起始处)可以控制(通常作为链表节点写入)。

3.2 利用流程图

flowchart TD A[1. 泄露 Libc 地址和堆地址] --> B[2. 利用任意地址写<br>修改 global_max_fast 为极大值] B --> C[3. 计算目标地址 target_addr<br>如 _IO_list_all] C --> D[4. 数学推导 fake_chunk_size<br>使得 fastbinsY[idx] == target_addr] D --> E[5. 修改堆上某 chunk 的 size 为 fake_chunk_size] E --> F[6. 释放该 chunk] F --> G[7. 发生数组越界写<br>chunk_addr 被写入 target_addr] G --> H[8. 后续配合 FSOP 实施控制流劫持]

4. 实战场景:将堆地址写入_IO_list_all

在 glibc 2.29+ 中,由于 Unsorted Bin Attack 失效,我们很难直接修改_IO_list_all。House of Corrosion 成为了首选。

场景演示
假设我们要将堆块chunk_A写入_IO_list_all

  1. 修改 global_max_fast:利用之前的漏洞(如 House of Botcake 获取的 Tcache Poisoning),分配到global_max_fast,写入0xFFFFFFFF
  2. 计算 size
    • target_addr = libc_base + libc.sym['_IO_list_all']
    • fastbinsY_addr = libc_base + libc.sym['main_arena'] + 0x10(具体偏移视 glibc 版本而定)
    • idx = (target_addr - fastbinsY_addr) / 8
    • fake_size = (idx + 2) * 16
  3. 伪造 chunk:在chunk_A处,修改其 size 字段为fake_size。同时,在chunk_A的 user_data 处(即 fd 指针位置)写入0(防止脏数据导致链表损坏,或者写入下一个伪造的 IO_FILE 地址)。
  4. 释放 chunk_A:调用free(chunk_A)。此时chunk_A的地址被写入_IO_list_all
  5. 后续利用:由于_IO_list_all现在指向chunk_A,我们可以继续在chunk_A上布置伪造的IO_FILE_plus结构体(House of Pig 或 House of Apple)。当程序调用exit时,就会遍历到我们伪造的 FILE 结构体,触发控制流劫持。

注意事项与限制

  • 对齐要求:计算出的fake_size必须符合内存对齐(在 64 位下必须是 0x10 的倍数)。公式(idx + 2) << 4天然保证了这一点。
  • size 标志位:伪造 size 时,记得加上PREV_INUSE位(最低位为 1),防止触发不必要的合并检查。
  • fastbin 链表污染:由于越界写入,fastbinsY数组中对应位置被写入了堆地址。如果后续 glibc 试图从这些被污染的索引处分配内存,可能会引发崩溃。因此在完成地址写入后,应尽量避免触发相关大小的malloc,直接进入 FSOP 触发阶段。

5. 总结与下篇预告

5.1 核心知识点总结

  1. 本质:通过修改global_max_fast打破大小限制,利用 Fastbin 释放时的数组索引机制,实现大范围越界写。
  2. 效果:可以将任意堆块地址写入到 libc 数据段中fastbinsY之后的任意地址,是替代 Unsorted Bin Attack 进行全局指针劫持的利器。
  3. 应用:广泛用于高版本 glibc 中劫持_IO_list_allstdout等指针,为 FSOP 攻击(如 House of Pig/Apple)铺平道路。

5.2 下篇预告

在下一篇中,我们将转向 2023 年以来在顶级 CTF 赛事中高频出现的新技术:House of Tangerine
这是一种利用_IO_obstack_jumps虚表的新一代 FSOP 技术,它能在不依赖exit且面对更严格 vtable 检查的环境下,依然实现稳定的控制流劫持。

结语:House of Corrosion 就像是打开了 libc 内存布局的潘多拉魔盒。一旦global_max_fast失守,整个 libc 数据段就变成了攻击者可以随意涂鸦的画板。理解这种“越界思维”,是突破现代 glibc 防御体系的关键一环。