glibc2.27-2.35 tcache堆利用
前言
本文记录了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 满载时,堆块才会被存入 fastbin 或 unsorted 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_hook(glibc-2.34引入) - 引入
tcache_key作为tcache的key检查(glibc-2.34引入)
tcachebin 堆指针异或加密
在 glibc 2.31 及更高版本中,tcache 机制针对 fd 指针 引入了新的保护措施,使其不再是简单的前一个堆块地址,而是经过以下处理:
- 取当前堆块的地址并 左移 12 位。
- 将该值与原本应存入的 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 作为 tcache 的 key 检查
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。
以下是我写的一篇题解










