• 学了点PE文件格式,现在可以趁热打铁,来学习一下ELF文件格式。

ELF文件格式概述

  • ELF文件全称为Executable and Linkable Format的缩写,中文意思其实就是可执行和可链接格式。其实ELF文件分为三大类:

    • 可执行文件无后缀
    • 可重定位文件.o
    • 共享的目标文件(动态链接库).so
  • ELF文件格式总体上就是如下图所示,这里介绍都是以64位的ELF文件做介绍:

1
2
3
4
5
6
7
8
9
+------------------------+
| ELF Header | # ELF文件的基本信息
+------------------------+
| Program Header Table | # 程序头表(用于运行时加载)
+------------------------+
| Sections (.text, .data, .bss, .rodata, .symtab, .strtab, .got, .plt等)
+------------------------+
| Section Headers | # 各个节的元数据
+------------------------+
  • ELF文件结构体有如下:
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
Elf64_Ehdr  			// ELF文件头ELF Header
Elf64_Phdr // ELF程序头表Program Header Table
Sections // 具体的节,有的节是数据和指令没有数据结构,有点节是有数据结构的
.null // 空节(必须存在)
.interp // 指向解释器路径 /lib64/ld-linux.so.2
.note.gnu.property // GNU特性标记
.note.gnu.build-id // 编译标识
.note.ABI-tag // ABI 兼容信息
.gnu.hash // GNU风格的符号哈希表
.dynsym // 动态符号表,有数据结构,Elf64_Sym
.dynstr // 动态符号表字符串标
.gnu.version // 版本控制信息
.gnu.version_r // 版本引用关系
.rela.dyn // 动态重定位表
.rela.plt // 重定位表用于PLT
.init // 构造函数(C 运行时初始化)
.plt // Procedure Linkage Table (PLT表) 过程链接表
.plt.got //
.text // 代码段,存放二进制指令,无数据结构
.fini //
.rodata // 只读数据,无数据结构
.eh_frame_hdr //
.eh_frame //
.got // 全局偏移表
.got.plt //
.data // 数据段,存放已经初始化的数据,无数据结构
.bss // 数据段,存放未初始化的数据,通常是未初始化的全局变量,无数据结构
.symtab // 静态符号表,有数据结构,Elf64_Sym
.strtab // 字符串表(对应符号名)
.rel.plt // 重定位表,有数据结构,Elf64_Rela
Elf64_Shdr // ELF节头表Section Headers
  • 下图展现了两种视图,给出了ELF文件的两种视角:
    • 链接视图,从编译器/链接器角度出发,方便将代码组织成可重定位文件或者可执行文件,该文件放在磁盘中的状态
    • 执行视图,从加载起/运行时角度出发,方便将ELF文件加载到内存中执行,该文件载入到内存中的状态
    • 在磁盘中对应的一些Section1这里我们就称为节,而载入到内存后就变成Segment我们称为段

image-20250704164851652

ELF文件格式

详细了解一下ELF文件格式,ELF文件格式相关结构体基本上都在glibc/elf/elf.h这个头文件中

elf_header

e_ident

  • ELF64_Ehdr这个结构体其实就是ELF文件头。其结构体如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; // 存放ELF模数以及ELF位数、字节序、版本
Elf64_Half e_type; // 表示文件类型,是可执行文件还是共享库
Elf64_Half e_machine; // 表示机器架构,必然x86_64、x86、MIPS
Elf64_Word e_version; // 版本号
Elf64_Addr e_entry; // 程序的入口虚拟地址(执行从此开始)
Elf64_Off e_phoff; // 程序头表在文件中的偏移
Elf64_Off e_shoff; // 节区头表在文件中的偏移
Elf64_Word e_flags; // 与处理器相关的标志,多数情况下为0
Elf64_Half e_ehsize; // ELF头部本身的大小
Elf64_Half e_phentsize; // 程序头表中每个条目的大小
Elf64_Half e_phnum; // 程序头表中的条目数量
Elf64_Half e_shentsize; // 节区头表中每个条目的大小
Elf64_Half e_shnum; // 节区头表中的条目数量
Elf64_Half e_shstrndx; // 包含节区名称字符串的节区在节区头表中的索引
} Elf64_Ehdr;
  • Elf64_Ehdr在这个所在的位置就是ELF文件的开头前0x40字节

