ELF文件详细学习

ELF文件结构

整体结构

ELF文件在链接视图(.o)文件和执行视图(可执行文件)有所差别,见下图

ELF.png

链接视图是以节(section)为单位,执行视图是以段(segment)为单位。

ELF Header

显示命令:

1
$readelf -h <ELF_file>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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: REL (Relocatable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x0
Start of program headers: 0 (bytes into file)
Start of section headers: 832 (bytes into file)
Flags: 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: 1
Section header string table index: 1 <corrupt: out of range>

7f 、45、4c、46分别对应ascii码的Del(删除)、字母E、字母L、字母F。这四个字节被称为ELF文件的魔数,操作系统在加载可执行文件时会确认魔数是否正确,如果不正确则拒绝加载。
第五个字节标识ELF文件是32位(01)还是64位(02)的。
第六个字节标识该ELF文件字节序是小端(01)还是大端(02)的。
第七个字节指示ELF文件的版本号,一般是01。
后九个字节ELF标准未做定义。一般为00.

ELF文件头定义了ELF魔数、文件机器字节长度、数据存储方式、版本、运行平台等一系列参数。

详细结构:

ELF Header的详细结构定义如下

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]; /* 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_entry:程序入口地址

e_ehsize:ELF Header结构大小

e_phoffe_phentsizee_phnum:描述Program Header Table的偏移、大小、结构。

e_shoffe_shentsizee_shnum:描述Section Header Table的偏移、大小、结构。

e_shstrndx:这一项描述的是字符串表在Section Header Table中的索引,值25表示的是Section Header Table中第25项是字符串表(String Table)

其中各个类型所占字节的大小

Elf64_Word :4字节

Elf64_Off :8字节

Elf64_Addr:8字节

Elf64_Half :2字节

Elf64_Xword:8字节

Section Header Table

显示命令:

段表是ELF除了文件以外的最重要结构体,它描述了ELF的各个段的信息,ELF文件的段结构就是由段表决定的。编译器、链接器和装载器都是依靠段表来定位和访问各个段的属性的。如上面ELF Header所示,段表在ELF文件中的位置由ELF文件头的“e_shoff”成员决定的。

1
$readelf -S <ELF_file>
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
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .strtab STRTAB 0000000000000000 00000280
00000000000000bc 0000000000000000 0 0 1
[ 2] .text PROGBITS 0000000000000000 00000040
000000000000003a 0000000000000000 WAX 0 0 16
[ 3] .rela.text RELA 0000000000000000 00000220
0000000000000018 0000000000000018 I 12 2 8
[ 4] data PROGBITS 0000000000000000 0000007c
0000000000000008 0000000000000000 A 0 0 4
[ 5] .rodata.str1.1 PROGBITS 0000000000000000 00000084
000000000000000b 0000000000000001 AMS 0 0 1
[ 6] .bss NOBITS 0000000000000000 00000090
0000000000000004 0000000000000000 WA 0 0 4
[ 7] .comment PROGBITS 0000000000000000 00000090
0000000000000026 0000000000000001 MS 0 0 1
[ 8] .note.GNU-stack PROGBITS 0000000000000000 000000b6
0000000000000000 0000000000000000 0 0 1
[ 9] .eh_frame X86_64_UNWIND 0000000000000000 000000b8
0000000000000078 0000000000000000 A 0 0 8
[10] .rela.eh_frame RELA 0000000000000000 00000238
0000000000000048 0000000000000018 I 12 9 8
[11] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 00000280
0000000000000000 0000000000000000 E 12 0 1
[12] .symtab SYMTAB 0000000000000000 00000130
00000000000000f0 0000000000000018

其中比较重要的段:

.text段主要存储函数代码

.bss段主要存储未初始化的全局变量以及局部静态变量

.rodata:存储全局常量,字符串常量。

.data:已初始化的全局变量和局部静态变量。

详细结构:

Section Header table 本质上是Elf64_Shdr 结构体的数组,Elf64_Shdr的结构定义如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
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:表示section的名称。由于每个section名称的长度不相同,并且为了节约空间,于是就将所有section的名称都存放在一个特定的名叫.shstrtab的section中,所以这里的sh_name的值指的就是在这个特定section中的偏移量,通过它可以获得一个字符串,也就是所需要的section名。0值表示无名称,一般用于类型为SHT_NULL的section中。

sh_type:表示section的类型。

sh_offset表示该section在文件中的偏移量。

sh_size该section的大小。

sh_linksh_info:如果该部分与链接相关,则“sh_link”和“sh_info”具有特殊的含义。

sh_addralign:该部分在进程的虚拟地址空间中的对齐方式。

sh_entsize:如果该部分包含表,如符号表、重新定位表,sh_entsize是表中每个条目的大小。

.strtab section

.strtab是字符串表,保存了节中用到所有的字符串,如下图所示,保存了所有的字符串,其他节通过索引偏移指定字符串的具体值

strtab.png

可通过下面命令查询所有的字符串具体的偏移量。

1
$readelf -p .strtab <ELF_FILE>

strtab_dump.png

以.text段举例,如最开始命令所示,.text段在整体文件的偏移为0x40,.text section table在.strtab section table的后面,根据ELF table Header,每个section header为64字节,开始在832字节。所以.text section table的位置为 0x340(832字节)+0x40(64字节)*2 = 0x3C0开始

text_name.png

开始字段即为sh_name为06,即从.strtab段开头偏移6,查表可知偏移6即为.text字符串。此即为.strtab段的作用。

.symtab section

symbol介绍

在 ELF(可执行和可链接格式)文件中,symbol起着重要作用。
此上下文中的symbol本质上是一个命名实体。它可以是一个变量,一个函数,
或程序中任何其他可识别的代码段。

在 ELF 文件中符号的主要作用包括

1.链接:符号允许链接器解析引用。当您想要构建程序时 对于多个源文件,每个 .c 文件都编译为单独的可重定位对象文件。 如果一个目标文件引用另一个目标文件中定义的函数或变量,它通过一个符号来实现。在链接阶段,链接器将解析这些符号,将对符号的每个引用替换为所引用实体的实际地址。

2.分析:symbol对于 objdump/gdb/ghidra 等分析工具至关重要,没有symbol的程序比有symbol的更难分析。

3.动态链接:symbol也用于动态链接,它们允许程序确定运行时要从动态加载的库中调用哪些函数或要使用的变量。

常见symbol

每个对象文件都有自己的符号表 (.symtab),其中包含在
对象文件。符号表中的符号可以是以下类型之一:

  1. 全局symbol:这些符号对其他对象文件和库可见,例如函数(’main’、’foo’)和全局变量。
  2. 外部symbol:这些符号在其他对象文件或库中定义,例如 libc 中的“printf”。
    3.节名:这些符号总是由编译器和汇编器生成,用于标记节的开头。
  3. 局部symbol:这些符号仅对目标文件本身可见,例如静态函数和静态变量。
    5.调试符号:这些符号由编译器和汇编器生成,用于调试,通常采用“DWARF”格式。
详细结构

.symtab和Section Header Table一样,是一个存储Elf64_Sym结构体的数组。

Elf64_Sym的具体结构如下。

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

st_name:symbol的名字在.strtab段中的偏移。

st_shndx:symbol段的索引。

st_info:高4位表示 Symbol Binding,低4位表示 Symbol Type

st_value:根据不同的上下文,有不同的定义.

1.对于 Relocatable file,如果 st_shndx 的值为 SHN_COMMON,那么 st_value 表示对齐约束。(上面 st_shndx = SHN_COMMON 已经提及)
2.对于 Relocatable file,如果 st_shndx 的值为关联的 Section Header index,那么 st_value 表示从该 Section 起始位置开始的 offset。(上面 st_shndx = Section Header index 已经提及)
3.对于 Executable file 和 Shared object file,st_value 则是已经计算好的虚存地址,这是为了方便 dynamic linker(动态链接器)

st_size:表示 Symbol 的大小,例如数据对象占据多少字节。如果 Symbol 没有大小或者大小未知,则值为0。

可通过命令来查看所有的symbol。

1
$readelf -s <ELF_FILE>

symbol.png

如上图所示即为各个symbol的详细信息。

Program Header Table

查看进程虚拟地址范围文件

/proc/<pid>/maps

ELF的Program Header Table用于描述ELF可执行文件的内存布局。

操作系统并不关心ELF部分的详细数据,它主要关心每个部分的权限:是否可读,可写,可执行。因此,一些具有相同权限的ELF部分的可以合并到一个segment中。

segment由链接器创建,描述segment的结构就是Program Header Table。只有ELF可执行文件和共享库才有Program Header Table,这是一个数组Elf64_Phdr结构,每个这样的结构定义一个segment。

详细结构

1
2
3
4
5
6
7
8
9
10
11
typedef struct {
Elf64_Word p_type; /* Segment type : LOAD / DYNAMIC / INTERP / ... */
Elf64_Word p_flags; /* Segment flags : R / W / X */
Elf64_Off p_offset; /* Segment file offset : Segment's offset in the ELF file */
Elf64_Addr p_vaddr; /* Segment virtual address : Segment's virtual address in the process's virtual address space */
Elf64_Addr p_paddr; /* Segment physical address : same as virtual address in most cases */
Elf64_Xword p_filesz; /* Segment size in file : Segment's size in the ELF file */
Elf64_Xword p_memsz; /* Segment size in memory : Segment's size in the process's virtual address space */
Elf64_Xword p_align; /* Segment alignment : Segment's alignment attribute, it p_align is 10, then the
segment's address should be 2^10 aligned */
} Elf64_Phdr;

