郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

在写PE结构解析的时候,以为ELF结构没必要去看文件结构,直到后面做了PWN题发现不是这样的,所有还是学习下ELF结构,然后再写个解析器

常见结构区分

目前,PC平台流行的可执行文件格式(Executable)主要包含如下两种,它们都是COFF(Common File Format)格式的变种

  • Windows下的PE(Portable Executable)
  • Linux下的ELF(Executable Linkable Format)

源代码经过编译后但未进行连接的那些中间文件(Windows的.obj和Linux的.o),它与可执行文件的格式非常相似,所以一般跟可执行文件格式一起采用同一种格式存储。

其中动态链接库(DDL,Dynamic Linking Library)和静态链接库(Static Linking Library)的格式都和当前系统对应的可执行文件结构一样

  • 动态链接库:Windows的.dll、Linux的.so
  • 静态链接库:Windows的.lib、Linux的.a

ELF

文件格式

ELF文件有三种类型,可以通过ELF Header中的e_type成员进行区分:

  • 可重定位文件(Relocatable File)ETL_REL。一般为.o文件,可以与其他目标文件链接来创建可执行文件或共享目标文件的代码和数据。静态链接库属于可重定位文件
  • 可执行文件(Executable File)ET_EXEC。可以执行的一个程序,此文件规定了exec()如何创建一个程序的进程映像。
  • 共享目标文件(Shared Object File):ET_DYN。一般为.so文件。
    • 链接器将其与其他可重定位文件、共享目标文件链接成新的目标文件;
    • 动态链接器(Dynamic Linker)将其与某个可执行文件或其他共享目标文件结合一个可执行文件,创建进程映像。

img

如图所示,为ELF文件的基本结构,主要由四部分组成:

  • ELF Header
  • ELF Program Header Table(或称Program Headers、程序头)
  • ELF Section Header Table(或称Section Headers、节头表)
  • ELF Section

从图中,就能看出它们各自的数据结构以及相互之间的索引关系。下面依次介绍。

png

段(Segment)与节(Section)的区别在于,段是程序执行的必要组成,当多个目标文件链接成一个可执行文件时,会将相同权限的节合并到一个段中。相比而言,节的粒度更小。


ELF Header

文件的最开始几个字节给出如何解释文件的提示信息。 这些信息独立于处理器, 也独立于文件中的其余内容。

#define EI_NIDENT 16
typedef struct {
unsigned char e_ident[EI_NIDENT]; //magic number
Elf32_Half e_type; //Ojbect file type
Elf32_Half e_machine; //Architecture
Elf32_Word e_version; //Object file version
Elf32_Addr e_entry; //entry point
Elf32_Off e_phoff; //程序头内容在文件的偏移量
Elf32_off e_shoff; //段头内容在文件的偏移量
Elf32_Word e_flags;
Elf32_Half e_ehsize; //elf头部大小
Elf32_Half e_phentsize; //程序头部表格的表项大小
Elf32_Half e_phnum; //程序头的个数Program header
Elf32_Half e_shentsize; //节区头部表格的表项大小
Elf32_Half e_shnum; //段头的个数Section header
Elf32_Half e_shstrndx; //String段在整个段列表中的索引值
}Elf32_Ehdr;

详细解释

e_ident

数组给出了ELF的一些标识信息,这个数组中不同下标的含义如表所示:

png

这些索引访问包含以下数值的字节:

  • EI_MAG0 到 EI_MAG3:魔数(Magic Number),标志此文件是一个ELF目标文件。

    名称            取值         位置
    EI_MAG0 0x7f e_ident[EI_MAG0]
    EI_MAG1 'E' e_ident[EI_MAG1]
    EI_MAG2 'L' e_ident[EI_MAG2]
    EI_MAG3 'F' e_ident[EI_MAG3]
  • EI_CLASS:标识文件的类别,或者说,容量

    名称             取值            位置
    ELFCLASSNONE 0 非法类别
    ELFCLASS32 1 32位目标
    ELFCLASS64 2 64位目标
    ps:ELFCLASS32支持虚存范围4GB,ELFCLASS64是为64位预留的,不过文件中的其他内容都没有针对64位定义
  • EI_DATA:字节e_ident[EI_DATA] 给出处理器特定数据的数据编码方式。

    名称            取值              位置
    ELFDATANONE 0 非法数据编码
    ELFDATA2LSB 1 高位在前
    ELFDATA2MSB 2 低位在前
  • EI_VERSION:ELF头部的版本号码,目前此值必须是EV_CURRENT。

  • EI_PAD:标记e_ident中未使用字节的开始,初始化为0。


