简介
ret2dlresolve 是一种利用程序漏洞(通常是缓冲区溢出)来绕过程序的安全机制并控制程序流程的攻击方式。它利用了动态链接库
(DLL)解析的过程,攻击者通过修改程序的控制流,迫使程序调用恶意的共享库函数。具体来说,攻击者通过构造特定的输入,使得程
序在执行时调用一个由攻击者控制的函数,从而实现远程代码执行。该攻击通常针对未启用安全防护(如地址空间布局随机化 ASLR 或栈
保护)的程序。
前置知识
ELF文件格式
ELF 概述
ELF 的全称是 Executable and Linkable Format,即可执行可链接格式。它定义了一种结构化的方式,来存储程序的各种信息,以便于操作系统进行加载、执行以及链接器进行代码和数据的链接。
ELF文件主要分为三种类型:
- 可重定位文件:通常以
.o结尾。包含代码和数据,可以与其他目标文件链接生成可执行文件或共享库。 - 可执行文件:可以直接运行的程序。例如
/bin/bash。 - 共享目标文件:通常以
.so结尾。包含代码和数据,可以在两种情况下被使用:- 链接时:与可重定位文件和其他共享目标文件一起,被链接器处理,生成新的可执行文件或共享库。
- 运行时:被动态链接器加载到进程的地址空间,与可执行文件合并,形成完整的进程映像。
ELF 文件结构布局
ELF文件从结构上可以分为两大部分:“链接视图” 和 “执行视图”。
- 链接视图:以 节 为单位组织,主要供链接器使用。
- 执行视图:以 段 为单位组织,主要供加载器(操作系统)使用。
一个典型的ELF文件布局如下所示:
)
1 | Program Header Table<-- 执行视图:描述如何创建进程映像(段信息) |
ELF头
位于文件开头,是整个ELF文件的“总目录”。可以使用 readelf -h <file> 查看。

主要包含以下信息:
- 魔数:前16个字节,包括
0x7F和字符串ELF,用于标识这是一个ELF文件。 - 文件类:标识是32位(
ELF32)还是64位(ELF64)文件。 - 数据编码:标识是小端序(Little Endian)还是大端序(Big Endian)。
- ELF版本:通常是当前版本
1。 - OS/ABI:标识目标操作系统ABI。
- 文件类型:指明是哪种类型的ELF文件(可重定位、可执行、共享库等)。
- 机器类型:指明需要的体系结构(如 x86, ARM, MIPS等)。
- 程序入口地址:可执行文件的入口点虚拟地址。
- 程序头表起始位置、大小和表项数量。
- 节头表起始位置、大小和表项数量。
- 节头表字符串表索引:用于存储节名称的字符串表在节头表中的索引。
1 | /* The ELF file header. This appears at the start of every ELF file. */ |
- e_ident[EI_NIDENT]

