返回安全研究

glibc 全局函数指针与 pwn 利用链演化(2.31 → 2.40+)

Pwn · glibc Internals · 2026-08-13

上海见形信息科技有限公司 · 灵御(Aegis)安全实验室

调用路径与内存破坏

glibc 中的分配器 hook、I/O 虚表和退出回调,会在特定执行路径中读取函数指针。分析这类目标需要同时回答四个问题:指针保存在什么位置,调用由什么操作触发,调用前执行哪些检查,参数与相关结构是否满足要求。

内存中的可写指针要形成可利用路径,还需要应用漏洞提供读取或写入能力。目标地址、调用约定、指针保护、结构完整性与触发时机共同决定结果。

本文讨论的地址与结构均依赖目标架构、glibc 版本及发行版补丁。研究对象应同时保存 libc、动态链接器、调试符号与对应源代码,按实际二进制确认行为。

关键版本变化

版本相关变化研究时需要检查的内容
2.18提供 C++ TLS 析构相关内部实现__cxa_thread_atexit_impl 与线程局部链表
2.24libio 引入 vtable 验证主 vtable 的合法范围和具体调用路径
2.26引入 tcache线程缓存、计数与释放顺序
2.29tcache double-free 检测等检查增强key 检测、链表一致性与 top size 检查
2.32引入 Safe-Linkingtcache、fastbin 单向链表的编码指针与地址条件
2.34分配器 hook 从正常分配路径退出API、兼容符号与调试 DSO 的区别
2.35–2.40+分配器、libio、退出处理与加载器继续演进对应版本的源代码、符号、调用顺序与发行版修改

glibc 2.34 的 NEWS 说明,__malloc_hook、__realloc_hook、__memalign_hook 和 __free_hook 已从 API 移除,兼容符号继续服务旧程序;正常 glibc 功能不再依赖这些 hook。libc_malloc_debug.so 在相关版本中提供过兼容调试支持。glibc 2.34 NEWS

分配器 hook 的调用条件

旧版分配器在分配或释放路径中检查 hook,存在注册回调时执行对应函数。研究 __free_hook 需要检查 __libc_free 的具体实现、回调签名和传入参数。

一个符号仍能从二进制或兼容接口中查询到,不能据此推断正常执行路径会调用它。分析 2.34 以后的目标时,需要沿调用路径确认是否存在读取和间接调用。

这项变化把研究重点推进到仍会读取复杂结构的路径,包括 FILE、退出回调、TLS 析构与动态链接器对象。

FSOP:FILE 结构与虚表

FILE、虚表和全局链表

_IO_FILE_plus 将 FILE 状态与虚表指针连接起来。_IO_jump_t 描述 __overflow、__underflow、__finish 等操作。标准流对应 _IO_2_1_stdin_、_IO_2_1_stdout_ 与 _IO_2_1_stderr_,_IO_list_all 关联多个流对象。

FSOP(File Stream Oriented Programming)分析的重点是:程序怎样进入流处理路径,哪些字段影响分支,以及最终间接调用如何取得目标与参数。

正常退出时的刷新路径

_IO_flush_all_lockp 会检查流的状态,并在满足条件时执行刷新操作。正常 exit 的清理过程是需要研究的触发位置之一。研究者应从具体版本的调用关系确认入口,检查写入位置、模式、锁与宽字符数据。

abort、断言失败与普通退出具有各自的实现路径,触发器分析应以目标版本的函数实现为依据。

vtable 校验与内部回调

libio 会验证主 vtable 的地址范围。使用合法表后,后续函数仍可能访问宽字符数据、缓冲区或其他内部回调。分析 House of Orange、Apple、Kiwi、Obstack 等命名技术时,应分别检查所依赖的字段、调用顺序和版本条件。

_IO_obstack_jumps 相关路径需要结合 obstack 对象、chunkfun 等回调与流状态研究。主 vtable 的合法性与后续回调的可控性是两个独立检查位置。

退出处理:__exit_funcs

进程退出时,__run_exit_handlers 按回调类型执行已登记的处理函数。内部结构包含回调类型、联合体、用户参数和关联对象。

exit_function_list 保存 next、idx 与 fns[32]。idx 属于链表节点。exit_function 的 flavor 决定联合体的解释方式,相关函数签名包括:

void (*at)(void);
void (*fn)(int status, void *arg);
void (*fn)(void *arg, int status);