e_type

目标文件类型

名称            取值          含义
ET_NONE 0 未知目标文件格式
ET_REL 1 可重定位文件
ET_EXEC 2 可执行文件
ET_DYN 3 共享目标文件
ET_CORE 4 Core文件(转储格式)
ET_LOPROC 0xff00 特定处理器文件
ET_HIPROC 0xffff 特定处理器文件
ps:ET_LOPROC和ET_HIPROC之间的取值用来标识与处理器相关的文件格式

e_machine

给出文件的目标体系结构类型

名称            取值          含义
EM_NONE 0 未指定
EM_M32 1 AT&T WE 32100
EM_SPARC 2 SPARC
EM_386 3 Intel 80386
EM_68K 4 Motorola 68000
EM_88K 5 Motorola 88000
EM_860 7 Intel 80860
EM_MIPS 8 MIPS RS3000
ps:其他值都是保留的。特定处理器的ELF名称会使用机器名来进行区分。

e_version

目标文件版本

名称            取值          含义
EV_NONE 0 非法版本
EV_CURRENT 1 当前版本

e_entry

程序入口的虚拟地址,如果目标文件没有程序入口,可以为0


e_phoff

程序头部表格(Program Header Table)的偏移量(按字节计算)。如果文件没有程序头部表格,可以为0


e_shoff

节区头部表格(Section Header Table)的偏移量(按字节计算)。如果文件没有节区头部表格,可以为0


e_flags

保存与文件相关的,特定于处理器的标志。标志名称采用EF_machine_flag的格式


e_ehsize

ELF头部的大小(以字节计算)


e_phentsize

程序头部表格的表项大小(按字节计算)


e_phnum

程序头部表格的表项数目。可以为0


e_shentsize

节区头部表格的表项大小(按字节计算)


e_shnum

节区头部表格的表项数目。可以为0


e_shstrndx

节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为SHN_UNDEF

实例演示

#Linux ubuntu 5.4.0-55-generic #61-Ubuntu SMP Mon Nov 9 20:49:56 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
ascotbe@ubuntu:~$ readelf -h /bin/sh
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x5cd0
Start of program headers: 64 (bytes into file)
Start of section headers: 127896 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 30
Section header string table index: 29

ELF文件结构示意图中定义的ELF_Endr的各个成员的含义与readelf具有对应关系:

成员 含义
e_ident Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
class: ELF64
Data: 2’s complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
e_type Type: DYN (Shared object file)
共享目标文件
e_machine Advanced Micro Devices X86-64
ELF文件的CPI平台属性
e_version Version: 0x1
ELF版本号。一般为常数1
e_entry Entry point address: 0x5cd0
入口地址,规定ELF程序的入口虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。
e_phoff Start of program headers: 64 (bytes into file)
e_shoff Start of section headers: 127896 (bytes into file)
Section Header Table 在文件中的偏移
e_word Flags: 0x0
ELF标志位,用来标识一些ELF文件平台相关的属性
e_ehsize Size of this header: 64(bytes)
ELF Header本身的大小
e_phentsize Size of program headers:56 (bytes)
e_phnum Number of program headers: 13
e_shentsize Size of section headers: 64 (bytes)
单个Section Header大小
e_shnum Number of section headers: 30
Section Header的数量
e_shstrndx Section header string table index: 29
Section Header字符串在Section Header Table中的索引

ELF Section Header Table

ELF节头表是一个节头数组。每一个节头都描述了其所对应的节的信息,如节名、节大小、在文件中的偏移、读写权限等。编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。

e_shoff成员给出从文件头到节区头部表格的偏移字节数;e_shnum给出表格中条目数目;e_shentsize给出每个项目的字节数。从这些信息中可以确切地定位节区的具体位置、长度。

