从0-1详解剖析ret2dlresolve
字数 2945 2025-08-30 06:50:35

Ret2dlresolve 技术详解

1. ELF 文件格式基础

ELF (Executable and Linkable Format) 是 Linux 和类 Unix 系统上常见的可执行文件和可链接文件格式。ELF 文件主要包含以下几种类型:

  • 可执行文件 (ET_EXEC):可直接执行的程序
  • 共享目标文件 (ET_DYN):动态链接的共享库 (.so)
  • 可重定位文件 (ET_REL):编译器生成的目标文件 (.o)
  • 核心转储文件 (ET_CORE):程序崩溃时生成的核心转储文件

ELF 文件结构定义在 /usr/include/elf.h 中,有 32 位和 64 位两种版本。

1.1 ELF 文件结构

ELF 文件主要由以下几部分组成:

  1. 文件头 (Elf32_Ehdr/Elf64_Ehdr):描述文件的基本信息
  2. 程序头表 (Program Header Table):描述段(Segment)信息
  3. 节表 (Section Header Table):描述节(Section)信息
  4. 各种节(Section):包含实际的代码、数据等

1.2 段(Segment)与节(Section)的区别

  • 段(Segment):逻辑组织单位,定义内存中的连续区域,包含多个属性相同的节
  • 节(Section):更细粒度的组织单位,包含特定类型的数据或代码

装载程序时,属性相同的节会被合并到一个段中加载到内存。

2. 动态链接相关结构

2.1 .interp 段

包含动态链接器的路径字符串,如 /lib64/ld-linux-x86-64.so.2

2.2 .dynamic 段

动态链接最重要的结构,保存动态链接器需要的基本信息,由 Elf*_Dyn 结构体数组组成。

重要成员:

  • DT_SYMTAB:动态符号表(.dynsym)地址
  • DT_STRTAB:动态字符串表(.dynstr)地址
  • DT_JMPREL:PLT 重定位表(.rel.plt)地址
  • DT_RELENT:单个重定位项大小
  • DT_SYMENT:单个符号表项大小

2.3 动态符号表(.dynsym)

保存动态链接相关的符号信息,由 Elf*_Sym 结构体数组组成。

Elf*_Sym 结构体成员:

  • st_name:符号名称在字符串表中的偏移
  • st_value:符号地址
  • st_size:符号大小
  • st_info:符号类型和绑定信息
  • st_other:通常为0
  • st_shndx:符号所在节的索引

2.4 动态链接重定位表(.rel.plt)

保存函数引用的重定位信息,由 Elf*_Rel 结构体数组组成。

Elf*_Rel 结构体成员:

  • r_offset:需要重定位的位置
  • r_info:低8位表示重定位类型,高24/32位表示符号索引

2.5 PLT 和 GOT 表

  • PLT (Procedure Linkage Table):包含跳转到GOT的代码
  • GOT (Global Offset Table):分为 .got.got.plt
    • .got:保存全局变量引用地址
    • .got.plt:保存函数引用地址,前三项有特殊意义:
      1. .dynamic 段偏移/地址
      2. link_map 结构体指针
      3. _dl_runtime_resolve 地址

3. 延迟绑定机制

动态链接采用延迟绑定(Lazy Binding)技术,函数在第一次调用时才进行解析。

3.1 第一次调用函数流程

  1. 调用 PLT 表中的跳转指令
  2. 跳转到 GOT 表中保存的下一条指令地址
  3. 压入重定位表索引并跳转到 PLT0
  4. 调用 _dl_runtime_resolve 进行符号解析
  5. 解析完成后更新 GOT 表并调用函数

3.2 第二次调用函数流程

  1. 调用 PLT 表中的跳转指令
  2. 直接跳转到 GOT 表中保存的函数地址

4. ret2dlresolve 技术原理

ret2dlresolve 是一种利用动态链接解析过程进行攻击的技术,通过伪造动态链接相关的数据结构来控制程序执行流程。

4.1 _dl_runtime_resolve 函数流程

ELFW(ST_VISIBILITY)(sym->st_other) 为0时的执行流程:

  1. link_map 访问 .dynamic,获取 .dynstr.dynsym.rel.plt 指针
  2. .rel.plt + 第二个参数得到 Elf*_Rel 指针 rel
  3. rel->r_info >> 8 作为 .dynsym 下标,得到 Elf*_Sym 指针 sym
  4. .dynstr + sym->st_name 得到符号名字符串指针
  5. 在动态链接库中查找函数地址并更新 GOT 表
  6. 调用该函数

4.2 攻击思路

根据 RELRO 保护级别的不同,攻击方法也有所不同:

4.2.1 NO RELRO 情况

.dynamic 段可写,可以改写 .dynamicDT_STRTAB 指针,使其指向伪造的字符串表。