image-20250705094300200

  • 开头第一行是e_ident这个数组,这个数组记录了如下信息
1
2
3
4
5
6
7
8
file_identification		// 文件头识别0x7F 0x45 0x4C 0x46
ei_class_2 // 文件类型0x1为32位, 0x2为64位
ei_data // 大小端序0x1为小端序,0x2为大端序
ei_version // 版本号,一般为1
ei_osabi // 目标操作系统ABI,如0=System V, 3=Linux
ei_abiversion // ABI版本,通常为1
ei_pad // 填充,之后可以拓展
ei_nident_size // 也是相当于填充

image-20250705094713527

e_type~e_entry

  • 这几个标识其实都非常重要,尤其是e_entry标明了程序的入口地址
1
2
3
4
Elf64_Half	e_type;				// 0x10表示文件类型,是可执行文件还是共享库 0x1.o文件  0x2可执行文件 0x3 .so文件
Elf64_Half e_machine; // 0x12表示机器架构,必然x86_64、x86、MIPS
Elf64_Word e_version; // 0x14版本号
Elf64_Addr e_entry; // 0x18程序的入口虚拟地址(执行从此开始)

image-20250705095540328

e_phoff与e_shoff

1
2
Elf64_Off	e_phoff;				// 程序头表在文件中的偏移,0x40表示程序头表的开始地址
Elf64_Off e_shoff; // 节区头表在文件中的偏移,0x2138表示节区头表的开始地址

image-20250705102622086

image-20250705102858639

e_flags~e_shstrndx

1
2
3
4
5
6
7
8
Elf64_Word	e_flags;			// 与处理器相关的标志,多数情况下为0
Elf64_Half e_ehsize; // ELF头部本身的大小 0x40
Elf64_Half e_phentsize; // 程序头表中每个条目的大小 可以通过e_phoff配合e_phentsize确定每个程序头表的起始地址下一个 PHT 条目 = e_phoff + n * e_phentsize
Elf64_Half e_phnum; // 程序头表中的条目数量 0x9
Elf64_Half e_shentsize; // 节区头表中每个条目的大小 0x40
Elf64_Half e_shnum; // 节区头表中的条目数量 0x1B
Elf64_Half e_shstrndx; // 包含节区名称字符串的节区在节区头表中的索引 .shstrtab 节头在文件中的偏移 = e_shoff + e_shstrndx * e_shentsize

image-20250705103208205

elf_program_header_table

  • 程序头表的开始是0x40位置,也就是ELF文件头中的e_phoff的值,并且程序头表相当于Elf64_Phdr phd[count]这样的一个结构体数组。这里就拿一个分析结构即可。
  • 并且每个程序头表的大小在ELF文件头中已经指明了即Elf64_Half e_shentsize,这里其实在AWDP上通防的时候已经接触到了
1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
Elf64_Word p_type; // 段的类型,决定此段的作用
Elf64_Word p_flags; // 段的访问权限标志,读/写/执行
Elf64_Off p_offset; // 该段在文件中的偏移
Elf64_Addr p_vaddr; // 该段被加载到内存时的虚拟地址
Elf64_Addr p_paddr; // 物理地址(通常在现代系统中未使用.保留为0)
Elf64_Xword p_filesz; // 文件中该段的大小
Elf64_Xword p_memsz; // 内存中该段占用的大小
Elf64_Xword p_align; // 段的对齐要求
} Elf64_Phdr;

image-20250705105114610