节区头部表格中比较特殊的几个小标如下:

名称 取值 说明
SHN_UNDEF 0 标记未定义的、缺失的、不相关的,或者没有含义的节区引用
SHN_LORESERVE 0xFF00 保留索引的下界
SHN_LOPROC 0xFF00 保留给处理器特殊的语义
SHN_HIPROC 0xFF1F 保留给处理器特殊的语义
SHN_ABS 0xFFF1 包含对应引用量的绝对取值。这些值不会被重定位所影响
SHN_COMMON 0xFFF2 相对于此节区定义的符号是公共符号。如FORTRAN中COMMON或者未分配的C外部变量
SHN_HIRESERVE 0xFFFF 保留索引的上界

介于SHN_LORESERVESHN_HIRESERVE之间的表项不会出现在节区头部表中。

每个节区头部可用如下数据结构描述:

typedef struct {
Elf32_Word sh_name; //给出节区名称。是节区头部字符串节区(Section Header String Table Section)的索引。名字是一个NULL结尾的字符串,保存在一个名为.shstrtab的字符串表(可通过Section Header索引到)
Elf32_Word sh_type; //节类型。为节区的内容和语义进行分类。
Elf32_Word sh_flags; //节标志位。节区支持1位形式的标志,这些标志描述了多种属性
Elf32_Addr sh_addr; //节的虚拟地址。如果节区将出现在进程的内存映像中或可被加载,则sh_addr为该节被加载后在进程地址空间中的虚拟地址,给出节区的每一个字节应处的位置。否则,此字段为0
Elf32_Off sh_offset; //节偏移。如果该节存在于文件中,则该节表示在文件中的偏移;否则无意义,如sh_offset对于BSS节来说就是没有意义的。即此成员的取值给出节区的第一个字节与文件头之间的偏移。
Elf32_Word sh_size; //节大小。此成员给出节区的长度(字节数)。除非节区的类型是SHT_NOBITS,否则节区占用文件中的sh_size字节。
Elf32_Word sh_link; //节链接信息。此成员给出节区头部表索引链接,其解释依赖于节区类型
Elf32_Word sh_info; //节链接信息。此成员给出附加信息,其解释依赖于节区类型
Elf32_Word sh_addralign; //节地址对齐方式。某些节区带有地址对齐约束。
Elf32_Word sh_entsize; //节项大小。某些节区中包含固定大小的项目,如符号表,其包含的每个符号所在的大小都一样。
} Elf32_Shdr;

注意:索引为零(SHN_UNDFF)的节区头部也是存在的,尽管此索引标记的是未定义的节区引用,并且节区的内容固定

png

部分字段解析

sh_type

