前言

本文记录了glibc2.27-glibc2.35关于tcache堆利用的一些变化

参考文章:

https://www.freebuf.com/articles/system/234219.html

https://www.roderickchan.cn/zh-cn/2023-03-01-analysis-of-glibc-heap-exploitation-in-high-version/

glibc2.27

glibc 2.27 版本中,引入了 tcache 机制,用于优化堆分配的性能。tcache 适用于 0x20 到 0x410 字节范围内的小块内存。释放堆块时,glibc 会优先检查对应大小的 tcache 是否已满,只有在 tcache 满载时,堆块才会被存入 fastbinunsorted bin 等其他 bin 结构。每个 tcache bin 最多可存储 7 个堆块。

glibc 2.27 及更早的版本中,tcache 机制缺乏安全性检查,允许 任意 double free。攻击者可以修改 tcache 中堆块的 fd 指针,该指针指向的是前一个堆块的 user data 段,并且 glibc 不会检查 size 字段,从而可以伪造指针,实现 任意地址分配。这一漏洞利用手法被称为 tcache poisoning(tcache 污染)。

glibc2.31

glibc 2.31 版本中,tcache 结构体 新增了 key 字段,用于增强安全性。

当一个堆块被释放并进入 tcache 时,glibc 会首先检查该堆块的 key 字段 是否等于当前线程 tcache_perthread_struct 结构体的地址:

  • 如果 key 字段不同,则允许将该堆块存入 tcache,并将 key 字段更新为 tcache 地址
  • 如果 key 字段相同,glibc 则会遍历 tcache 链表,检查其中是否已存在该堆块,从而防止 double free

因此,若要绕过此检查并 对同一 tcache 级别的堆块进行 double free,可以在释放前 手动修改 key 字段为其他值,再执行 free,从而成功绕过重复释放检测。

glibc2.35

  • tcachebin 堆指针异或加密(glibc-2.32 引入)
  • tcahebin 链的数量检查(glibc-2.33 引入)
  • fastbin 堆指针异或加密(glibc-2.32 引入)
  • 堆内存对齐检查(glibc-2.32 引入)
  • 移除__malloc_hook__free_hookglibc-2.34 引入)
  • 引入 tcache_key 作为 tcachekey 检查(glibc-2.34 引入)

tcachebin 堆指针异或加密

glibc 2.31 及更高版本中,tcache 机制针对 fd 指针 引入了新的保护措施,使其不再是简单的前一个堆块地址,而是经过以下处理:

  1. 取当前堆块的地址并 左移 12 位
  2. 将该值与原本应存入的 fd 指针值(前一个堆块的地址) 进行 XOR 运算,得到新的 fd 值

绕过方法

tcache 仅存有一个堆块 时,可以通过其 fd 指针 提取用于加密的 XOR 密钥

  • 读取 fd 指针值,然后左移 12 位,即可得到 堆地址
  • 该泄漏的堆地址可用于后续的 解密伪造 fd 指针

伪造 fd 指针 时:

  • 计算目标 fd 指向的地址,即伪造的下一个堆块地址。
  • 用此地址与 泄漏的 XOR 密钥 进行 XOR 计算,得到正确的加密 fd 值。
  • 将计算出的 伪造 fd 写入目标堆块,确保其符合 tcache 机制的检查。

注意:后续申请的堆块地址应尽量接近泄漏的堆块地址,避免因地址差距过大导致 XOR 计算的密钥变化,影响利用成功率。

tcache 链数量检查

从tcache中重新申请堆块时,会检查当前tcache的数量,如果数量为0,就不会再从tcache中申请。

在此之前,可以在tcache的第一个堆块的fd位置进行tcache poisoning,现在需要多申请至少一个,然后在第二个上改。

堆内存对齐检查

通过错位来申请堆块变成历史,申请的堆块必须对其0x10。

引入 tcache_key 作为 tcachekey 检查

tcache_key是一个随机的值,每次放入tcache的堆块的key字段都是随机值,无法通过泄漏tcache的key字段来泄漏堆地址。

移除__malloc_hook__free_hook

在2.34以前,可以通过向这两个钩子函数写入system地址或者one_gadget地址来实现getshell,现在成为了历史。

一般来说glibc2.35以后的getshell,可以通过篡改IO_FILE结构体来实现,也可以通过下面这种办法:

在Linux C中,environ是一个全局变量,它储存着系统的环境变量。

它储存在libc中

因此environ是沟通libc地址与栈地址的桥梁。

通过libc找到environ地址后,泄露environ地址处的值,可以得到环境变量地址,环境变量保存在栈中,通过偏移可以得到栈上任意变量的地址。

可以通过计算得到edit函数的返回地址,然后向该地址写入rop链从而实现getshell。

以下是我写的一篇题解

https://www.nssctf.cn/note/set/11600