p_type与p_flags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Elf64_Word	p_type;			// 段的类型,决定此段的作用
//以下是一些段类型的宏定义
0x00000000 PT_NULL //忽略此条目
0x00000001 PT_LOAD //可加载段,如代码段数据段
0x00000002 PT_DYNAMIC //动态链接信息
0x00000003 PT_INTERP //动态链接路径
0x00000004 PT_NOTE //备注段
0x00000006 PT_PHDR //程序头表自身所在段

Elf64_Word p_flags; // 段的访问权限标志,读/写/执行
// 一下是读/写/执行的标志位
0x1 PF_X 可执行
0x2 PF_W 可写
0x4 PF_R 可读
// 如果要可读可写就使用与操作 PF_W | PF_R

image-20250705105817940

p_offset与p_vaddr

  • p_offset表示该段在文件中的起始地址,而p_vaddr表示该段在内存中的起始地址。由于该程序开启了PIE保护,所以该ELF文件中p_offsetp_vaddr的值是一样的。

image-20250705110859461

  • 如果没有开启PIE保护,一般会是0x400040程序载入到内存中一般基址都为0x400000(在64位下),下面就是另一个没有开PIE保护的程序,就会发现p_offsetp_vaddr不同

image-20250705111130061

p_filesz~p_align

1
2
3
4
Elf64_Xword	p_filesz;		// 文件中该段的大小
Elf64_Xword p_memsz; // 内存中该段占用的大小
Elf64_Xword p_align; // 段的对齐要求
// 貌似没什么特殊的情况有特殊的情况后面会补充

image-20250705111907018

elf_section_headers

  • 与上面的program_header_table有点类似,程序头表的开始位置,也就是ELF文件头中的e_shoff的值,并且程序头表相当于Elf64_Shdr shd[count]这样的一个结构体数组。这里就拿一个分析结构即可。

  • elf_program_header_table其实描述的是SectionSegment的映射关系,而section主要就是这个节在文件存储中的相关信息

  • 结构体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
Elf64_Word sh_name; // 当前节名在 .shstrtab中的偏移
Elf64_Word sh_type; // 节的类型
Elf64_Xword sh_flags; // 节的标志可执行、是否分配内存
Elf64_Addr sh_addr; // 程序执行时节的虚拟地址
Elf64_Off sh_offset; // 节文件偏移
Elf64_Xword sh_size; // 节的大小,以字节为单位
Elf64_Word sh_link; // 链接到其他节区
Elf64_Word sh_info; // 附加的节信息
Elf64_Xword sh_addralign; // 节的对齐要求
Elf64_Xword sh_entsize; // 若节是一个表,此字段是每个条目的大小,如符号表
} Elf64_Shdr;

image-20250705112831439

sh_name~sh_flags

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Elf64_Word	sh_name;		// 当前节名在 .shstrtab中的偏移,之后在.shstrtab再详细说明
Elf64_Word sh_type; // 节的类型
//下面是描述节类型
0x0 SHT_NULL // 空节
0x1 SHT_PROGBITS // 代码、数据段
0x2 SHT_SYMTAB // 符号表
0x3 SHT_STRTAB // 字符串表
0x4 SHT_RELA // 重定位表
0x9 SHT_REL // 重定位表
0xB SHT_DYNSYM // 动态符号表


Elf64_Xword sh_flags; // 节的标志可执行、是否分配内存
0x1 SHF_WRTE // 可写
0x2 SHF_ALLOC // 程序运行时会加载到内存
0x4 SHF_EXECINSTR // 可执行代码段

image-20250705113556971

sh_addr~sh_offset

  • sh_offset其实就是该节头对应的节在文件的起始地址,而sh_addr就是程序运行时虚拟地址的起始位置

image-20250705132346627

sh_size~sh_entsize

1
2
3
4
5
Elf64_Xword	sh_size;		// 节的大小,以字节为单位
Elf64_Word sh_link; // 链接到其他节区
Elf64_Word sh_info; // 附加的节信息
Elf64_Xword sh_addralign; // 节的对齐要求
Elf64_Xword sh_entsize; // 若节是一个表,此字段是每个条目的大小,如符号表