节名是一个字符串,只是在链接和编译过程中有意义,但它不能真正地表示节的类型。对于编译器和链接器来说,主要决定节的属性是节的类型(sh_type)和节的标志位(sh_flags

节的类型相关常量以SHT_开头,上述readelf -S命令执行的结果省略了该前缀。节区类型定义如表:

名称 取值 说明
SHT_NULL 0 此值标志节区头部是非活动的,没有对应的节区。(无效节)
SHT_PROGBITS 1 程序节。此节区包含程序定义的信息,其格式和含义都有程序来解释,代码节、数据节都是这种类型
SHT_SYMTAB 2 此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能会发生变化。一般,SHT_SYMTAB节区提供用于链接编辑(指ld而言)的符号,尽管也可用来实现动态链接
SHT_STRTAB 3 此节区包含字符串表。目标文件可能包含多个字符串表节区
SHT_RELA 4 此节区包含重定位表项,其中可能会有补齐内容(addend),例如32位目标文件中的Elf32_Rela类型。目标文件可能拥有多个重定位节区
SHT_HASH 5 此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表,不过此限制将来可能会解除
SHT_DYNAMIC 6 此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制
SHT_NOTE 7 此节区包含以某种方式来标记文件的信息,即提示信息
SHT_NOBITS 8 表示该节在文件中没有内容,如.bss节。这种类型的节区不占用文件中的空间,其他方面和SHT_PROGBITS相似。尽管此节区不包含任何字节,成员sh_offset中还是会包含概念性的文件偏移
SHT_REL 9 此节区包含重定位表项,其中没有补齐(addends),例如32位目标文件中的Elf32_rel类型。目标文件中可以拥有多个重定位节区
SHT_SHLIB 10 此节区被保留,不过其语义是未规定的。包含此类型节区的程序与ABI不兼容
SHT_DYNSYM 11 动态链接的符号表。作为一个完整的符号表,它可能包含很多对动态链接而言不必要的符号。因此,目标文件也可以包含一个SHT_DYNSYM节区,其中保存动态链接符号的一个最小集合,以节省空间
SHT_LOPROC 0x70000000 这一段(包含两个边界),是保留给处理器专用语义的
SHT_HIPROC 0x7FFFFFFF 这一段(包含两个边界),是保留给处理器专用语义的
SHT_LOUSER 0x80000000 此值给出保留给应用程序的索引下界
SHT_HIUSER 0x8FFFFFFF 此值给出保留给应用程序的索引上界

sh_flags

sh_flags字段定义了一个节区中包含的内容是否可以修改、是否可以执行等消息。如果一个标志位被设置,则该位取值为1。未定义的各位都设置为0。

名称 取值 含义
SHF_WRITE 0x1 表示该节在进程空间中可写
SHF_ALLOC 0x2 表示该节在进程空间中需要分配空间。有些包含指示或控制信息的节不需要在进程空间中分配空间,就不会有这个标志。
SHF_EXECINSTR 0x4 表示该节在进程空间中可以被执行
SHF_MASKPROC 0xF0000000

其中已经定义了的各位含义如下:

  • SHF_WRITE:节区包含进程执行过程中将可写的数据
  • SHF_ALLOC:此节区在进程执行中占用内存。某些控制节区并不出现于目标文件的内存映像中,对于那些节区,此位应设置为0
  • SHF_EXECINSTR:节区包含可执行的机器指令
  • SHF_MASKPROC:所有包含于此掩码中的四位都用于处理器专用的语义

sh_link和sh_info

如果节的类型是与链接有关的(无论是动态链接还是静态链接),如重定位表、符号表等,则sh_linksh_info两个成员所包含的意义如下所示。其它类型的节中这两个成员没有意义。

根据节区类型的不同,sh_link和sh_info的具体含义也有所不同:

png


ELF Sections

节的分类

.text

.text节是保存了程序代码指令的代码节。一段可执行程序,如果存在Phdr,则.text节就会存在于text段中。由于.text节保存了程序代码,所以节类型为SHT_PROGBITS

.rodata

.rodata节保存了只读的数据,如一行c语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能在text段(而不是data段)中找到.rodata节。由于.rodata节是只读的,所以节类型为SHT_PROGBITS

.plt

.plt节也称为过程链接表(Procedure Linkage Table),其包含了动态链接器调用从共享库导入的函数所必需的相关代码。由于.plt节保存了代码,所以节类型为SHT_PROGBITS

.data

.data节存在于data段中,其保存了初始化的全局变量等数据。由于.data节保存了程序的变量数据,所以节类型为SHT_PROGBITS

.bss

.bss节存在于data段中,占用空间不超过4字节,仅表示这个节本来的空间。.bss节保存了未进行初始化的全局数据。程序加载时数据被初始化为0,在程序执行期间可以进行赋值。由于.bss节未保存实际的数据,所以节类型为SHT_NOBITS

.got.plt

.got节保存了全局偏移量。.got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。由于.got .plt节与程序执行有关,所以节类型为SHT_PROGBITS

.dynsym

.dynsym节保存在text段中。其保存了从共享库导入的动态符号表。节类型为SHT_DYNSYM

.dynstr

.dynstr保存了动态链接字符串表,表中存放了一系列字符串,这些字符串代表了符号名称,以空字符作为终止符。

.rel.name .relaname

name根据重定位所适用的节区给定。例如.text节区的重定位节区名字将是:.rel.text或者.rela.text。重定位表保存了重定位相关的信息,这些信息描述了如何在链接或运行时,对ELF目标文件的某些部分或者进程镜像进行补充或者修改。由于重定位表保存了重定位相关的数据,所以节类型为SHT_REL

.hash

.hash节也称为.gnu.hash,其保存了一个用于查找符号的散列表。

.symtab

.symtab节是一个ElfN_Sym的数组,保存了符号信息。节类型为SHT_SYMTAB

.strtab

.strtab节保存的是符号字符串表,表中的内容会被.symtabElfN_Sym结构中的st_name引用。节类型为SHT_STRTAB

.ctors&.dtors

ctors(构造器)节和.dtors(析构器)节分别保存了指向构造函数和析构函数的函数指针,构造函数是在main函数执行之前需要执行的代码;析构函数是在main函数之后需要执行的代码。

我们可以使用readelf工具来查看节头表。

ascotbe@ubuntu:~$ readelf -S /bin/sh
There are 30 section headers, starting at offset 0x1f398:

Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000000318 00000318
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.propert NOTE 0000000000000338 00000338
0000000000000020 0000000000000000 A 0 0 8
[ 3] .note.gnu.build-i NOTE 0000000000000358 00000358
0000000000000024 0000000000000000 A 0 0 4
[ 4] .note.ABI-tag NOTE 000000000000037c 0000037c
0000000000000020 0000000000000000 A 0 0 4
[ 5] .gnu.hash GNU_HASH 00000000000003a0 000003a0
00000000000002d8 0000000000000000 A 6 0 8
[ 6] .dynsym DYNSYM 0000000000000678 00000678
0000000000001158 0000000000000018 A 7 1 8
[ 7] .dynstr STRTAB 00000000000017d0 000017d0
00000000000006b6 0000000000000000 A 0 0 1
[ 8] .gnu.version VERSYM 0000000000001e86 00001e86
0000000000000172 0000000000000002 A 6 0 2
[ 9] .gnu.version_r VERNEED 0000000000001ff8 00001ff8
0000000000000070 0000000000000000 A 7 1 8
[10] .rela.dyn RELA 0000000000002068 00002068
0000000000001b00 0000000000000018 A 6 0 8
[11] .rela.plt RELA 0000000000003b68 00003b68
00000000000007f8 0000000000000018 AI 6 25 8
[12] .init PROGBITS 0000000000005000 00005000
000000000000001b 0000000000000000 AX 0 0 4
[13] .plt PROGBITS 0000000000005020 00005020
0000000000000560 0000000000000010 AX 0 0 16
[14] .plt.got PROGBITS 0000000000005580 00005580
0000000000000020 0000000000000010 AX 0 0 16
[15] .plt.sec PROGBITS 00000000000055a0 000055a0
0000000000000550 0000000000000010 AX 0 0 16
[16] .text PROGBITS 0000000000005af0 00005af0
0000000000011cf5 0000000000000000 AX 0 0 16
[17] .fini PROGBITS 00000000000177e8 000177e8
000000000000000d 0000000000000000 AX 0 0 4
[18] .rodata PROGBITS 0000000000018000 00018000
0000000000001f42 0000000000000000 A 0 0 32
[19] .eh_frame_hdr PROGBITS 0000000000019f44 00019f44
0000000000000804 0000000000000000 A 0 0 4
[20] .eh_frame PROGBITS 000000000001a748 0001a748
00000000000031a8 0000000000000000 A 0 0 8
[21] .init_array INIT_ARRAY 000000000001ef30 0001df30
0000000000000008 0000000000000008 WA 0 0 8
[22] .fini_array FINI_ARRAY 000000000001ef38 0001df38
0000000000000008 0000000000000008 WA 0 0 8
[23] .data.rel.ro PROGBITS 000000000001ef40 0001df40
0000000000000bc8 0000000000000000 WA 0 0 32
[24] .dynamic DYNAMIC 000000000001fb08 0001eb08
00000000000001f0 0000000000000010 WA 7 0 8
[25] .got PROGBITS 000000000001fcf8 0001ecf8
00000000000002f0 0000000000000008 WA 0 0 8
[26] .data PROGBITS 0000000000020000 0001f000
0000000000000240 0000000000000000 WA 0 0 32
[27] .bss NOBITS 0000000000020240 0001f240
0000000000002c30 0000000000000000 WA 0 0 32
[28] .gnu_debuglink PROGBITS 0000000000000000 0001f240
0000000000000034 0000000000000000 0 0 4
[29] .shstrtab STRTAB 0000000000000000 0001f274
000000000000011d 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)

