四、 ELF 文件格式分析
ELF文件(目标文件)格式主要四种:
当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件
共享库。文件保存着代码和合适的数据,用来被下连接编辑器和动态链接器链接。(linux下后缀为 .so 的文件。)
执行文件的格式与上述两种文件的格式之间的区别主要在于观察的角度不同:一种称为连接视图(Linking View),一种称为执行视图(Execution View)
文件保存着一个用来执行的程序。(例如bash,gcc等)
文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为 .a 和 .o 的文件)
可重定向文件:
可执行文件:
共享目标文件:
核心转储文件(core dump):
目标文件既要参与程序链接又要参与程序执行。 出于方便性和效率考虑, 目标文件格式提供了两种并行视图,分别反映了这些活动的不同需求。
ELF header:ELF 文件头
包含了描述整个文件的基本属性,比如 ELF 文件版本、目标机器型号、程序入口地址等等
Program header table:程序表头。如果存在的话,告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表
Section or Segment:节或段
ELF 文件中包含的各个节,具体见后面章节。
Section header table:节头表,可选
每个节区在表中都有一项,描述了 ELF 文件包含的所有节的信息,比如每个节的节名、节的长度、在文件中的偏移、读写权限及段的其他属性
用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
String tables:字符串表
Symbol tables:符号表
注意:尽管图中显示的各个组成部分是有顺序的,实际上除了 ELF 头部表以外,其他节区和段都没有规定的顺序。
4.1 ELF header:文件头
文件的最开始几个字节给出如何解释文件的提示信息。 这些信息独立于处理器, 也独立于文件中的其余内容。
还是对 hello.c 这段代码进行分析,对编译出来的 a.out 文件执行命令:readelf -h a.out
ELF 文件头中的定义如下:
Magic:ELF 魔数
Class:文件机器字节长度
Data:数据存储方式
Version:版本
OS/ABI:运行平台
ABI Version:ABI版本
Type:ELF 文件类型
Machine:硬件平台
Version:硬件平台版本
Entry point address:入口地址
Start of program headers:程序头入口和长度
Start of section headers:节头的位置和长度
Flags:处理器标志
Size of this header:ELF 文件头的大小
Size of program headers:程序头的大小
Number of program headers:程序头的数量
Size of section headers:节的大小
Number of section headers:节的数量
Section header string table index:段表头字符串表的位置
段由若干个节(Section)构成,节头表对每一个节的信息有相关描述。对可执行程序而言,节头表是可选的。 ELF 头部是一个关于本文件的路线图(road map),从总体上描述文件的结构。下面是ELF头部的数据结构:
/usr/include/elf.h
1 typedef struct
2 {
3 unsigned char e_ident[EI_NIDENT]; /* 魔数和相关信息 */
4 Elf32_Half e_type; /* 目标文件类型 */
5 Elf32_Half e_machine; /* 硬件体系 */
6 Elf32_Word e_version; /* 目标文件版本 */
7 Elf32_Addr e_entry; /* 程序进入点 */
8 Elf32_Off e_phoff; /* 程序头部偏移量 */
9 Elf32_Off e_shoff; /* 节头部偏移量 */
10 Elf32_Word e_flags; /* 处理器特定标志 */
11 Elf32_Half e_ehsize; /* ELF头部长度 */
12 Elf32_Half e_phentsize; /* 程序头部中一个条目的长度 */
13 Elf32_Half e_phnum; /* 程序头部条目个数 */
14 Elf32_Half e_shentsize; /* 节头部中一个条目的长度 */
15 Elf32_Half e_shnum; /* 节头部条目个数 */
16 Elf32_Half e_shstrndx; /* 节头部字符表索引 */
17 } Elf32_Ehdr;
e_ident[EI_NIDENT]:ELF魔数
e_ident标识索引如下:
对应的信息是 Magic、Class、Data、Version、OS/ABI 和 ABI Version
e_ident[0] - e_ident[3] 包含了ELF文件的魔数,依次是0x7f、'E'、'L'、'F'。注意,任何一个ELF文件必须包含此魔数。
e_ident[4] 表示硬件系统的位数,1代表32位,2代表64位。
e_ident[5] 表示数据编码方式(字节序),1代表小端排序(最大有意义的字节占有最低的地址),2代表大端排序(最大有意义的字节占有最高的地址)。
e_ident[6] 指定ELF文件的主版本号,一般为1。
e_ident[7]到e_ident[14]是填充符,通常是0。ELF格式规范中定义这几个字节是被忽略的,但实际上是这几个字节完全可以可被利用。
e_ident[7] 为0x21,表示本文件已被感染;或者存放可执行代码。
e_type:ELF 文件类型
ET_NONE:值为0。未知目标文件格式
ET_REL:值为 1。可重定位文件,一般为 .o 文件
ET_EXEC:值为2。可执行文件
ET_DYN:值为 3。共享目标文件,一般为 .so 文件
ET_CORE:值为4。Core 文件(转储格式)
ET_LOPROC:值为 0xff00。特定处理器文件
ET_HIPROC:值为 0xffff。特定处理器文件
ET_LOPROC 和 ET_HIPROC 之间的取值用来标识与处理器相关的文件格式。
e_machine:ELF 文件的 CPU 平台属性。相关常量以 EM_ 开头
名称 |
取值 |
含义 |
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 |
其它值都是保留的。特定处理器的 ELF 名称会使用机器名来进行区分。 |
e_version:目标文件版本,一般为常数 1
名称 |
取值 |
含义 |
EV_NONE |
0 |
非法版本 |
EV_CURRENT |
1 |
当前版本 |
e_entry:程序入口地址,规定 ELF 程序入口的虚拟地址,操作系统在加载完该程序后从这个地址开始执行进程的指令。可重定位文件一般没有入口地址,则这个值为 0
e_phoff:对应 Start of program headers。程序头部表格( Program Header Table)的偏移量(按字节计算)。如果文件没有程序头部表格,可以为 0。
e_shoff:对应 Start of section headers。节区头部表格( Section Header Table) 的偏移量(按字节计算)。 如果文件没有节区头部表格,可以为 0。
e_flags:ELF 标志位,用来标识一些 ELF 文件平台相关的属性。相关常量的格式一般为 EF_machine_flag,machine 为平台,flag 为标志
e_ehsize:ELF 文件头本身的大小(以字节计算),对应 Size of this header
e_phentsize:对应 Size of program headers。程序头部表格的表项大小(按字节计算)
e_phnum:对应 Number of program headers。程序头部表格的表项数目。可以为 0。
e_shentsize:对应 Size of section headers,节区头部表格的表项大小(按字节计算)。
e_shnum:对应 Number of section headers,节区头部表格的表项数目。可以为 0。
e_shstrndx:节区头部表格中与节区名称字符串表相关的表项的索引。 如果文件没有节区名称字符串表,此参数可以为 SHN_UNDEF。
ELF 头部中大多数字段都是对子头部数据的描述,其意义相对比较简单。值得注意的是某些病毒可能修改字段 e_entry(程序进入点)的值,以指向病毒代码。
4.2 Program header table:程序头表
紧接ELF头部的是程序头表。执行命令:readelf -l a.out
程序头是一个结构数组,包含了 ELF 头表中字段 e_phnum 定义的条目,此结构描述一个段或其他系统准备执行该程序所需要的信息。
结构体位于:/usr/include/elf.h
1 typedef struct {
2 Elf32_Word p_type; /* 段类型 */
3 Elf32_Off p_offset; /* 段位置相对于文件开始处的偏移量 */
4 Elf32_Addr p_vaddr; /* 段在内存中的地址 */
5 Elf32_Addr p_paddr; /* 段的物理地址 */
6 Elf32_Word p_filesz; /* 段在文件中的长度 */
7 Elf32_Word p_memsz; /* 段在内存中的长度 */
8 Elf32_Word p_flags; /* 段的标记 */
9 Elf32_Word p_align; /* 段在内存中对齐标记 */
10 } Elf32_Phdr;
对一个ELF可执行程序而言,一个基本的段是标记 p_type 为 PT_INTERP 的段,它表明了运行此程序所需要的程序解释器(/lib/ld-linux.so.2),实际上也就是动态连接器(dynamic linker)。
最重要的段是标记 p_type 为 PT_LOAD 的段,它表明了为运行程序而需要加载到内存的数据。查看上面实际输入,可以看见有两个可 LOAD 段,第一个为只读可执行(FLg 为 R E ),第二个为可读可写(Flg 为 RW)。
段一包含了文本节 .text ,注意到 ELF 文件头部中程序进入点的值为 0x400430,它会指向节.text在内存中的地址。
段二包含了数据节 .data,此数据节中数据是可读可写的,相对的只读数据节 .rodata 包含在段一中。
4.3 Section:节区
节区中包含目标文件中的所有信息,除了: ELF 头部、程序头部表格、节区头部表格。节区满足以下条件:
目标文件中的每个节区都有对应的节区头部描述它, 反过来, 有节区头部不意味着有节区。
每个节区占用文件中一个连续字节区域(这个区域可能长度为 0)。
文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生。
目标文件中可能包含非活动空间( INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容未指定。
很多节区中包含了程序和控制信息。 下面的表中给出了系统使用的节区, 以及它们的类型和属性。
名称 |
类型 |
属性 |
含义 |
.bss |
SHT_NOBITS |
SHF_ALLOC +SHF_WRITE |
包含将出现在程序的内存映像中的为初始化数据。根据定义,当程序开始执行,系统将把这些数据初始化为 0。 此节区不占用文件空间。 |
.comment |
SHT_PROGBITS |
(无) |
包含版本控制信息。 |
.data |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
这些节区包含初始化了的数据,将出现在程序的内存映像中。 |
.data1 |
SHT_PROGBITS |
SHF_ALLOC + SHF_WRITE |
|
.debug |
SHT_PROGBITS |
(无) |
此节区包含用于符号调试的信息。 |
.dynamic |
SHT_DYNAMIC |
此节区包含动态链接信息。节区的属性将包含 SHF_ALLOC 位。是否 SHF_WRITE 位被设置取决于处理器。 |
|
.dynstr |
SHT_STRTAB |
SHF_ALLOC |
此节区包含用于动态链接的字符串,大多数情况下这些字符串代表了与符号表项相关的名称。 |
.dynsym |
SHT_DYNSYM |
SHF_ALLOC |
此节区包含了动态链接符号表。 |
.fini |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
此节区包含了可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将安排执行这里的代码。 |
.got |
SHT_PROGBITS |
此节区包含全局偏移表。 |
|
.hash |
SHT_HASH |
SHF_ALLOC |
此节区包含了一个符号哈希表。 |
.init |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
此节区包含了可执行指令,是进程初始化代码的一部分。当程序开始执行时,系统要在开始调用主程序入口之前(通常指 C 语言的 main 函数)执行这些代码。 |
.interp |
SHT_PROGBITS |
此节区包含程序解释器的路径名。如果程序包含一个可加载的段,段中包含此节区,那么节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 |
|
.line |
SHT_PROGBITS |
(无) |
此节区包含符号调试的行号信息,其中描述了源程序与机器指令之间的对应关系。其内容是未定义的。 |
.note |
SHT_NOTE |
(无) |
此节区中包含注释信息,有独立的格式。 |
.plt |
SHT_PROGBITS |
此节区包含过程链接表( procedure linkage table)。 |
|
.relname |
SHT_REL |
这些节区中包含了重定位信息。如果文件中包含可加载的段,段中有重定位内容,节区的属性将包含 SHF_ALLOC 位,否则该位置 0。传统上 name 根据重定位所适用的节区给定。 例如 .text 节区的重定位节区名字将是: .rel.text 或者 .rela.text。 |
|
.relaname |
SHT_RELA |
||
.rodata |
SHT_PROGBITS |
SHF_ALLOC |
这些节区包含只读数据, 这些数据通常参与进程映像的不可写段。 |
.rodata1 |
SHT_PROGBITS |
SHF_ALLOC |
|
.shstrtab |
SHT_STRTAB |
此节区包含节区名称。 |
|
.strtab |
SHT_STRTAB |
此节区包含字符串, 通常是代表与符号表项相关的名称。如果文件拥有一个可加载的段,段中包含符号串表, 节区的属性将包含 SHF_ALLOC 位,否则该位为 0。 |
|
.symtab |
SHT_SYMTAB |
此节区包含一个符号表。如果文件中包含一个可加载的段,并且该段中包含符号表,那么节区的属性中包含SHF_ALLOC 位,否则该位置为 0。 |
|
.text |
SHT_PROGBITS |
SHF_ALLOC + SHF_EXECINSTR |
此节区包含程序的可执行指令。 |
在分析这些节区的时候,需要注意如下事项:
以'.'开头的节区名称是系统保留的。 应用程序可以使用没有前缀的节区名称, 以避免与系统节区冲突。
目标文件格式允许人们定义不在上述列表中的节区。
目标文件中也可以包含多个名字相同的节区。
保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。
处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 街区是由 FOO 体系结构定义的 psect 节区。
另外,有些编译器对如上节区进行了扩展, 这些已存在的扩展都使用约定俗成的名称,如:.sdata、.tdesc、.sbss、.lit4、.lit8、.reginfo、.gptab、.liblist、.conflict 等等
4.4 Section header table:节区头部表格
执行命令 readelf -S a.out 可以查看到节表头,这里面保存了 ELF 文件中的各种各样的节。节表是 ELF 文件中除了文件头以外最重要的结构,它描述了 ELF 各个 节 的信息,比如每个节的节名、节的长度、在文件中的偏移、读写权限及节的其他属性。即 ELF 文件的节结构就是由节表来决定的,编译器、链接器和装载器都是依靠节表来定位和访问各个节的属性的。节表在 ELF 文件中的位置由 ELF 文件头的'e_shoff'成员决定。
1 There are 31 section headers, starting at offset 0x19d8:
2
3 Section Headers:
4 [Nr] Name Type Address Offset Size EntSize Flags Link Info Align
5 [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0
6 [ 1] .interp PROGBITS 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1
7 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4