红色区域:从左到右
EI_MAG0 ~3、EI_CLASS 、EI_DATA、EI_VERSION、EI_OSABI、EI_ABIVERSION、EI_PAD
EI_MAG0 ~3:0X7F ELF 文件标识
EI_CLASS:0x02 当取值为0时,是非法类别,1是32位的目标,2是64位的目标。
EI_DATA:0x01 表示数据的编码,当为0时,表示非法数据编码,1表示高位在前,2表示低位在前。
EI_VERSION:0x01 ELF 版本:01 = 当前版本
EI_ABIVERSION:0x00 ABI 版本
EI_PAD:0x00 填充字节 (共7个字节)
黄色区域:从左到右,从上到下
e_type 、e_machine 、e_version、e_entry
e_phoff、 e_shoff
- e_type :
00 30(小端序)
1 | 值(十六进制) 宏定义 描述 |
- e_machine :
00 3E
1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ |
- e_version
00 00 00 01
EV_NONE:00
EV_CURRENT:01
0表示非法版本,1表示当前版本。
- e_entry
F0 10 00 00 00 00 00 00 0x00000000000010F0 (入口点地址)
- e_phoff
40 00 00 00 00 00 00 00 0x0000000000000040 (程序头表偏移)
- e_shoff
E0 39 00 00 00 00 00 00 0x00000000000039E0 (节头表偏移)
程序头表
程序头表是一个由 Elf*_Phdr 结构体组成的数组,用于描述 ELF 文件中的段(Segment)信息,这些信息指明了操作系统应如何将这些段装载到内存中并执行。因此,只有可执行文件和共享库包含程序头表,而目标文件则没有。
1 | typedef struct |
p_type:段的类型,用于区分该段是代码段、数据段、动态链接信息段还是其他特殊类型的段。
p_offset:段内容在ELF文件内的起始偏移量,指示了从文件何处开始读取该段。
p_vaddr:段在进程虚拟内存空间中的起始地址,即该段应该被加载到的虚拟地址。
p_paddr:段在物理内存中的起始地址。此字段通常被保留,在现代操作系统中由于使用虚拟内存,其值通常与 p_vaddr 相同。
p_filesz:段在ELF文件中所占的大小。某些段(如 .bss)在文件中可能不占空间,此时此值会小于 p_memsz。
p_memsz:段在内存中所占的大小。如果该段包含未初始化的数据(如 .bss),其在内存中的大小会大于在文件中的大小。
- p_flags:段的权限标志,定义了内存页的访问权限,如可读、可写、可执行。
- p_align:段在文件和内存中的对齐要求。其值为2的正整数次幂,加载地址和文件偏移必须满足 (addr % align) == (offset % align) 的对齐关系。
ELF文件的节区
是 ELF 文件中ELF文件的节区按功能划分的各个部分,其信息由节区头部表统一描述,可视为节区的 “目录”。可以使用 readelf -S
<file> 查看