符号表

节的分类中我们介绍了.dynsym节和.symtab节,两者都是符号表。那么它们到底有什么区别呢?存在什么关系呢?

符号是对某些类型的数据或代码(如全局变量或函数)的符号引用,函数名或变量名就是符号名。例如,printf()函数会在动态链接符号表.dynsym中存有一个指向该函数的符号项(以Elf_Sym数据结构表示)。在大多数共享库和动态链接可执行文件中,存在两个符号表。即.dynsym.symtab

.dynsym保存了引用来自外部文件符号的全局符号。如printf库函数。.dynsym保存的符号是.symtab所保存符合的子集,.symtab中还保存了可执行文件的本地符号。如全局变量,代码中定义的本地函数等。

既然.dynsym.symtab的子集,那为何要同时存在两个符号表呢?

通过readelf -S命令可以查看可执行文件的输出,一部分节标志位(sh_flags)被标记为了A(ALLOC)、WA(WRITE/ALLOC)、AX(ALLOC/EXEC)。其中,.dynsym被标记为ALLOC,而.symtab则没有标记。

ALLOC表示有该标记的节会在运行时分配并装载进入内存,而.symtab不是在运行时必需的,因此不会被装载到内存中。.dynsym保存的符号只能在运行时被解析,因此是运行时动态链接器所需的唯一符号.dynsym对于动态链接可执行文件的执行是必需的,而.symtab只是用来进行调试和链接的。