image-20250705132613809

ELF64_Chdr

1
2
3
4
5
6
7
typedef struct
{
Elf64_Word ch_type; /* Compression format. */
Elf64_Word ch_reserved;
Elf64_Xword ch_size; /* Uncompressed data size. */
Elf64_Xword ch_addralign; /* Uncompressed data alignment. */
} Elf64_Chdr;

ELF64_Sym

1
2
3
4
5
6
7
8
9
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;

ELF64_Syminfo

1
2
3
4
5
typedef struct
{
Elf64_Half si_boundto; /* Direct bindings, symbol bound to */
Elf64_Half si_flags; /* Per symbol flags */
} Elf64_Syminfo;

ELF64_Rel

1
2
3
4
5
6
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;

ELF64_Rela

  • 结构体如下:
1
2
3
4
5
6
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;

ELF64_Dyn

1
2
3
4
5
6
7
8
9
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;

ELF64_Verdef

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
Elf64_Half vd_version; /* Version revision */
Elf64_Half vd_flags; /* Version information */
Elf64_Half vd_ndx; /* Version Index */
Elf64_Half vd_cnt; /* Number of associated aux entries */
Elf64_Word vd_hash; /* Version name hash value */
Elf64_Word vd_aux; /* Offset in bytes to verdaux array */
Elf64_Word vd_next; /* Offset in bytes to next verdef
entry */
} Elf64_Verdef;

ELF64_Verdaux

1
2
3
4
5
6
typedef struct
{
Elf64_Word vda_name; /* Version or dependency names */
Elf64_Word vda_next; /* Offset in bytes to next verdaux
entry */
} Elf64_Verdaux;

ELF64_Verneed

1
2
3
4
5
6
7
8
9
10
typedef struct
{
Elf64_Half vn_version; /* Version of structure */
Elf64_Half vn_cnt; /* Number of associated aux entries */
Elf64_Word vn_file; /* Offset of filename for this
dependency */
Elf64_Word vn_aux; /* Offset in bytes to vernaux array */
Elf64_Word vn_next; /* Offset in bytes to next verneed
entry */
} Elf64_Verneed;

ELF64_Vernaux

1
2
3
4
5
6
7
8
9
10
typedef struct
{
Elf64_Word vna_hash; /* Hash value of dependency name */
Elf64_Half vna_flags; /* Dependency specific information */
Elf64_Half vna_other; /* Unused */
Elf64_Word vna_name; /* Dependency name string offset */
Elf64_Word vna_next; /* Offset in bytes to next vernaux
entry */
} Elf64_Vernaux;

ELF64_auxv_t

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
uint64_t a_type; /* Entry type */
union
{
uint64_t a_val; /* Integer value */
/* We use to have pointer elements added here. We cannot do that,
though, since it does not work when using 32-bit definitions
on 64-bit platforms and vice versa. */
} a_un;
} Elf64_auxv_t;

ELF64_Nhdr

1
2
3
4
5
6
typedef struct
{
Elf64_Word n_namesz; /* Length of the note's name. */
Elf64_Word n_descsz; /* Length of the note's descriptor. */
Elf64_Word n_type; /* Type of the note. */
} Elf64_Nhdr;

ELF64_Move

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf64_Xword m_value; /* Symbol value. */
Elf64_Xword m_info; /* Size and index. */
Elf64_Xword m_poffset; /* Symbol offset. */
Elf64_Half m_repeat; /* Repeat count. */
Elf64_Half m_stride; /* Stride info. */
} Elf64_Move;

ELF64_Lib

1
2
3
4
5
6
7
8
typedef struct
{
Elf64_Word l_name; /* Name (string table index) */
Elf64_Word l_time_stamp; /* Timestamp */
Elf64_Word l_checksum; /* Checksum */
Elf64_Word l_version; /* Interface version */
Elf64_Word l_flags; /* Flags */
} Elf64_Lib;