深入glibc源码图解_dl_fixup如何解析动态链接函数在Linux系统的动态链接机制中_dl_fixup函数扮演着至关重要的角色。本文将带您深入glibc-2.23源码通过图解方式剖析这个关键函数的运作原理并揭示其与ret2dlresolve攻击技术的内在联系。1. 动态链接基础架构动态链接是现代操作系统实现代码共享的核心机制。当一个程序调用动态链接库中的函数时系统不会立即加载该函数的实际代码而是采用延迟绑定(Lazy Binding)技术在函数第一次被调用时才进行解析和加载。这种机制依赖于几个关键数据结构PLT(Procedure Linkage Table)程序链接表包含跳转到GOT的指令GOT(Global Offset Table)全局偏移表存储函数地址.rel.plt重定位表记录需要重定位的函数信息.dynsym动态符号表存储符号信息.dynstr动态字符串表存储函数名等字符串当程序第一次调用动态链接函数时会经历以下流程跳转到PLT表对应条目PLT条目跳转到GOT中存储的地址第一次调用时GOT指向PLT中的下一条指令该指令将重定位参数压栈并跳转到PLT[0]PLT[0]将link_map压栈并跳转到_dl_runtime_resolve2. _dl_fixup函数源码解析_dl_fixup是动态链接解析的核心函数位于glibc的dl-runtime.c文件中。让我们逐段分析其实现_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg) { // 获取符号表地址 const ElfW(Sym) *const symtab (const void *) D_PTR (l, l_info[DT_SYMTAB]); // 获取字符串表地址 const char *strtab (const void *) D_PTR (l, l_info[DT_STRTAB]); // 计算重定位项地址 const PLTREL *const reloc (const void *) (D_PTR (l, l_info[DT_JMPREL]) reloc_offset); // 获取符号表项 const ElfW(Sym) *sym symtab[ELFW(R_SYM) (reloc-r_info)]; // 检查重定位类型 assert (ELFW(R_TYPE)(reloc-r_info) ELF_MACHINE_JMP_SLOT);这段代码展示了_dl_fixup如何通过link_map结构访问各种关键数据结构。其中l_info数组存储了动态段的各种信息DT_JMPREL指向.rel.plt段DT_SYMTAB指向.dynsym段DT_STRTAB指向.dynstr段函数接着进行符号查找if (__builtin_expect (ELFW(ST_VISIBILITY) (sym-st_other), 0) 0) { // 复杂查找路径 result _dl_lookup_symbol_x (strtab sym-st_name, l, sym, l-l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); value DL_FIXUP_MAKE_VALUE (result, sym ? (LOOKUP_VALUE_ADDRESS (result) sym-st_value) : 0); } else { // 简化查找路径 value DL_FIXUP_MAKE_VALUE (l, l-l_addr sym-st_value); result l; }最后函数将解析得到的地址写入GOT表// 写入GOT表 return elf_machine_fixup_plt (l, result, reloc, rel_addr, value);3. 关键数据结构详解理解_dl_fixup需要熟悉几个核心数据结构3.1 Elf32_Rel结构体32位系统下的重定位项结构typedef struct { Elf32_Addr r_offset; // 需要重定位的地址 Elf32_Word r_info; // 符号索引和重定位类型 } Elf32_Rel;r_offset指向GOT表中需要修改的位置r_info低8位表示重定位类型高24位表示符号表索引3.2 Elf32_Sym结构体符号表项结构typedef struct { Elf32_Word st_name; // 符号名在字符串表中的偏移 Elf32_Addr st_value; // 符号值 Elf32_Word st_size; // 符号大小 unsigned char st_info;// 符号类型和绑定属性 unsigned char st_other;// 符号可见性 Elf32_Section st_shndx;// 节区索引 } Elf32_Sym;3.3 link_map结构link_map是动态链接器的核心数据结构包含模块的加载信息和各种表的指针struct link_map { ElfW(Addr) l_addr; // 模块加载基址 char *l_name; // 模块名称 ElfW(Dyn) *l_ld; // 动态段指针 struct link_map *l_next, *l_prev; // 链表指针 // 更多字段... };4. ret2dlresolve攻击原理ret2dlresolve攻击正是基于对_dl_fixup函数解析过程的理解。攻击者通过伪造关键数据结构欺骗动态链接器解析并执行任意函数。攻击步骤通常包括控制reloc_arg使重定位项指向可控内存区域伪造Elf32_Rel结构控制r_info指向伪造的符号表项伪造Elf32_Sym结构控制st_name指向伪造的字符串伪造函数名字符串将目标函数名(如system)写入内存下表展示了正常解析与攻击伪造的对比步骤正常解析ret2dlresolve攻击重定位项.rel.plt中的合法项伪造的Elf32_Rel结构符号表项.dynsym中的合法项伪造的Elf32_Sym结构字符串.dynstr中的函数名伪造的字符串(如system)5. 64位系统的特殊考量64位系统下的ret2dlresolve攻击面临额外挑战数据结构差异重定位项使用Elf64_Rela结构包含64位字段和addend符号表项大小为24字节额外验证if (l-l_info[VERSYMIDX (DT_VERSYM)] ! NULL) { const ElfW(Half) *vernum (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]); ElfW(Half) ndx vernum[ELFW(R_SYM) (reloc-r_info)] 0x7fff; // ... }这段代码会检查符号版本信息如果伪造的符号表索引过大可能导致越界访问。解决方案通常包括设置st_other不为0跳过版本检查精心构造伪造数据使索引值合法6. 防御措施与绕过技术现代系统采用了多种防护措施对抗ret2dlresolve攻击RELRO保护No RELRO.dynamic等段可写攻击最简单Partial RELRO部分段只读增加攻击难度Full RELRO所有段在启动时解析并设为只读绕过技术对于Partial RELRO可以伪造整个link_map结构利用已解析函数的GOT条目作为伪造的符号表# 示例伪造link_map的Python代码片段 def fake_linkmap(fake_addr, known_got, offset): linkmap p64(offset (2**64-1)) # l_addr linkmap p64(0) # 填充 linkmap p64(fake_addr 0x18) # DT_JMPREL指针 # 更多伪造数据... return linkmap7. 实际应用与思考理解_dl_fixup不仅有助于安全研究还能加深对动态链接机制的认识。在实际应用中我们需要注意内存布局精心安排伪造数据的内存位置对齐要求符号表项需要16字节对齐(32位)或24字节对齐(64位)参数控制确保函数调用时的参数正确传递通过深入分析glibc源码和动态链接过程我们不仅能掌握ret2dlresolve攻击技术更能理解Linux系统底层的工作机制。这种从原理到实践的认知路径正是安全研究最具价值的部分。