ret2dlresolve

简介

ret2dlresolve 是一种利用程序漏洞(通常是缓冲区溢出)来绕过程序的安全机制并控制程序流程的攻击方式。它利用了动态链接库

(DLL)解析的过程,攻击者通过修改程序的控制流,迫使程序调用恶意的共享库函数。具体来说,攻击者通过构造特定的输入,使得程

序在执行时调用一个由攻击者控制的函数,从而实现远程代码执行。该攻击通常针对未启用安全防护(如地址空间布局随机化 ASLR 或栈

保护)的程序。

前置知识

ELF文件格式

ELF 概述

ELF 的全称是 Executable and Linkable Format,即可执行可链接格式。它定义了一种结构化的方式,来存储程序的各种信息,以便于操作系统进行加载、执行以及链接器进行代码和数据的链接。

ELF文件主要分为三种类型:

  1. 可重定位文件:通常以 .o 结尾。包含代码和数据,可以与其他目标文件链接生成可执行文件或共享库。
  2. 可执行文件:可以直接运行的程序。例如 /bin/bash
  3. 共享目标文件:通常以 .so 结尾。包含代码和数据,可以在两种情况下被使用:
    • 链接时:与可重定位文件和其他共享目标文件一起,被链接器处理,生成新的可执行文件或共享库。
    • 运行时:被动态链接器加载到进程的地址空间,与可执行文件合并,形成完整的进程映像。

ELF 文件结构布局

ELF文件从结构上可以分为两大部分:“链接视图”“执行视图”

  • 链接视图:以 为单位组织,主要供链接器使用。
  • 执行视图:以 为单位组织,主要供加载器(操作系统)使用。

一个典型的ELF文件布局如下所示:

)

1
2
3
Program Header Table<-- 执行视图:描述如何创建进程映像(段信息)
.text、.data、.bss... <-- 各种节,包含实际的代码、数据等
Section Header Table <-- 链接视图:描述所有节的信息

ELF头

位于文件开头,是整个ELF文件的“总目录”。可以使用 readelf -h <file> 查看。

1

主要包含以下信息:

  • 魔数:前16个字节,包括 0x7F 和字符串 ELF,用于标识这是一个ELF文件。
  • 文件类:标识是32位(ELF32)还是64位(ELF64)文件。
  • 数据编码:标识是小端序(Little Endian)还是大端序(Big Endian)。
  • ELF版本:通常是当前版本 1
  • OS/ABI:标识目标操作系统ABI。
  • 文件类型:指明是哪种类型的ELF文件(可重定位、可执行、共享库等)。
  • 机器类型:指明需要的体系结构(如 x86, ARM, MIPS等)。
  • 程序入口地址:可执行文件的入口点虚拟地址。
  • 程序头表起始位置、大小和表项数量
  • 节头表起始位置、大小和表项数量
  • 节头表字符串表索引:用于存储节名称的字符串表在节头表中的索引。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/* The ELF file header.  This appears at the start of every ELF file.  */

#define EI_NIDENT (16)

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;
  • e_ident[EI_NIDENT]

1

红色区域:从左到右

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
2
3
4
5
6
7
8
9
10
值(十六进制)  宏定义          描述
0x00 ET_NONE 未知类型
0x01 ET_REL 可重定位文件(例如:.o文件)
0x02 ET_EXEC 可执行文件
0x03 ET_DYN 共享目标文件(共享库)
0x04 ET_CORE 核心转储文件
0xFE00 ET_LOOS 操作系统特定范围开始
0xFEFF ET_HIOS 操作系统特定范围结束
0xFF00 ET_LOPROC 处理器特定范围开始
0xFFFF ET_HIPROC 处理器特定范围结束
  • e_machine :

00 3E

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
#ifndef _LINUX_ELF_EM_H
#define _LINUX_ELF_EM_H

/* These constants define the various ELF target machines */
#define EM_NONE 0
#define EM_M32 1
#define EM_SPARC 2
#define EM_386 3
#define EM_68K 4
#define EM_88K 5
#define EM_486 6 /* Perhaps disused */
#define EM_860 7
#define EM_MIPS 8 /* MIPS R3000 (officially, big-endian only) */
/* Next two are historical and binaries and
modules of these types will be rejected by
Linux. */
#define EM_MIPS_RS3_LE 10 /* MIPS R3000 little-endian */
#define EM_MIPS_RS4_BE 10 /* MIPS R4000 big-endian */

#define EM_PARISC 15 /* HPPA */
#define EM_SPARC32PLUS 18 /* Sun's "v8plus" */
#define EM_PPC 20 /* PowerPC */
#define EM_PPC64 21 /* PowerPC64 */
#define EM_SPU 23 /* Cell BE SPU */
#define EM_ARM 40 /* ARM 32 bit */
#define EM_SH 42 /* SuperH */
#define EM_SPARCV9 43 /* SPARC v9 64-bit */
#define EM_H8_300 46 /* Renesas H8/300 */
#define EM_IA_64 50 /* HP/Intel IA-64 */
#define EM_X86_64 62 /* AMD x86-64 */
#define EM_S390 22 /* IBM S/390 */
#define EM_CRIS 76 /* Axis Communications 32-bit embedded processor */
#define EM_M32R 88 /* Renesas M32R */
#define EM_MN10300 89 /* Panasonic/MEI MN10300, AM33 */
#define EM_OPENRISC 92 /* OpenRISC 32-bit embedded processor */
#define EM_ARCOMPACT 93 /* ARCompact processor */
#define EM_XTENSA 94 /* Tensilica Xtensa Architecture */
#define EM_BLACKFIN 106 /* ADI Blackfin Processor */
#define EM_UNICORE 110 /* UniCore-32 */
#define EM_ALTERA_NIOS2 113 /* Altera Nios II soft-core processor */
#define EM_TI_C6000 140 /* TI C6X DSPs */
#define EM_HEXAGON 164 /* QUALCOMM Hexagon */
#define EM_NDS32 167 /* Andes Technology compact code size
embedded RISC processor family */
#define EM_AARCH64 183 /* ARM 64 bit */
#define EM_TILEPRO 188 /* Tilera TILEPro */
#define EM_MICROBLAZE 189 /* Xilinx MicroBlaze */
#define EM_TILEGX 191 /* Tilera TILE-Gx */
#define EM_ARCV2 195 /* ARCv2 Cores */
#define EM_RISCV 243 /* RISC-V */
#define EM_BPF 247 /* Linux BPF - in-kernel virtual machine */
#define EM_CSKY 252 /* C-SKY */
#define EM_LOONGARCH 258 /* LoongArch */
#define EM_FRV 0x5441 /* Fujitsu FR-V */