攻击步骤:

  1. 伪造 .dynstr 字符串表
  2. 修改 .dynamic 中的 DT_STRTAB 指针指向伪造的字符串表
  3. 调用 _dl_runtime_resolve 解析伪造的函数名

4.2.2 Partial RELRO 情况

.dynamic 段不可写,需要操纵 _dl_runtime_resolve 的第二个参数,使其访问到可控内存并伪造相关结构。

攻击步骤:

  1. 伪造 Elf*_Rel 结构
  2. 伪造 Elf*_Sym 结构
  3. 伪造 .dynstr 字符串
  4. 控制第二个参数指向伪造的 Elf*_Rel 结构
  5. 调用 _dl_runtime_resolve

4.3 32位与64位的区别

  1. 参数传递

    • 32位:通过栈传递参数
    • 64位:通过寄存器传递参数
  2. 索引计算

    • 32位:reloc_arg 是偏移量
    • 64位:reloc_arg 是索引
  3. 结构体大小

    • 32位:Elf32_Rel 8字节,Elf32_Sym 16字节
    • 64位:Elf64_Rel 16字节,Elf64_Sym 24字节

5. 实际利用示例

5.1 32位 Partial RELRO 利用

# 伪造 .dynstr
fake_dynstr = '\x00libc.so.6\x00system\x00'

# 伪造 Elf32_Sym
fake_sym = p32(st_name) + p32(0) + p32(0) + p32(0x12)

# 伪造 Elf32_Rel
fake_rel = p32(r_offset) + p32(r_info)

# 计算 reloc_arg
reloc_arg = (fake_rel_addr - rel_plt_addr) // 8

# 调用 _dl_runtime_resolve
rop.raw(plt0)
rop.raw(reloc_arg)
rop.raw('aaaa')  # 返回地址
rop.raw(binsh_addr)  # system参数

5.2 64位 Partial RELRO 利用

# 伪造 link_map
fake_link_map = p64(0)  # l_addr
fake_link_map += p64(0)  # l_name
fake_link_map += p64(0)  # l_ld
fake_link_map += p64(dt_strtab)  # l_info[5]
fake_link_map += p64(dt_symtab)  # l_info[6]
fake_link_map += p64(dt_jmprel)  # l_info[23]

# 伪造 Elf64_Rel
fake_rel = p64(r_offset) + p64(r_info)

# 伪造 Elf64_Sym
fake_sym = p32(st_name) + p8(0x12) + p8(0) + p16(0) + p64(st_value)

# 调用 _dl_runtime_resolve
rop.raw(plt0)
rop.raw(fake_link_map_addr)
rop.raw(reloc_arg)
rop.raw(binsh_addr)  # system参数

6. 防御措施

  1. RELRO 保护

    • NO RELRO:不保护
    • Partial RELRO:部分保护,.dynamic 只读
    • Full RELRO:完全保护,所有重定位在加载时完成
  2. 其他保护

    • ASLR:地址空间布局随机化
    • Stack Canary:栈保护
    • NX:数据执行保护

7. 总结

ret2dlresolve 是一种强大的攻击技术,可以在不知道 libc 版本的情况下完成利用。理解 ELF 文件格式、动态链接机制和延迟绑定原理是掌握该技术的关键。根据不同的保护级别,需要采用不同的利用方法,32位和64位环境下的实现也有所不同。