详细的介绍已经在注释中写明。

显示命令

1
$readelf -l <ELF_file>
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
Elf file type is EXEC (Executable file)
Entry point 0x401690
There are 11 program headers, starting at offset 64

Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000520 0x0000000000000520 R 0x1000
LOAD 0x0000000000001000 0x0000000000401000 0x0000000000401000
0x0000000000074315 0x0000000000074315 R E 0x1000
LOAD 0x0000000000076000 0x0000000000476000 0x0000000000476000
0x0000000000029133 0x0000000000029133 R 0x1000
LOAD 0x000000000009f470 0x00000000004a0470 0x00000000004a0470
0x0000000000065620 0x000000000006ae98 RW 0x1000
NOTE 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x0000000000000020 0x0000000000000020 R 0x8
NOTE 0x00000000000002c8 0x00000000004002c8 0x00000000004002c8
0x0000000000000044 0x0000000000000044 R 0x4
TLS 0x000000000009f470 0x00000000004a0470 0x00000000004a0470
0x0000000000000018 0x0000000000000058 R 0x8
GNU_PROPERTY 0x00000000000002a8 0x00000000004002a8 0x00000000004002a8
0x0000000000000020 0x0000000000000020 R 0x8
GNU_EH_FRAME 0x0000000000091960 0x0000000000491960 0x0000000000491960
0x0000000000002174 0x0000000000002174 R 0x4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 RW 0x10
GNU_RELRO 0x000000000009f470 0x00000000004a0470 0x00000000004a0470
0x0000000000063b90 0x0000000000063b90 R 0x1