/*
* This is an interim value that we will use until the committee comes
* up with a final number.
*/
#define EM_ALPHA 0x9026

/* Bogus old m32r magic number, used by old tools. */
#define EM_CYGNUS_M32R 0x9041
/* This is the old interim value for S/390 architecture */
#define EM_S390_OLD 0xA390
/* Also Panasonic/MEI MN10300, AM33 */
#define EM_CYGNUS_MN10300 0xbeef


#endif /* _LINUX_ELF_EM_H */
  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
typedef struct
{
Elf32_Word p_type; /* Segment type */
Elf32_Off p_offset; /* Segment file offset */
Elf32_Addr p_vaddr; /* Segment virtual address */
Elf32_Addr p_paddr; /* Segment physical address */
Elf32_Word p_filesz; /* Segment size in file */
Elf32_Word p_memsz; /* Segment size in memory */
Elf32_Word p_flags; /* Segment flags */
Elf32_Word p_align; /* Segment alignment */
} Elf32_Phdr;

typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;

  • 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct
{
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
  • 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 要全面得多。gdbnm 等工具主要就是读取这个表来显示符号信息。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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;

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;
  1. 作用与存在:

    • 主要作用:在静态链接过程中,链接器用它来解析符号(函数、变量名)的引用和定义。
    • 次要作用:为调试器 (gdb) 提供符号信息,方便开发者调试。
    • 发布与安全:程序发布时通常不需要符号表。可以通过 strip 命令将其从文件中移除,以减小体积并增加逆向分析难度(这就是为什么有些 Pwn 题附件没有函数名)。
  2. 数据结构:
    符号表是 Elf32_Sym(32位)或 Elf64_Sym(64位)结构体的数组。每个结构体描述一个符号。

    Elf32_Sym 结构体字段详解:

    字段 C 类型 描述
    st_name Elf32_Word 符号名偏移。指向 .strtab (字符串表) 中的索引,实际符号名在那里以字符串形式存储。
    st_value Elf32_Addr 符号的值/地址。其含义根据文件类型和符号类型而变化: • 目标文件 (.o):对于已定义的非COMMON块符号,表示在它所在 Section 中的偏移。 • 目标文件 (.o):对于 COMMON块 符号(如未初始化的全局变量),表示对齐要求。 • 可执行文件:表示符号的**虚拟内存地址 (Virtual Address)**。
    st_size Elf32_Word 符号的大小。例如,一个函数有多大,一个全局变量占多少字节。为 0 表示大小未知或为零。
    st_info unsigned char 符号类型与绑定信息。一个字节,高4位表示类型,低4位表示绑定。 • 绑定 (Binding): STB_LOCAL (局部), STB_GLOBAL (全局), STB_WEAK (弱符号)。 • 类型 (Type): STT_NOTYPE (无类型), STT_OBJECT (数据对象), STT_FUNC (函数) 等。
    st_other unsigned char 符号可见性。通常为 0。
    st_shndx Elf32_Section 符号所在 Section 的索引。这是一个关键字段,它告诉链接器或调试器这个符号“住在哪里”: • 如果是一个普通的已定义符号,其值为对应 Section(如 .text, .data, .bss)的索引。 • SHN_ABS:符号是一个绝对值,在链接时不会改变(例如,初始值不为 0 的全局变量,其值固定)。 • SHN_COMMON:符号是一个 COMMON块,通常是未初始化的全局变量。它在链接时由链接器分配空间(通常在 .bss 段)。 • SHN_UNDEF:符号未在本文件中定义。这通常意味着该符号(如 printf)是在其他目标文件或库中定义的。
.rel.text/.rel.data
1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
Elf32_Addr r_offset; /* Address */
Elf32_Word r_info; /* Relocation type and symbol index */
} Elf32_Rel;

typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;
  1. 目的与作用:

    • 解决地址未知问题:在编译生成目标文件 (.o) 时,代码中引用的外部函数和全局变量的最终内存地址是未知的。
    • 为链接器提供”修补”指南:重定位表就是告诉链接器:”在最终生成可执行文件时,请到这个文件的这些位置,用正确的地址替换掉当前的临时值。”
    • 类型:主要分为代码重定位 (.rel.text) 和数据重定位 (.rel.data)。
  2. 数据结构:
    重定位表是 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;

typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un;
} Elf64_Dyn;

基本概念

.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 段内容

1

.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 相关的相对重定位

这里从参考文章里搬两个延迟绑定的图过来。

1

1

ret2dlresolve

参考文章

从0-1详解剖析ret2dlresolve-先知社区

新手向]ret2dl-resolve详解 - 知乎

ret2dl_resolve - 狒猩橙 - 博客园

ret2dlresolve超详细教程(x86&x64)-CSDN博客