深入glibc:图解_dl_fixup函数如何一步步解析动态链接函数(附ret2dlresolve利用原理)
深入glibc图解_dl_fixup函数如何一步步解析动态链接函数在Linux系统中动态链接是一个至关重要的机制它允许程序在运行时加载共享库中的函数。这个过程看似简单但背后却隐藏着复杂的机制。本文将带你深入探索glibc中的_dl_fixup函数揭示动态链接函数解析的全过程。1. 动态链接的基本原理当我们在程序中调用一个动态链接的函数时实际上经历了一个精巧的延迟绑定过程。这个过程确保了只有在函数第一次被调用时才会进行解析后续调用则直接跳转到解析后的地址。让我们用一个简单的例子来说明这个过程#include unistd.h int main() { write(1, Hello\n, 6); return 0; }编译这个程序后如果我们查看它的反汇编代码会发现write函数的调用实际上是通过PLTProcedure Linkage Table来实现的。PLT是动态链接机制中的关键组件它充当了程序与共享库之间的桥梁。动态链接的核心优势在于节省内存多个程序可以共享同一个库的代码段灵活性库可以独立更新而不需要重新编译程序加载效率只有实际使用的函数才会被解析和加载2. PLT和GOT的协作机制PLT和GOTGlobal Offset Table共同构成了动态链接的基础设施。让我们详细看看它们是如何工作的第一次调用函数时程序跳转到PLT表项PLT表项跳转到GOT表项由于是第一次调用GOT表项指向PLT中的下一条指令程序压入重定位偏移量(reloc_arg)跳转到PLT[0]即动态链接器的入口后续调用时程序跳转到PLT表项PLT表项跳转到GOT表项GOT表项此时已经保存了函数的真实地址直接跳转到目标函数这个过程中_dl_runtime_resolve函数扮演了关键角色。它接收两个参数link_map包含动态链接信息的结构体reloc_arg重定位偏移量_dl_runtime_resolve实际上会调用_dl_fixup函数来完成具体的解析工作。3. _dl_fixup函数的详细解析_dl_fixup函数是动态链接解析过程的核心它位于glibc的elf/dl-runtime.c文件中。让我们逐步分析它的工作原理_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) { // 获取重定位表项 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(ELF(R_TYPE)(reloc-info) ELF_MACHINE_JMP_SLOT); // 查找符号 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); // 更新GOT表 return elf_machine_fixup_plt(l, result, reloc, rel_addr, value); }这个过程可以分解为以下几个关键步骤定位重定位表项通过reloc_arg在.rel.plt节中找到对应的重定位表项这个表项包含了函数在符号表中的索引和重定位类型获取符号信息通过重定位表项中的r_info字段找到符号表项符号表项包含了函数名在字符串表中的偏移等信息符号查找使用_dl_lookup_symbol_x函数在加载的共享库中查找符号这个函数会返回包含目标符号的库的基地址地址计算与更新计算函数的实际地址库基地址 符号偏移将这个地址写入GOT表的对应条目中4. 关键数据结构解析理解动态链接机制需要熟悉几个关键的数据结构4.1 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; // 链表中前后对象 // ... 其他字段 ... ElfW(Dyn) *l_info[DT_NUM]; // 动态段信息数组 };其中l_info数组包含了指向各种动态段信息的指针如DT_JMPREL指向PLT重定位表DT_SYMTAB指向动态符号表DT_STRTAB指向字符串表4.2 重定位表项重定位表项Elf32_Rel/Elf64_Rela描述了如何修改指令或数据以正确引用符号typedef struct { Elf32_Addr r_offset; // 需要修改的地址通常是GOT表项 Elf32_Word r_info; // 符号索引和重定位类型 } Elf32_Rel;r_info字段的高24位是符号索引低8位是重定位类型。4.3 符号表项符号表项Elf32_Sym/Elf64_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;5. 动态链接的安全考量理解动态链接机制不仅有助于深入理解Linux系统也对系统安全有重要意义。动态链接过程中涉及多个数据结构的交互如果这些数据结构被恶意篡改就可能导致安全漏洞。在安全研究中有一种称为ret2dlresolve的技术就是利用了动态链接机制的特性。这种技术允许攻击者在特定条件下控制函数的解析过程从而执行任意代码。理解_dl_fixup的工作原理是分析和防御这类攻击的基础。6. 调试动态链接过程为了更好地理解动态链接的实际工作过程我们可以使用GDB进行调试。以下是一个简单的调试步骤在PLT表项设置断点break *writeplt跟踪第一次调用时的执行流程stepi观察GOT表的变化x/x writegot跟踪_dl_runtime_resolve的执行finish通过这样的调试我们可以直观地看到动态链接的整个过程从PLT跳转到GOT再到_dl_runtime_resolve和_dl_fixup的调用最后GOT表被更新为函数的真实地址。7. 实际案例分析让我们通过一个实际的例子来观察动态链接的过程。考虑以下简单的漏洞程序#include unistd.h #include stdio.h #include string.h void vuln() { char buf[100]; read(0, buf, 256); } int main() { char buf[100] Welcome!\n; write(1, buf, strlen(buf)); vuln(); return 0; }编译这个程序时我们可以观察到write和read都是动态链接的函数它们的PLT表项和GOT表项在二进制中是明确可见的第一次调用这些函数时会触发解析过程通过分析这个程序的动态链接行为我们可以更深入地理解_dl_fixup的实际工作方式。8. 总结与展望动态链接是现代操作系统中的重要机制而_dl_fixup函数则是这个机制的核心。通过本文的分析我们了解了PLT和GOT如何协作实现延迟绑定_dl_runtime_resolve和_dl_fixup的调用关系动态链接过程中涉及的关键数据结构如何调试和观察动态链接的实际过程理解这些底层机制不仅有助于我们编写更高效的代码也为深入分析系统安全提供了基础。随着技术的发展动态链接机制也在不断演进但基本原理仍然保持着一致性。