逆向工程视角看Ret2Libc:从GOT/PLT表到动态链接,深入理解Linux程序内存布局

张开发
2026/6/24 18:55:45 15 分钟阅读
逆向工程视角看Ret2Libc:从GOT/PLT表到动态链接,深入理解Linux程序内存布局
逆向工程揭秘Ret2Libc从内存布局到动态链接的深度解析在二进制安全领域Ret2Libc技术就像一把打开系统防护的钥匙。当NX保护让传统的shellcode注入失效时安全研究者们发现了这个巧妙绕过机制的方法。但真正掌握Ret2Libc需要的不仅是照搬EXP模板而是理解背后精妙的动态链接机制和内存管理原理。1. ELF文件与进程内存的映射关系当我们在终端输入./vulnerable_program时操作系统完成了一系列复杂的加载工作。通过readelf -l vulnerable_program命令可以清晰地看到ELF文件如何被映射到进程地址空间Elf文件类型为 EXEC (可执行文件) 入口点 0x400520 共有 9 个程序头开始于偏移量64 程序头 Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R E 0x8 INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 0x1 LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000007e4 0x00000000000007e4 R E 0x200000 LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000228 0x0000000000000230 RW 0x200000 DYNAMIC 0x0000000000000e28 0x0000000000600e28 0x0000000000600e28 0x00000000000001d0 0x00000000000001d0 RW 0x8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 0x4 GNU_EH_FRAME 0x00000000000006c4 0x00000000004006c4 0x00000000004006c4 0x0000000000000034 0x0000000000000034 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x00000000000001f0 0x00000000000001f0 R 0x1关键内存区域在进程中的布局如下内存区域加载地址范围权限内容描述.text0x400000-0x401000R-X程序代码段.plt0x400500-0x400700R-X过程链接表.got0x601000-0x601020RW-全局偏移表libc0x7ffff7a0d000-0x7ffff7bcd000R-X共享库代码段heap0x602000-0x623000RW-动态分配内存stack0x7ffffffde000-0x7ffffffff000RW-函数调用栈提示使用gdb-peda的vmmap命令可以实时查看进程的内存映射情况这对理解ASLR下的内存布局至关重要。2. PLT/GOT机制的动态链接舞蹈动态链接的核心在于延迟绑定(Lazy Binding)机制这就像图书馆的按需借阅系统。当程序第一次调用putsplt时会经历以下步骤PLT跳转call putsplt实际上跳转到PLT表中的存根代码GOT查询PLT代码读取GOT表中存储的地址绑定决议如果是第一次调用GOT指向绑定例程(_dl_runtime_resolve)解析完成后GOT被更新为真实的函数地址函数执行后续调用直接通过GOT跳转到目标函数用objdump -d -j .plt vulnerable_program查看PLT代码0000000000400520 putsplt: 400520: ff 25 02 0b 20 00 jmpq *0x200b02(%rip) # 601028 putsgot.plt 400526: 68 00 00 00 00 pushq $0x0 40052b: e9 e0 ff ff ff jmpq 400510 _init0x28而GOT表在初始状态下存放的是PLT中下一条指令的地址$ objdump -s -j .got.plt vulnerable_program Contents of section .got.plt: 601000 280e6000 00000000 00000000 00000000 (.............. 601010 00000000 00000000 26054000 00000000 .............. 601020 00000000 00000000 36054000 00000000 ........6......当程序首次调用函数时动态链接器会执行以下操作将符号名称(puts)和重定位信息压栈调用_dl_runtime_resolve进行符号查找将解析得到的真实地址写入GOT表跳转到目标函数执行3. Ret2Libc攻击的完整链条Ret2Libc攻击本质上是将程序控制流劫持到libc函数上。一个完整的攻击通常包含两个阶段3.1 信息泄露阶段通过溢出覆盖返回地址使其跳转到putsplt并打印GOT表中的函数地址# 32位泄露示例 payload bA*offset # 填充缓冲区 payload p32(puts_plt) # 覆盖返回地址 payload p32(main_addr) # 返回地址(使程序可以继续执行) payload p32(puts_got) # puts的参数关键点在于选择已解析的函数(如puts、write)的GOT表项控制参数传递方式(32位通过栈64位通过寄存器)设计合理的返回地址维持程序稳定3.2 计算与二次攻击获取泄露地址后计算libc基址和关键函数偏移# 计算libc基址和system地址 leaked_addr u32(r.recv(4)) libc_base leaked_addr - libc.symbols[puts] system_addr libc_base libc.symbols[system] binsh_addr libc_base next(libc.search(b/bin/sh))二次攻击时需要注意64位系统需通过ROP链设置参数寄存器考虑栈对齐问题(可能需要在ROP链中添加ret指令)处理不同libc版本的偏移差异4. 对抗防护机制的进阶技巧现代系统部署了多种防护机制理解其原理才能有效绕过4.1 对抗ASLR地址空间布局随机化(ASLR)使每次运行的内存布局不同但同一进程内的相对偏移固定。突破方法包括通过信息泄露获取模块基址利用非随机化区域(如程序本身的代码段)爆破部分随机化的地址(针对低熵ASLR)4.2 绕过RELRO保护RELRO保护等级对GOT表的影响保护等级GOT表可写性影响No RELRO完全可写可直接修改GOT表Partial RELRO部分只读可覆盖未初始化的GOT项Full RELRO完全只读必须寻找其他攻击面当遇到Full RELRO时可以考虑转向return-to-plt攻击利用_dl_runtime_resolve进行高级攻击结合堆漏洞实现任意地址写4.3 现代环境下的挑战在最新系统环境中还需要考虑栈保护(Stack Canary)的绕过PIE(位置无关可执行文件)的影响更复杂的libc版本识别技术使用工具检查防护机制checksec --filevulnerable_program [*] /tmp/vulnerable_program Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)5. 实战工具链与调试技巧高效分析Ret2Libc需要掌握专业工具链5.1 静态分析工具readelf查看ELF文件结构readelf -S vulnerable_program # 查看节区头 readelf -r vulnerable_program # 查看重定位表objdump反汇编关键代码objdump -d -j .plt vulnerable_program objdump -s -j .got.plt vulnerable_program5.2 动态调试技巧GDB调试中的实用命令break *0x400520 # 在PLT入口设断点 x/10x 0x601028 # 查看GOT表内容 info sharedlibrary # 查看加载的共享库 p system # 打印函数地址自动化分析脚本示例from pwn import * def analyze_binary(path): e ELF(path) print(f[*] PLT entries: {list(e.plt.keys())}) print(f[*] GOT entries: {list(e.got.keys())}) if not e.pie: print([] Binary is not PIE, base address fixed) else: print([!] PIE enabled, addresses will vary) analyze_binary(./vulnerable_program)5.3 漏洞利用开发流程确定溢出点通过模式字符串或fuzzing找到精确偏移构建ROP链使用工具自动生成或手动构造ROPgadget --binary vulnerable_program gadgets.txt处理内存泄漏设计稳定的信息泄露payload计算地址根据泄露值和libc数据库计算关键地址最终利用组合所有元素完成攻击链在CTF竞赛中遇到一道典型的Ret2Libc题目时我通常会先运行checksec快速评估防护情况然后用rabin2或readelf查看程序结构。有一次遇到Partial RELRO保护的程序通过泄露printf的GOT地址结合libc-database快速定位到正确的libc版本最终构建出稳定的ROP链获得了shell。这个过程让我深刻体会到理解底层原理比记忆EXP模板重要得多。

更多文章