ELF文件结构
ELF文件结构
ELF文件的全称是Executable and Linkable Format,直译为“可执行可链接格式”,包括目标文件(.o)、可执行文件(可以直接运行)、静态链接库、动态链接库、核心转储文件(core dump)。ELF文件的定义可以在/usr/include/elf.h
中找到,本文主要介绍ELF64,ELF文件通常由下列部分组成:
ELF头(ELF header):放在ELF文件开头,描述该文件信息。
节头表(Section header table):包含对节(section)的描述,对于可重定位文件(relocatable files)是必须的,对于可装载文件(loadable files)是可选的。
程序头表(Program header table):对于可装载文件(loadable files)是必须的,对于可重定位文件(relocatable files)是可选的。用来描述加载程序或动态链接库所需要的段(segments)和其他数据结构。
节或段的内容,包括符号表等。
ELF头
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;
Elf64_Ehdr中的数据结构含义如下:
数据结构名称 | 大小(byte) | 对齐(byte) | 目标 |
---|---|---|---|
Elf64_Addr | 8 | 8 | Unsigned program address |
Elf64_Off | 8 | 8 | Unsigned file offset |
Elf64_Half | 2 | 2 | Unsigned medium integer |
Elf64_Word | 4 | 4 | Unsigned integer |
Elf64_Sword | 4 | 4 | Signed integer |
Elf64_Xword | 8 | 8 | Unsigned long integer |
Elf64_Sxword | 8 | 8 | Signed long integer |
unsigned char | 1 | 1 | Unsigned small integer |
我们用readelf -h hello.o
看一下从源文件到可执行文件:源文件的预处理、编译、汇编、链接中生成的hello.o
文件的ELF头(因为我机器上显示的结果是中文,所以接下来就按照中文来说明,比如ELF头中类别对应Class,类型对应Type)。
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
Version: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: REL (可重定位文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x0
程序头起点: 0 (bytes into file)
Start of section headers: 864 (bytes into file)
标志: 0x0
Size of this header: 64 (bytes)
Size of program headers: 0 (bytes)
Number of program headers: 0
Size of section headers: 64 (bytes)
Number of section headers: 14
Section header string table index: 13
ELF头一开始的位置是魔术字符(Magic),在ASCII码中,'E'、'L'、'F'分别对应45、4c、46。当文件被映射到内存中,可以通过魔术字符确定映射地址。 ELF头与Elf64_Ehdr存在对应关系:
Elf64_Ehdr成员 | ELF头 | 含义 |
---|---|---|
e_ident | Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 | |
e_type | 类型: REL (可重定位文件) | ELF文件类型(包括Relocatable/Executable/Shared/Core等类型) |
e_machine | 系统架构: Advanced Micro Devices X86-64 | |
e_version | 版本: 0x1 | 版本号,通常为0x1 |
e_entry | 入口点地址: 0x0 | 程序入口点的虚拟地址,操作系统在加载完程序后从该地址开始执行进程的指令。可重定位文件没有入口地址,所以为0。用readelf命令查看前文生成的可执行文件可以看到入口地址 |
e_phoff | 程序头起点:0 (bytes into file) | 程序头表偏移(单位:byte) |
e_shoff | Start of section headers: 864 (bytes into file) | 节头表偏移(单位:byte) |
e_flags | 标志:0x0 | 特定于处理器的标识 |
e_ehsize | Size of this header: 64 (bytes) | ELF头本身的大小(单位:byte) |
e_phentsize | Size of program headers: 0 (bytes) | 程序头大小(单位:byte) |
e_phnum | Number of program headers: 0 | 程序头个数 |
e_shentsize | Size of section headers: 64 (bytes) | 节头大小(单位:byte) |
e_shnum | Number of section headers: 14 | 节头个数 |
e_shstrndx | Section header string table index: 13 | 字符串表在节头表中索引 |
对于魔数字符,再展开介绍一下。Magic共16个字节(Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00),第0~3个字节标识文件;第4个字节标识文件类别(类别: ELF64),1表示32位,2标识64位;第5个字节标识文件数据编码(数据: 2 补码,小端序 (little endian)),1标识小端序,2表示大端序;第6个字节标识文件版本(Version: 1 (current)),值为1;第7个字节标识操作系统和ABI(OS/ABI: UNIX - System V),0表示System V ABI,1表示HP-UX operating system,255表示Standalone (embedded) application;第8个字节标识ABI版本(ABI 版本: 0),值为1;剩余字节被保留为将来使用,设置为0。
节头表
一个目标文件(包括Relocatable/Executable/Shared/Core等类型)中包含很多节,这些节的信息保存在节头表中,表的每一项都是一个Elf64_Shdr结构体(也称为节描述符),节点信息包括节名、节大小、在文件中的偏移、读写权限等,编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。/usr/include/elf.h
中的Elf64_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;
通过readelf -S hello.o
命令查看hello.o文件的节头表。
There are 14 section headers, starting at offset 0x360:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
0000000000000027 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 00000270
0000000000000060 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 00000067
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 00000067
0000000000000019 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000080
000000000000002c 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ac
0000000000000000 0000000000000000 0 0 1
[ 8] .note.gnu.propert NOTE 0000000000000000 000000b0
0000000000000020 0000000000000000 A 0 0 8
[ 9] .eh_frame PROGBITS 0000000000000000 000000d0
0000000000000038 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 000002d0
0000000000000018 0000000000000018 I 11 9 8
[11] .symtab SYMTAB 0000000000000000 00000108
0000000000000138 0000000000000018 12 10 8
[12] .strtab STRTAB 0000000000000000 00000240
0000000000000029 0000000000000000 0 0 1
[13] .shstrtab STRTAB 0000000000000000 000002e8
0000000000000074 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)
正如ELF头中内容所示,hello.o中包含14个节。节名(sh_name)是一个单位为字节的偏移量,表示相对于节名字符串表(section name string table,也就是.shstrtab)起点的偏移,节名实际存放在节名字符串表中,通过查表得到。节类型(sh_type)分为以下几类,readelf命令的结果省略了前缀SHT_。
节类型 | 含义 |
---|---|
SHT_NULL | 无效节 |
SHT_PROGBITS | 程序节。代码节、数据节都是这种类型 |
SHT_SYMTAB | 符号表 |
SHT_STRTAB | 字符串表 |
SHT_RELA | 重定位表 |
SHT_HASH | 符号表的哈希表 |
SHT_DYNAMIC | 动态链接信息 |
SHT_NOTE | 提示性信息 |
SHT_NOBITS | 表示该节在文件中没有内容,不占用空间 |
SHT_REL | 重定位信息 |
SHT_SHLIB | 保留 |
SHT_DYNSYM | 动态链接的符号表 |
SHT_LOOS/SHT_HIOS | 特定环境使用 |
SHT_LOPROC/SHT_HIPROC | 特定处理器使用 |
节标志位(sh_flags)表示该节在进程虚拟地址空间中的属性,1表示可写,2表示该节在进程空间中需要分配空间,有些包含指示或者控制信息的节不需要在进程分配空间,就没有这个标志,4表示该节在进程空间中可以被执行。
节链接信息(sh_link、sh_info),如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表等,则sh_link、sh_info两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。
sh_type | sh_link | sh_info |
---|---|---|
SHT_DYNAMIC | 该节所使用的字符串表在节头表中的下标 | 0 |
SHT_HASH | 该节所使用的符号表在节头表中的下标 | 0 |
SHT_REL | 该节所使用的相应符号表在节头表中的下标 | 该重定位表所作用的节在节头表中的下标 |
SHT_RELA | 该节所使用的相应符号表在节头表中的下标 | 该重定位表所作用的节在节头表中的下标 |
SHT_SYMTAB | 操作系统相关 | 操作系统相关 |
SHT_DYNSYM | 操作系统相关 | 操作系统相关 |
other | SHN_UNDEF | 0 |
重要的节
.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.*节(重定位表)
重定位表保存了重定位相关的信息,这些信息描述了如何在链接或运行时,对ELF目标文件的某部分或者进程镜像进行补充或修改。由于重定位表保存了重定位相关的数据,所以节类型为SHT_REL。.hash节
.hash节也称为.gnu.hash,其保存了一个用于查找符号的散列表。.symtab节(符号表)
.symtab节是一个ElfN_Sym的数组,保存了符号信息。节类型为SHT_SYMTAB。.strtab节(字符串表)
.strtab节保存的是符号字符串表,表中的内容会被.symtab的ElfN_Sym结构中的st_name引用。节类型为SHT_STRTAB。.ctors节和.dtors节
.ctors(构造器)节和.dtors(析构器)节分别保存了指向构造函数和析构函数的函数指针,构造函数是在main函数执行之前需要执行的代码;析构函数是在main函数之后需要执行的代码。
符号表包括.dynsym
和.symtab
,前者是后者的子集。.dynsym
保存了引用自外部文件的符号,只能在运行时被解析(flag为Alloc),而.symtab
还保存了本地符号,用于调试和链接,不会被装载到内存中。
程序头表
在/usr/include/elf.h
,程序头表的结构如下:
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;
由于hello.o没有程序头表,所以通过readelf -l hello
来读取可执行文件hello的程序头表,如下所示。
Elf 文件类型为 DYN (共享目标文件)
Entry point 0x1060
There are 13 program headers, starting at offset 64
程序头:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040
0x00000000000002d8 0x00000000000002d8 R 0x8
INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000000005f8 0x00000000000005f8 R 0x1000
LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000
0x00000000000001f5 0x00000000000001f5 R E 0x1000
LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000
0x0000000000000170 0x0000000000000170 R 0x1000
LOAD 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000258 0x0000000000000260 RW 0x1000
DYNAMIC 0x0000000000002dc8 0x0000000000003dc8 0x0000000000003dc8
0x00000000000001f0 0x00000000000001f0 RW 0x8
NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x0000000000000358 0x0000000000000358 0x0000000000000358
0x0000000000000044 0x0000000000000044 R 0x4
GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338
0x0000000000000020 0x0000000000000020 R 0x8
GNU_EH_FRAME 0x0000000000002020 0x0000000000002020 0x0000000000002020
0x0000000000000044 0x0000000000000044 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x0000000000002db8 0x0000000000003db8 0x0000000000003db8
0x0000000000000248 0x0000000000000248 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .plt.got .plt.sec .text .fini
04 .rodata .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .data .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .note.gnu.property
10 .eh_frame_hdr
11
12 .init_array .fini_array .dynamic .got
与节类似,段也有几种不同的类型(p_type),如下:
段类型 | 含义 |
---|---|
PT_NULL | 无效段 |
PT_LOAD | 可加载段 |
PT_DYNAMIC | 动态链接表 |
PT_INTERP | 程序解释器路径名 |
PT_NOTE | 信息段 |
PT_SHLIB | 保留 |
PT_PHDR | 程序头表 |
PT_LOOS / PT_HIOS | 特定环境使用 |
PT_LOPROC / PT_HIPROC | 特定处理机使用 |
段标志位(p_flags)表示权限,X表示执行允许,W表示写允许,R表示读允许。
最后再说一下节(section)和(segment)的关系。每个段包括一个或多个节,因为系统不关心这些节的具体内容,只关心这些节的权限(读、写、执行),将具有相同权限的节放到同一个段中。节是链接视角下的ELF文件,段是运行视角下的ELF文件。
参考资料
ELF-64 Object File Format
计算机那些事(4)—— ELF文件结构
CTF竞赛权威指南(Pwn篇)(杨超 编著,吴石 eee战队 审校,电子工业出版社)
ELF文件结构的更多相关文章
- elf文件结构解读以及plt节got节的理解
前言: 熟悉elf文件结构是一件很不错的事,因为安卓中的so加固以及修复都是需要这些知识的,包括pwn里面的rop之类的,也都是 和got节,plt节息息相关的,个人建议是在搞懂elf文件结构后,自己 ...
- ELF文件结构描述
ELF目标文件格式最前部ELF文件头(ELF Header),它包含了描述了整个文件的基本属性,比如ELF文件版本.目标机器型号.程序入口地址等.其中ELF文件与段有关的重要结构就是段表(Sectio ...
- 程序运行之ELF文件结构
ELF目标文件格式的最前部是ELF文件头.包含了整个文件的基本属性.比如ELF文件版本,目标机器型号,程序入口地址等.然后是ELF的各个段,其中ELF文件中与段有关的重要结构就是段表.段表描述了ELF ...
- Linux ELF 文件格式
ELF 文件类型 ELF (Executable Linkable Format) 是 linux 下的可执行文件格式,与 windows 下的 PE (Portable Executable) 格式 ...
- ELF文件解析器支持x86x64ELF文件
此文为静态分析ELF文件结构,遍历其中Elf_Ehdr文件头信息,遍历Elf_Shdr节表头信息,并将所有节放置在左侧树控件上,遍历Elf_Phdr程序头也放置在左侧树控件上,并着重分析字符串表,重定 ...
- Linux ELF格式分析
http://www.cnblogs.com/hzl6255/p/3312262.html ELF, Executable and Linking Format, 是一种用于可执行文件.目标文件.共享 ...
- Shell 筛选符合条件的 ELF 文件
0 运行环境 本机系统:Windows 10 虚拟机软件:Oracle VM VirtualBox 6 虚拟机系统:Ubuntu 18 1 引言 - 编译过程 我们知道在 CPU 上执行的是低级别的机 ...
- (九)ELF和动态链接
前言: 我们都知道我们所写的程序是被编译为一条条的CPU指令去执行的,但是在linux系统下能够运行的程序在windows环境下却运行不起来,但是我们使用的CPU明明是一样的,这又是为什么呢? 一.程 ...
- Lab1:练习四——分析bootloader加载ELF格式的OS的过程
练习四:分析bootloader加载ELF格式的OS的过程. 1.题目要求 通过阅读bootmain.c,了解bootloader如何加载ELF文件.通过分析源代码和通过qemu来运行并调试bootl ...
随机推荐
- 简述在 MySQL 数据库中 MyISAM 和 InnoDB 的区别 ?
MyISAM: 不支持事务,但是每次查询都是原子的: 支持表级锁,即每次操作是对整个表加锁: 存储表的总行数: 一个 MYISAM 表有三个文件:索引文件.表结构文件.数据文件: 采用菲聚集索引,索引 ...
- My模板设计模式
模板模式 目标: 第一个设计模式:模板模式 步骤: 第一个设计模式:模板模式 讲解: 我们现在使用抽象类设计一个模板模式的应用, 例如在小学的时候,我们经常写作文,通常都是有模板可以套用的. 假如我现 ...
- MySQL怎么用命令修改字段长度
MySQL怎么用命令修改double字段长度 1 alter table 表名 modify column 列名 类型(要修改的长度) COMMENT 备注信息; 2 alter table t_ov ...
- DASCTF Oct吉林工师web
迷路的魔法少女 进入环境给出源码 <?php highlight_file('index.php'); extract($_GET); error_reporting(0); function ...
- 【freertos】007-系统节拍和系统延时管理实现细节
前言 本章节的时钟系统节拍主要分析FreeRTOS内核相关及北向接口层,南向接口层不分析. 本章节的系统延时主要分析任务系统延时实现. 原文:李柱明博客:https://www.cnblogs.com ...
- jq点击改变元素样式、添加类,显示隐藏,图标旋转,再次点击还原;表格点击显示下拉详情
点击前 点击后 <tr> <td class="right" data-id="{$vo.id}" id="{$vo.id}&quo ...
- 《JavaScript Dom编程艺术》读书笔记(二)
算术操作符 加减乘除这些算术操作中的每一种都必须借助于相应的操作符才能完成.操作符是JavaScript为完成各种操作而定义的一些符号.等号(=).加号(+).减号(-).乘号(*).除号(/). 下 ...
- 原型模式故事链--new一个对象的过程
上一个总标题:https://segmentfault.com/a/11...提问:你有对象了吗?答:没有.笨!new一个不就好了吗! 问题点:为什么我要理解new一个对象的过程?答:不理解这个过程, ...
- 有关表单autocomplete = "off" 失效问题解决方案
一.autocomplete介绍 autocomplete是Html5中的新属性.该属性规定输入字段是否应该启用自动完成功能.自动完成允许浏览器预测对字段的输入.当用户在字段开始键入的时候,浏览器基于 ...
- sparksql Seq生成DataFrame
首先,使用样例类: case class User(id:Int,name: String,gender:String, age: Int) 之后使用Seq创建Dataframe val alice: ...