Section to Segment mapping:
Segment Sections...
00 .note.gnu.property .note.gnu.build-id .note.ABI-tag .rela.plt
01 .init .plt .text .fini
02 .rodata .stapsdt.base rodata.cst32 .eh_frame_hdr .eh_frame .gcc_except_table
03 .tdata .init_array .fini_array .data.rel.ro .got .got.plt .data .bss
04 .note.gnu.property
05 .note.gnu.build-id .note.ABI-tag
06 .tdata .tbss
07 .note.gnu.property
08 .eh_frame_hdr
09
10 .tdata .init_array .fini_array .data.rel.ro .got

plt表和got表

d03d5ab1f7ee16945a9d7da28888ba8f.png

静态链接和动态链接的区别,静态链接在编译为可执行文件时将用户文件和库文件打包到一起,动态链接打包库函数寻址的程序片段,加载到内存空间后在再进行寻找。

plt表是一个数组,数组中的每个元素都一个可执行的代码片段。数组中每个元素都与一个外部函数相对应(如strlen@plt。每当程序试图调用一个外部函数时,就会跳转到.plt表的对应代码片段,再由该代码片段跳转至对应函数。

got.plt 表是一个数组,数组中的每个元素都一个地址。数组中每个元素都与一个外部函数相对应(如strlen@got)。每当程序试图调用一个外部函数时,.plt表的跳转地址正是来自于.got.plt表。

每当程序试图调用一个外部函数时,就会跳转到.plt表的对应代码片段,再由该代码片段跳转至目标函数。

got表劫持

可以事先将got表中的函数地址改为后门函数地址当执行时plt表直接跳转到后门函数从而劫持获取权限。

通过命令即可查询到每个.plt表对应的got表。

1
$objdump -d -j .plt /challenge/level41

ELF文件详细学习
http://jty-123.github.io/2023/10/11/ELF文件/
作者
Jty
发布于
2023年10月11日
许可协议