Ret2dlresolve 技术详解 1. ELF 文件格式基础 ELF (Executable and Linkable Format) 是 Linux 和类 Unix 系统上常见的可执行文件和可链接文件格式。ELF 文件主要包含以下几种类型: 可执行文件 (ET_ EXEC) :可直接执行的程序 共享目标文件 (ET_ DYN) :动态链接的共享库 (.so) 可重定位文件 (ET_ REL) :编译器生成的目标文件 (.o) 核心转储文件 (ET_ CORE) :程序崩溃时生成的核心转储文件 ELF 文件结构定义在 /usr/include/elf.h 中,有 32 位和 64 位两种版本。 1.1 ELF 文件结构 ELF 文件主要由以下几部分组成: 文件头 (Elf32_ Ehdr/Elf64_ Ehdr) :描述文件的基本信息 程序头表 (Program Header Table) :描述段(Segment)信息 节表 (Section Header Table) :描述节(Section)信息 各种节(Section) :包含实际的代码、数据等 1.2 段(Segment)与节(Section)的区别 段(Segment) :逻辑组织单位,定义内存中的连续区域,包含多个属性相同的节 节(Section) :更细粒度的组织单位,包含特定类型的数据或代码 装载程序时,属性相同的节会被合并到一个段中加载到内存。 2. 动态链接相关结构 2.1 .interp 段 包含动态链接器的路径字符串,如 /lib64/ld-linux-x86-64.so.2 。 2.2 .dynamic 段 动态链接最重要的结构,保存动态链接器需要的基本信息,由 Elf*_Dyn 结构体数组组成。 重要成员: DT_SYMTAB :动态符号表(.dynsym)地址 DT_STRTAB :动态字符串表(.dynstr)地址 DT_JMPREL :PLT 重定位表(.rel.plt)地址 DT_RELENT :单个重定位项大小 DT_SYMENT :单个符号表项大小 2.3 动态符号表(.dynsym) 保存动态链接相关的符号信息,由 Elf*_Sym 结构体数组组成。 Elf*_Sym 结构体成员: st_name :符号名称在字符串表中的偏移 st_value :符号地址 st_size :符号大小 st_info :符号类型和绑定信息 st_other :通常为0 st_shndx :符号所在节的索引 2.4 动态链接重定位表(.rel.plt) 保存函数引用的重定位信息,由 Elf*_Rel 结构体数组组成。 Elf*_Rel 结构体成员: r_offset :需要重定位的位置 r_info :低8位表示重定位类型,高24/32位表示符号索引 2.5 PLT 和 GOT 表 PLT (Procedure Linkage Table) :包含跳转到GOT的代码 GOT (Global Offset Table) :分为 .got 和 .got.plt .got :保存全局变量引用地址 .got.plt :保存函数引用地址,前三项有特殊意义: .dynamic 段偏移/地址 link_map 结构体指针 _dl_runtime_resolve 地址 3. 延迟绑定机制 动态链接采用延迟绑定(Lazy Binding)技术,函数在第一次调用时才进行解析。 3.1 第一次调用函数流程 调用 PLT 表中的跳转指令 跳转到 GOT 表中保存的下一条指令地址 压入重定位表索引并跳转到 PLT0 调用 _dl_runtime_resolve 进行符号解析 解析完成后更新 GOT 表并调用函数 3.2 第二次调用函数流程 调用 PLT 表中的跳转指令 直接跳转到 GOT 表中保存的函数地址 4. ret2dlresolve 技术原理 ret2dlresolve 是一种利用动态链接解析过程进行攻击的技术,通过伪造动态链接相关的数据结构来控制程序执行流程。 4.1 _ dl_ runtime_ resolve 函数流程 当 ELFW(ST_VISIBILITY)(sym->st_other) 为0时的执行流程: 用 link_map 访问 .dynamic ,获取 .dynstr 、 .dynsym 、 .rel.plt 指针 .rel.plt + 第二个参数得到 Elf*_Rel 指针 rel rel->r_info >> 8 作为 .dynsym 下标,得到 Elf*_Sym 指针 sym .dynstr + sym->st_name 得到符号名字符串指针 在动态链接库中查找函数地址并更新 GOT 表 调用该函数 4.2 攻击思路 根据 RELRO 保护级别的不同,攻击方法也有所不同: 4.2.1 NO RELRO 情况 .dynamic 段可写,可以改写 .dynamic 的 DT_STRTAB 指针,使其指向伪造的字符串表。 攻击步骤: 伪造 .dynstr 字符串表 修改 .dynamic 中的 DT_STRTAB 指针指向伪造的字符串表 调用 _dl_runtime_resolve 解析伪造的函数名 4.2.2 Partial RELRO 情况 .dynamic 段不可写,需要操纵 _dl_runtime_resolve 的第二个参数,使其访问到可控内存并伪造相关结构。 攻击步骤: 伪造 Elf*_Rel 结构 伪造 Elf*_Sym 结构 伪造 .dynstr 字符串 控制第二个参数指向伪造的 Elf*_Rel 结构 调用 _dl_runtime_resolve 4.3 32位与64位的区别 参数传递 : 32位:通过栈传递参数 64位:通过寄存器传递参数 索引计算 : 32位: reloc_arg 是偏移量 64位: reloc_arg 是索引 结构体大小 : 32位: Elf32_Rel 8字节, Elf32_Sym 16字节 64位: Elf64_Rel 16字节, Elf64_Sym 24字节 5. 实际利用示例 5.1 32位 Partial RELRO 利用 5.2 64位 Partial RELRO 利用 6. 防御措施 RELRO 保护 : NO RELRO :不保护 Partial RELRO :部分保护, .dynamic 只读 Full RELRO :完全保护,所有重定位在加载时完成 其他保护 : ASLR:地址空间布局随机化 Stack Canary:栈保护 NX:数据执行保护 7. 总结 ret2dlresolve 是一种强大的攻击技术,可以在不知道 libc 版本的情况下完成利用。理解 ELF 文件格式、动态链接机制和延迟绑定原理是掌握该技术的关键。根据不同的保护级别,需要采用不同的利用方法,32位和64位环境下的实现也有所不同。