img

上图所示为通过符号表索引字符串表的示意图。符号表中的每一项都是一个Elf_Sym结构,对应可以在字符串表中索引得到一个字符串。该数据结构符号表项的格式如下:

typedef struct {
Elf32_Word st_name; //符号名。该值为该符号名在字符串表中的偏移地址。
Elf32_Addr st_value; //符号对应的值。存放符号的值(可能是地址或位置偏移量)。
Elf32_Word st_size; //符号的大小。
unsigned char st_info; //此成员给出符号的类型和绑定属性。下面给出若干取值和含义的绑定关系
unsigned char st_other; //符号所在的节
Elf32_Half st_shndx; //符号类型及绑定属性
} Elf32_sym;

字符串表

类似于符号表,在大多数共享库和动态链接可执行文件中,也存在两个字符串表。即.dynstr.strtab,分别对应于.dynsymsymtab。此外,还有一个.shstrtab的节头字符串表,用于保存节头表中用到的字符串,可通过sh_name进行索引。

ELF文件中所有字符表的结构基本一致,如上图所示。

重定位表

重定位就是将符号定义和符号引用进行连接的过程。可重定位文件需要包含描述如何修改节内容的相关信息,从而使可执行文件和共享目标文件能够保存进程的程序镜像所需要的正确信息。

重定位表是进行重定位的重要依据。我们可以使用objdump工具查看目标文件的重定位表:

typedef struct {
Elf32_Addr r_offset; //重定位入口的偏移。
//对于可重定位文件来说,这个值是该重定位入口所要修正的位置的第一个字节相对于节起始的偏移
//对于可执行文件或共享对象文件来说,这个值是该重定位入口所要修正的位置的第一个字节的虚拟地址
Elf32_word r_info; //重定位入口的类型和符号
//对于可执行文件和共享目标文件来说,它们的重定位入口是动态链接类型的。
} Elf32_Rel;

typedef struct {
Elf32_Addr r_offset;
Elf32_Word r_info;
Elf32_Word r_addend; //此成员给出一个常量补齐,用来计算将被填充到可重定位字段的数值。
} Elf32_Rela;

参考文章

http://www.chuquan.me/2018/05/21/elf-introduce/
https://paper.seebug.org/papers/Archive/refs/elf/Understanding_ELF.pdf