1 | typedef struct |
sh_name:节名称在字符串表(.shstrtab 节)中的索引。通过此索引可以找到表示该节名称的字符串。
sh_type:节的类型,定义了节的内容和语义。常见类型包括:
SHT_PROGBITS:程序定义的内容,如代码或数据。
SHT_SYMTAB:符号表。
SHT_STRTAB:字符串表。
sh_flags:节的属性标志,描述了节在进程内存中的行为。例如:
SHF_WRITE:该节在运行时可写。
SHF_ALLOC:该节在内存中需要分配空间。
SHF_EXECINSTR:该节包含可执行的机器指令。
sh_addr:如果该节在进程内存映像中需要被分配空间(例如,具有 SHF_ALLOC 标志),此字段指定该节在内存中的虚拟地址。对于目标文件或不需加载的节,此值为 0。
sh_offset:该节内容在 ELF 文件中的起始字节偏移。
sh_size:该节内容的大小(字节数)。对于 .bss 这类在文件中不占空间但运行时需要内存的节,此字段表示其在内存中应分配的大小。
sh_link:一个节头表索引,指向与此节相关的另一个节。具体含义取决于 sh_type。例如,在符号表中,它指向该符号表所使用的字符串表。
sh_info:提供节的附加信息,具体含义依赖于节的类型。例如,在符号表中,它指向第一个全局符号的索引。
sh_addralign:节的地址对齐约束。这是一个正整数,通常是 2 的幂。节的地址 sh_addr 必须满足 sh_addr % sh_addralign == 0。值为 0 或 1 表示没有对齐约束。
sh_entsize:对于包含固定大小条目(如符号表)的节,此字段给出每个条目的大小(字节数)。如果节中不包含此类固定大小的结构,则此值为 0。
ELF 中常见的节
1.代码与初始化数据节(核心功能)
这些节包含了程序运行所必需的代码和已初始化的数据。
.text- 类型:
PROGBITS - 属性: 可执行、只读
- 详细解释: 这是 ELF 文件中最核心的节。它包含了由编译器编译生成的机器指令(代码)。当程序运行时,CPU 就是从这块内存区域读取并执行指令的。所有你编写的函数(除了内联的)代码最终都在这里。
- 类型:
.data- 类型:
PROGBITS - 属性: 可读写
- 详细解释: 存放已初始化且初始值不为零的全局变量和静态局部变量。例如,在函数外定义的
int global_var = 100;或在函数内定义的static int static_var = 50;就会存储在.data节中。因为这些变量在程序启动时就有明确的值,所以它们需要占用文件空间来存储这些初始值。
- 类型:
.rodata- 类型:
PROGBITS - 属性: 只读
- 详细解释: 存放只读数据。最常见的就是字符串常量。例如,你在代码中写的
"Hello, World\n"这个字符串就会存放在这里。此外,一些编译器也会将const修饰的全局常量放在这里。这个节的存在可以防止程序意外修改常量数据,提高安全性。
- 类型:
.bss- 类型:
NOBITS - 属性: 可读写
- 详细解释: 存放未初始化或初始化为零的全局变量和静态局部变量。例如
int global_var_uninit;或static int static_var_zero = 0;。 - 关键特点: 它的类型是
NOBITS,意味着这个节在 ELF 文件本身中不占用实际的空间。它只是在程序头中告诉加载器:“请在内存中为我预留这么大的一块空间,并把这块内存全部初始化为零”。这极大地节省了磁盘空间。
- 类型:
2.动态链接相关节
这些节对于动态链接库(.so 文件)和动态链接的可执行文件至关重要。
.dynamic- 类型:
DYNAMIC - 详细解释: 这个节包含了一个数组,数组的每一项都是一个描述动态链接信息的结构(
Elf64_Dyn)。它包含了动态链接器(如ld-linux.so)运行所需的所有信息,例如:- 依赖的共享库列表(
.dynstr,.dynsym的位置) - 全局偏移表(GOT)的位置(
.got.plt) - 重定位表的位置(
.rela.dyn) - 符号哈希表的位置(
.hash或.gnu.hash)
- 依赖的共享库列表(
- 可以把它看作是动态链接的“目录”或“元数据区”。
- 类型:
.dynsym- 类型:
DYNSYM - 详细解释: 动态链接符号表。它包含了从外部共享库导入或向外部导出的符号(函数名、变量名)的信息。这些符号是在运行时需要被解析的。与之相对的是
.symtab,后者包含所有符号,包括调试用的局部符号。
- 类型:
.dynstr- 类型:
STRTAB - 详细解释: 动态链接字符串表。它存储了
.dynsym中符号名称的字符串。.dynsym中的符号条目本身不存储长字符串,而是存储一个在.dynstr中的偏移量。
- 类型:
.got&.got.plt- 类型:
PROGBITS - 属性: 可读写
- 详细解释: 全局偏移表。这是动态链接实现“位置无关代码(PIC)”的核心数据结构。
- **
.got**:通常用于存放全局变量的地址。 - **
.got.plt**:专门用于存放外部函数的地址。它是过程链接表(PLT)的搭档。
- **
- 工作原理简析: 程序第一次调用一个共享库函数时,会通过 PLT 跳转到
.got.plt中对应的项。该项初始指向 PLT 中的一段代码,该代码会调用动态链接器来解析这个函数的真实地址,并将其写回.got.plt。之后再次调用该函数时,就会直接跳转到真实的函数地址。这实现了所谓的“延迟绑定”。
- 类型:
.plt- 类型:
PROGBITS - 属性: 可执行
- 详细解释: 过程链接表。这是一小段存根代码。当你调用一个共享库函数(如
printf)时,编译器生成的代码实际上是调用.plt中的一个条目。.plt的代码会间接跳转到.got.plt中存储的地址。如上所述,第一次调用时,它会触发动态链接器进行符号解析。
- 类型:
.rela.dyn&.rela.plt- 类型:
RELA - 详细解释: 重定位表。它包含了在动态链接过程中需要修改的地址信息。
- **
.rela.dyn**: 主要对数据引用(如全局变量)进行重定位。 - **
.rela.plt**: 主要对函数引用进行重定位,与.plt和.got.plt密切相关。
- **
- 类型:
3. 调试与链接信息节
这些节包含了丰富的符号和调试信息,主要用于调试和链接,在发布剥离(strip)后的可执行文件中通常会被移除。
.symtab- 类型:
SYMTAB - 详细解释: 符号表。它包含了程序中所有的符号信息,包括局部符号、调试符号等。这比
.dynsym要全面得多。gdb、nm等工具主要就是读取这个表来显示符号信息。strip命令删除的主要就是这个节。
- 类型:
.strtab- 类型:
STRTAB - 详细解释: 字符串表。存储了
.symtab中符号名称的字符串。
- 类型:
.shstrtab- 类型:
STRTAB - 详细解释: 节头字符串表。它存储了所有节名称(如
.text,.data)的字符串。节头表(Section Header Table)中的每个节都有一个指向这个表的偏移量来获取自己的名字。
- 类型:
.debug_\*- 类型:
PROGBITS - 详细解释: 一系列用于存储调试信息的节,例如:
.debug_info: 核心的调试信息。.debug_line: 映射机器指令到源代码行号。.debug_abbrev:.debug_info中使用的缩写。.debug_frame: 调用帧信息(CFI),用于栈回溯。
这些节通常在编译时使用-g选项生成。
- 类型:
.comment- 类型:
PROGBITS - 详细解释: 存放编译器版本信息。例如
GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0。
- 类型:
4. 其他重要节
.init&.fini- 类型:
PROGBITS - 属性: 可执行
- 详细解释: 包含进程初始化和终止的代码。
- **
.init**: 在main函数之前被执行,负责初始化工作。 - **
.fini**: 在main函数返回后被执行,负责清理工作。
- **
- 在现代系统中,这些功能更多地通过
.init_array和.fini_array来实现。
- 类型:
.init_array&.fini_array- 类型:
INIT_ARRAY/FINI_ARRAY - 详细解释: 这是一个函数指针数组。
- **
.init_array**: 里面的每个函数指针都会在main函数之前被依次调用。 - **
.fini_array**: 里面的每个函数指针都会在main函数返回后被依次调用(或exit时)。
这为全局对象的构造和析构(在C++中)以及使用__attribute__((constructor))的函数提供了实现机制。
- **
- 类型:
.eh_frame&.eh_frame_hdr- 类型:
PROGBITS - 详细解释: 用于存放异常处理(Exception Handling)和栈展开(Stack Unwinding)的信息。这在C++异常处理和生成栈跟踪时非常重要。
.eh_frame_hdr是一个索引,用于加速栈展开。
- 类型:
.ctors&.dtors- 详细解释: 与
.init_array/.fini_array功能类似,是旧版 GCC 使用的全局构造和析构函数数组。现在已基本被后者取代。
- 详细解释: 与
要详细了解的节
**这里要详细介绍这几个.symtab .rel.text/.rel.data .strtab .interp dynamic .dynsym .rel.dyn/.rel.data **
.symtab
1 | typedef struct |
作用与存在:
- 主要作用:在静态链接过程中,链接器用它来解析符号(函数、变量名)的引用和定义。
- 次要作用:为调试器 (
gdb) 提供符号信息,方便开发者调试。 - 发布与安全:程序发布时通常不需要符号表。可以通过
strip命令将其从文件中移除,以减小体积并增加逆向分析难度(这就是为什么有些 Pwn 题附件没有函数名)。
数据结构:
符号表是Elf32_Sym(32位)或Elf64_Sym(64位)结构体的数组。每个结构体描述一个符号。Elf32_Sym结构体字段详解:字段 C 类型 描述 st_nameElf32_Word符号名偏移。指向 .strtab(字符串表) 中的索引,实际符号名在那里以字符串形式存储。st_valueElf32_Addr符号的值/地址。其含义根据文件类型和符号类型而变化: • 目标文件 (.o):对于已定义的非COMMON块符号,表示在它所在 Section 中的偏移。 • 目标文件 (.o):对于 COMMON块 符号(如未初始化的全局变量),表示对齐要求。 • 可执行文件:表示符号的**虚拟内存地址 (Virtual Address)**。 st_sizeElf32_Word符号的大小。例如,一个函数有多大,一个全局变量占多少字节。为 0 表示大小未知或为零。 st_infounsigned char符号类型与绑定信息。一个字节,高4位表示类型,低4位表示绑定。 • 绑定 (Binding): STB_LOCAL(局部),STB_GLOBAL(全局),STB_WEAK(弱符号)。 • 类型 (Type):STT_NOTYPE(无类型),STT_OBJECT(数据对象),STT_FUNC(函数) 等。st_otherunsigned char符号可见性。通常为 0。 st_shndxElf32_Section符号所在 Section 的索引。这是一个关键字段,它告诉链接器或调试器这个符号“住在哪里”: • 如果是一个普通的已定义符号,其值为对应 Section(如 .text,.data,.bss)的索引。 •SHN_ABS:符号是一个绝对值,在链接时不会改变(例如,初始值不为 0 的全局变量,其值固定)。 •SHN_COMMON:符号是一个 COMMON块,通常是未初始化的全局变量。它在链接时由链接器分配空间(通常在.bss段)。 •SHN_UNDEF:符号未在本文件中定义。这通常意味着该符号(如printf)是在其他目标文件或库中定义的。
.rel.text/.rel.data
1 | typedef struct |
目的与作用:
- 解决地址未知问题:在编译生成目标文件 (
.o) 时,代码中引用的外部函数和全局变量的最终内存地址是未知的。 - 为链接器提供”修补”指南:重定位表就是告诉链接器:”在最终生成可执行文件时,请到这个文件的这些位置,用正确的地址替换掉当前的临时值。”
- 类型:主要分为代码重定位 (
.rel.text) 和数据重定位 (.rel.data)。
- 解决地址未知问题:在编译生成目标文件 (
数据结构:
重定位表是Elf32_Rel结构体(或带加数版本的Elf32_Rela)的数组。每个结构体称为一个 重定位入口,描述一个需要”修补”的地方。Elf32_Rel结构体字段详解:
| 字段 | C 类型 | 描述 |
|---|---|---|
r_offset |
Elf32_Addr |
需要被修正的位置。 • 在目标文件 (.o) 中:此值是相对于该重定位表对应 Section 起始位置的偏移量。例如,在 .rel.text 中,r_offset 表示需要修改的位置在 .text 段中的偏移。 • 在可执行文件或共享库中:此值是需要修改的内存虚拟地址(主要用于动态链接)。 |
r_info |
Elf32_Word |
复合字段,包含两个关键信息: • 低 8 位:重定位类型。这决定了链接器/动态链接器应该如何计算并填充正确的值。例如: - R_386_PC32: PC 相对寻址的重定位(常用于函数调用)。 - R_386_32: 绝对地址重定位(常用于全局变量)。 • 高 24 位:符号在符号表中的索引。告诉链接器这个位置引用的到底是哪个符号(比如 printf 还是 global_var)。 |
重定位过程简单比喻
把编译链接过程想象成拼装一个模型:
- 目标文件 (.o):是一个个独立的零件,上面有些预留的插孔(需要重定位的位置)。
- 符号表:是一份零件清单,说明了每个零件(符号)是什么。
- 重定位表:是一份组装说明书,明确写着:”在A零件的X位置,需要插入清单上编号为Y的零件,插入方式请按Z方法(重定位类型)进行。”
链接器就是按照这份”组装说明书”(重定位表),将所有的零件(目标文件)正确地拼接在一起,并在所有预留的插孔处填入最终正确的地址。
.strtab
ELF 文件使用字符串表来解决不定长字符串存储问题。通过将字符串集中存储,其他部分只需通过数字偏移量引用字符串,无需处理变长字段。
字符串表类型
| 表类型 | 段名称 | 主要用途 |
|---|---|---|
| 字符串表 | .strtab |
存储符号名称(函数名、变量名等) |
| 段表字符串表 | .shstrtab |
存储段名称(.text, .data等) |
示例:
1 | my_strtab = '\x00Scrt1.o\x00__abi_tag\x00crtstuff.c\x00deregister_tm_clones\x00__do_global_dtors_aux\x00completed.0\x00eat\x00__libc_start_main@GLIBC_2.34\x00sem_wait@GLIBC_2.34\x00' |
.interp
基本概念
.interp 段(解释器段)是动态链接的 ELF 可执行文件中的一个特殊段,用于指定程序运行时所需的动态链接器路径。
核心特性
| 特性 | 说明 |
|---|---|
| 段名 | .interp(interpreter 的缩写) |
| 内容 | 一个以空字符结尾的字符串,表示动态链接器的文件路径 |
| 示例路径 | /lib64/ld-linux-x86-64.so.2(64位系统) /lib/ld-linux.so.2(32位系统) |
| 作用 | 告诉系统使用哪个动态链接器来加载和运行该程序 |
.dynamic
1 | typedef struct |
基本概念
.dynamic 段是动态链接 ELF 文件的核心结构,包含了动态链接器所需的所有基本信息。它由 Elf*_Dyn 结构体数组组成,每个条目描述一个动态链接相关的信息。
常见的动态段类型(d_tag)
| 类型 | 值类型 | 描述 |
|---|---|---|
| DT_SYMTAB | d_ptr |
动态符号表(.dynsym)的地址 |
| DT_STRTAB | d_ptr |
动态字符串表(.dynstr)的地址 |
| DT_STRSZ | d_val |
动态字符串表的大小 |
| DT_HASH | d_ptr |
符号哈希表的地址(用于快速符号查找) |
| DT_GNU_HASH | d_ptr |
GNU 扩展的哈希表地址 |
| DT_SONAME | d_val |
共享库在字符串表中的名称偏移量 |
| DT_RPATH | d_val |
库搜索路径(已废弃,使用 DT_RUNPATH) |
| DT_RUNPATH | d_val |
库搜索路径 |
| DT_INIT | d_ptr |
初始化函数地址(在库加载时调用) |
| DT_FINI | d_ptr |
终止函数地址(在程序结束时调用) |
| DT_NEEDED | d_val |
依赖的共享库名称在字符串表中的偏移量 |
| DT_REL / DT_RELA | d_ptr |
重定位表的地址 |
| DT_RELSZ / DT_RELASZ | d_val |
重定位表的大小 |
| DT_PLTGOT | d_ptr |
全局偏移表(GOT)或过程链接表(PLT)的地址 |
| DT_JMPREL | d_ptr |
PLT 重定位表的地址 |
| DT_PLTRELSZ | d_val |
PLT 重定位表的大小 |
| DT_DEBUG | d_ptr |
调试用途 |
| DT_NULL | - | 标记 .dynamic 段结束 |
查看 .dynamic 段内容

.dynsym
基本概念
动态符号表(.dynsym)是动态链接 ELF 文件中的关键结构,专门用于存储与动态链接相关的符号信息。它只包含那些在模块间共享的符号,不包含模块内部的私有符号。
与静态符号表的对比
| 特性 | 动态符号表(.dynsym) | 静态符号表(.symtab) |
|---|---|---|
| 用途 | 动态链接,运行时符号解析 | 静态链接,调试信息 |
| 内容 | 仅动态链接相关符号 | 所有符号(包括 .dynsym 中的符号) |
| 大小 | 较小,只包含必要符号 | 较大,包含完整符号信息 |
| 运行时 | 保留在内存中,供动态链接器使用 | 通常被 strip 移除,不加载到内存 |
| 必需性 | 动态链接必需 | 调试可选,运行时不必需 |
相关辅助段
| 段名 | 用途 | 说明 |
|---|---|---|
| .dynsym | 动态符号表 | 存储动态链接相关的符号定义 |
| .dynstr | 动态字符串表 | 存储 .dynsym 中符号的名称字符串 |
| .hash | 符号哈希表 | 加速符号查找过程 |
| .gnu.hash | GNU 扩展哈希表 | 更高效的符号哈希表(较新版本) |
.rel.dyn/.rel.data
基本概念
动态链接重定位表用于在程序运行时修正对导入符号的引用。与静态链接在编译时完成重定位不同,动态链接的重定位发生在程序加载时。
两种动态重定位表对比
| 特性 | .rel.dyn(或 .rela.dyn) | .rel.plt(或 .rela.plt) |
|---|---|---|
| 用途 | 数据引用的重定位 | 函数引用的重定位 |
| 修正位置 | .got 和数据段 |
.got.plt |
| 对应静态段 | 相当于 .rel.data |
相当于 .rel.text |
| 重定位类型 | 绝对地址重定位 | PLT 相关的相对重定位 |
这里从参考文章里搬两个延迟绑定的图过来。