这三种形式分别对应不同回调类型。分析时应同时确认类型、参数顺序、指针编码和链表状态。相关定义可在 stdlib/exit.h 中查阅。

退出回调中的函数指针会使用 pointer guard 相关机制。能修改链表头、能控制函数指针的编码值、能提供有效参数,属于需要分别证明的条件。

TLS 析构:tls_dtor_list

tls_dtor_list 是线程局部链表。__cxa_thread_atexit_impl 注册回调时对函数指针执行 PTR_MANGLE,__call_tls_dtors 调用前执行 PTR_DEMANGLE。

节点包含函数、对象、关联的 link_map 和下一个节点。其回调类型为:

typedef void (*dtor_func)(void *);

调用以 func(cur->obj) 形式进行。实现还会访问 cur->map->l_tls_dtor_count,并释放当前节点。因此,节点的对象指针、关联对象与内存状态都参与后续执行。完整路径见 stdlib/cxa_thread_atexit_impl.c。

研究这一目标时,需要确认线程局部数据位置、guard 的取得条件、节点生命周期、析构触发时机和 map 的有效性。多个线程各自维护的状态也需要分别考虑。

动态链接器与其他回调

_dl_fini、link_map 与 _rtld_global

动态链接器在进程结束阶段处理已加载对象与 fini 回调。_rtld_global 保存加载器全局状态,相关结构在版本之间会变化。

分析需要检查 link_map 的有效性、加载命名空间、析构次序、动态段与 fini 数组,以及具体字段在目标二进制中是否存在、是否可写。旧版本中的 _dl_open_hook 或锁回调路径,需要在目标版本中重新确认。

printf 扩展回调

__printf_function_table 参与自定义格式处理。其回调签名使用 FILE、格式信息与参数数组:

int function(FILE *s, const struct printf_info *info,
             const void *const *args);

实际调用必须满足格式解析条件与对应参数类型。研究 printf 回调时,应检查参数注册、格式字符索引、参数信息表与调用函数。

错误处理函数

__stack_chk_fail、__fortify_fail 和 __libc_message 是执行路径中的函数。研究其相关间接调用时,需要区分函数符号、GOT 项、内部数据与真正的可写回调,并检查 RELRO、链接方式与目标版本实现。

堆写入能力与目标条件

Safe-Linking

Safe-Linking 用指针保存位置参与单向链表指针编码。在采用相应实现的版本中,PROTECT_PTR 的关系可以表示为:

protected_ptr = (position >> 12) ^ pointer;

position 是保存该指针的位置。检查相关利用条件时,需要确认目标地址、对齐要求、链表计数和读取能力。实现见 malloc/malloc.c。

tcache、unsortedbin 与 largebin

  • tcache poisoning 需要结合 next 编码、计数、地址对齐和释放检查分析。
  • unsortedbin 的写入路径受链表一致性与大小等检查约束。
  • largebin 操作涉及 fd_nextsize、bk_nextsize 与对应完整性检查,能够写入的值和时机由具体路径决定。
  • House of Botcake 等组合技术依赖对象重叠、释放顺序与目标版本分配行为。
  • top chunk 相关路径需要确认 size 与 arena 状态检查,以及进入异常处理后的真实调用关系。

写入原语应独立描述可写地址、值、长度、次数与前置条件,再检查它能否满足目标对象的全部要求。

版本研究记录表

候选目标必须确认的条件主要源代码位置
分配器 hook正常路径是否读取 hook,调试兼容功能是否启用malloc/malloc.c、NEWS
FILE 与虚表表地址、流状态、锁、宽字符数据与调用参数libio/libioP.h、libio/genops.c、libio/vtables.c
__exit_funcs回调类型、编码函数指针、参数与链表状态stdlib/exit.h、stdlib/exit.c
tls_dtor_listTLS 位置、guard、节点与 map 有效性stdlib/cxa_thread_atexit_impl.c
动态链接器字段存在性、可写性、对象完整性与析构次序elf/dl-fini.c、sysdeps/generic/ldsodefs.h
printf 扩展格式解析、注册表、参数数组和回调签名stdio-common/vfprintf-internal.c

同一版本号也可能带有发行版补丁。研究记录应保存二进制标识、架构、编译选项、源代码修订与调试验证结果,使每条结论能对应到具体环境。

参考资料