ELF 二进制格式(二进制的e是什么意思)

2023-08-13 05:31:51

 看《深入Linux内核架构》时,经常提到ELF格式,故去阅读其附录,并做下此笔记

ELF 是什么

全称“Executable and Linkable Format”,即可执行和可连接格式,Linux下的可执行文件,链接文件,共享库文件(.so),目标文件(.o)等都是此格式

ELF 文件结构

下图左为链接文件,右则为执行文件视图

各部分的基本介绍

ELF文件头:存储文件类型和大小的有关信息,以及文件加载后程序执行的入口信息程序头表:可执行文件的数据在虚拟地址空间中的组织方式,以及段的数目,位置以及用途节:将文件分成一个个节区,每个节区都有其对应的功能,如符号表,哈希表等段:就是将文件分成一段一段映射到内存中。段中通常包括一个或多个节区节头表:存储段的附加信息

通过实例深入了解ELF文件

简单写一份c代码用于测试,采用linux下的readelf工具进行探究

// elf-test.c #include <stdio.h> int add(int a, int b){ printf("Add number\n"); return a + b; } int main() { int a, b; a = 1; b = 2; int res = add(a, b); printf("Result: %d\n", res); return 0; }

然后执行以下命令,通过gcc生成相应的可执行文件和目标文件

dman@DESKTOP:/mnt/a/os/pla/code$ gcc elf-test.c -o test dman@DESKTOP:/mnt/a/os/pla/code$ gcc elf-test.c -c -o test.o

PS: 但是我发现这样,使用file命令查看test文件发现文件类型是shared object(共享库类型)

dman@DESKTOP:/mnt/a/os/pla/code$ file test test: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV)...

经过一番搜索发现gcc会默认开--enable-default-pie 模式,如下

dman@DESKTOP:/mnt/a/os/pla/code$ gcc -v Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper OFFLOAD_TARGET_NAMES=nvptx-none OFFLOAD_TARGET_DEFAULT=1 Target: x86_64-linux-gnu Configured with: .... --enable-default-pie .... Thread model: posix gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

gcc中PIE(Position-Independent-Executable)模式能用来创建介于共享库和通常可执行代码之间的代码,能像共享库一样可重分配地址的程序,故test文件类型是share object

引入PIE的原因是让程序能装载在随机的地址,通常情况下,内核都在固定的地址运行,如果能改用位置无关,那攻击者就很难借助系统中的可执行码实施攻击了。类似缓冲区溢出之类的攻击将无法实施。

故可以添加-no-pie参数关闭该功能,故生成可执行文件test命令为

dman@DESKTOP:/mnt/a/os/pla/code$ gcc elf-test.c -no-pie -o test

通过file命令可以看到test文件类型为executable

test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked...

以及test.o文件类型为relocatable

test.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

使用readelf查看文件头

这里为了方便就直接在显示结果上添加注释信息

先查看可执行文件

dman@DESKTOP:/mnt/a/os/pla/code$ readelf -h test ELF Header: Magic: 7f 45(E)4c(L)46(F)02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64(64位机) Data: 2s complement, little endian(小端序) Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type(文件类型): EXEC (Executable file) (可执行文件) Machine: Advanced Micro Devices X86-64 (运行设备) Version: 0x1 Entry point address: 0x400450 Start of program headers: 64 (bytes into file) Start of section headers: 6456 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 9 Size of section headers: 64 (bytes) Number of section headers: 29 Section header string table index: 28

再查看链接文件test.o,这里略去和test文件头相同部分

dman@DESKTOP:/mnt/a/os/pla/code$ readelf -h test.o ELF Header: ... Type: REL (Relocatable file) (可重定位文件) ... Entry point address: 0x0 Start of program headers: 0 (bytes into file) (不需要程序头表) Start of section headers: 992 (bytes into file) ... Size of program headers: 0 (bytes) (不需要程序头表) Number of program headers: 0 (不需要程序头表) Size of section headers: 64 (bytes) Number of section headers: 13 Section header string table index: 12

readelf查看程序头表

dman@DESKTOP:/mnt/a/os/pla/code$ readelf -l test Elf file type is EXEC (Executable file) Entry point 0x400450 There are 9 program headers, starting at offset 64 Program Headers: Type Offset(偏移) VirtAddr(虚拟地址)PhysAddr(物理地址) FileSiz(文件大小)MemSiz(内存大小) Flags(访问权限) Align(字段对齐) PHDR (保存程序头表) 0x0000000000000040 0x0000000000400040 0x0000000000400040 0x00000000000001f8 0x00000000000001f8 R 0x8 INTERP(需要调用的解释器) 0x0000000000000238 0x0000000000400238 0x0000000000400238 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD (一个需要从二进制文件映射到虚拟地址空间的段,其中保存了程序目标代码等) 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000007a0 0x00000000000007a0 R E 0x200000 LOAD 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x0000000000000228 0x0000000000000230 RW 0x200000 DYNAMIC(保存动态链接器需要的信息) 0x0000000000000e20 0x0000000000600e20 0x0000000000600e20 0x00000000000001d0 0x00000000000001d0 RW 0x8 NOTE 0x0000000000000254 0x0000000000400254 0x0000000000400254 0x0000000000000044 0x0000000000000044 R 0x4 GNU_EH_FRAME 0x000000000000063c 0x000000000040063c 0x000000000040063c 0x0000000000000044 0x0000000000000044 R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000000e10 0x0000000000600e10 0x0000000000600e10 0x00000000000001f0 0x00000000000001f0 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .dynamic .got .got.plt .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .dynamic .got

readelf查看节头表

可执行文件

dman@DESKTOP:/mnt/a/os/pla/code$ readelf -S test.o There are 13 section headers, starting at offset 0x3e0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS(程序必须要解释的类型) 0000000000000000 00000040 0000000000000069 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA(重定位信息) 0000000000000000 000002d0 0000000000000078 0000000000000018 I 10 1 8 [ 3] .data PROGBITS 0000000000000000 000000a9 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 000000a9 0000000000000000 0000000000000000 WA 0 0 1 [ 5] .rodata PROGBITS 0000000000000000 000000a9 0000000000000017 0000000000000000 A 0 0 1 [ 6] .comment PROGBITS 0000000000000000 000000c0 000000000000002a 0000000000000001 MS 0 0 1 [ 7] .note.GNU-stack PROGBITS 0000000000000000 000000ea 0000000000000000 0000000000000000 0 0 1 [ 8] .eh_frame PROGBITS 0000000000000000 000000f0 0000000000000058 0000000000000000 A 0 0 8 [ 9] .rela.eh_frame RELA 0000000000000000 00000348 0000000000000030 0000000000000018 I 10 8 8 [10] .symtab SYMTAB(符号表类型) 0000000000000000 00000148 0000000000000150 0000000000000018 11 9 8 [11] .strtab STRTAB 0000000000000000 00000298 0000000000000037 0000000000000000 0 0 1 [12] .shstrtab STRTAB 0000000000000000 00000378 0000000000000061 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)

可重定位文件

dman@DESKTOP:/mnt/a/os/pla/code$ readelf -S test There are 29 section headers, starting at offset 0x1938: 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 0000000000400238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000400254 00000254 0000000000000020 0000000000000000 A 0 0 4 [ 3] .note.gnu.build-i NOTE 0000000000400274 00000274 0000000000000024 0000000000000000 A 0 0 4 [ 4] .gnu.hash GNU_HASH 0000000000400298 00000298 000000000000001c 0000000000000000 A 5 0 8 [ 5] .dynsym DYNSYM 00000000004002b8 000002b8 0000000000000078 0000000000000018 A 6 1 8 [ 6] .dynstr STRTAB 0000000000400330 00000330 0000000000000044 0000000000000000 A 0 0 1 [ 7] .gnu.version VERSYM 0000000000400374 00000374 000000000000000a 0000000000000002 A 5 0 2 [ 8] .gnu.version_r VERNEED 0000000000400380 00000380 0000000000000020 0000000000000000 A 6 1 8 [ 9] .rela.dyn RELA 00000000004003a0 000003a0 0000000000000030 0000000000000018 A 5 0 8 [10] .rela.plt RELA 00000000004003d0 000003d0 0000000000000030 0000000000000018 AI 5 22 8 [11] .init(程序初始化代码) PROGBITS 0000000000400400 00000400 0000000000000017 0000000000000000 AX 0 0 4 [12] .plt PROGBITS 0000000000400420 00000420 0000000000000030 0000000000000010 AX 0 0 16 [13] .text(保存二进制代码) PROGBITS 0000000000400450 00000450 00000000000001c2 0000000000000000 AX 0 0 16 [14] .fini(程序结束代码) PROGBITS 0000000000400614 00000614 0000000000000009 0000000000000000 AX 0 0 4 [15] .rodata(保存只读数据) PROGBITS 0000000000400620 00000620 000000000000001b 0000000000000000 A 0 0 4 [16] .eh_frame_hdr PROGBITS 000000000040063c 0000063c 0000000000000044 0000000000000000 A 0 0 4 [17] .eh_frame PROGBITS 0000000000400680 00000680 0000000000000120 0000000000000000 A 0 0 8 [18] .init_array INIT_ARRAY 0000000000600e10 00000e10 0000000000000008 0000000000000008 WA 0 0 8 [19] .fini_array FINI_ARRAY 0000000000600e18 00000e18 0000000000000008 0000000000000008 WA 0 0 8 [20] .dynamic DYNAMIC 0000000000600e20 00000e20 00000000000001d0 0000000000000010 WA 6 0 8 [21] .got PROGBITS 0000000000600ff0 00000ff0 0000000000000010 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 0000000000601000 00001000 0000000000000028 0000000000000008 WA 0 0 8 [23] .data(保存初始化过的数据) PROGBITS 0000000000601028 00001028 0000000000000010 0000000000000000 WA 0 0 8 [24] .bss NOBITS 0000000000601038 00001038 0000000000000008 0000000000000000 WA 0 0 1 [25] .comment PROGBITS 0000000000000000 00001038 0000000000000029 0000000000000001 MS 0 0 1 [26] .symtab SYMTAB 0000000000000000 00001068 00000000000005e8 0000000000000018 27 43 8 [27] .strtab STRTAB 0000000000000000 00001650 00000000000001e4 0000000000000000 0 0 1 [28] .shstrtab STRTAB 0000000000000000 00001834 0000000000000103 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)

内核中的数据结构

注意这里是linux2.6里的代码,mark一下以后再改

基本数据类型

/* 32-bit ELF base types. */ typedef __u32 Elf32_Addr; typedef __u16 Elf32_Half; typedef __u32 Elf32_Off; typedef __s32 Elf32_Sword; typedef __u32 Elf32_Word; /* 64-bit ELF base types. */ typedef __u64 Elf64_Addr; typedef __u16 Elf64_Half; typedef __s16 Elf64_SHalf; typedef __u64 Elf64_Off; typedef __s32 Elf64_Sword; typedef __u32 Elf64_Word; typedef __u64 Elf64_Xword; typedef __s64 Elf64_Sxword;

头部

ELF头
// 64位和32位ELF头结构大体相同便不列出 typedef struct elf32_hdr{ unsigned char e_ident[EI_NIDENT]; // ELF "magic number" Elf32_Half e_type; /* 保存ELF文件类型 */ Elf32_Half e_machine;/* 文件所需体系结构 */ Elf32_Word e_version;/* 版本信息 */ Elf32_Addr e_entry; /* 虚拟内存的入口点 */ 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; /* 程序头表的项的数目 */ Elf32_Half e_shentsize;/* 节头表一项的长度 */ Elf32_Half e_shnum; /* 节头表项的数目 */ Elf32_Half e_shstrndx;/* 节头表一项的索引 */ } Elf32_Ehdr;

2. 程序头表

程序头表由几项构成,表项数据结构如下(32位和64位没有什么太大区别)

typedef struct elf64_phdr { Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; /* 段文件偏移量 */ Elf64_Addr p_vaddr; /* 段虚拟地址 */ Elf64_Addr p_paddr; /* 段物理地址 */ Elf64_Xword p_filesz; /* 文件中段的长度 */ Elf64_Xword p_memsz; /* 内存中段的长度 */ Elf64_Xword p_align; /* 段的对齐值,在文件和内存中 */ } Elf64_Phdr;

3. 节头表

节头表由数组实现,每个元素包含一个节的信息,节的数据结构如下(32位和64位没有什么太大区别)

typedef struct elf64_shdr { Elf64_Word sh_name; /* 节名,字符串表中的索引 */ 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;

ELF标准定义了若干固定名称的节。这些用于执行大多数目标文件所需的标准任务。所有名称都 从点开始,以便与用户定义节或非标准节相区分。最重要的标准节如下所示

.bss保存程序未初始化的数据节,在程序开始运行前填充0字节.data包含已经初始化的程序数据。例如,预先初始化的结构,其中在编译时填充了静态数据。 这些数据可以在程序运行期间更改。.rodata保存了程序使用的只读数据,不能修改,例如字符串 .dynamic和.dynstr保存了动态信息.interp保存了程序解释器的名称,形式为字符串.shstrtab包含了一个字符串表,定义了节名称.strtab保存了一个字符串表,主要包含了符号表需要的各个字符串.symtab保存了二进制文件的符号表.init和.fini保存了程序初始化和结束时执行的机器指令。这两个节的内容通常是由编译器 及其辅助工具自动创建的,主要是为程序建立一个适当的运行时环境.text保存了主要的机器指令。

字符串表

因为其格式非常动态,内核不能提供一个固定的数据结构, 而必须手工分析现存数据

符号表

符号表表项数据结构(32位和64位没有什么太大区别)

typedef struct elf64_sym { Elf64_Word st_name; /* 符号名称,字符串表中的索引 */ unsigned char st_info; /* 类型和绑定属性(全局符号,局部符号,弱符号) */ unsigned char st_other; /* 语义未定义,0 */ Elf64_Half st_shndx; /* 相关节的索引 */ Elf64_Addr st_value; /* 符号的值 */ Elf64_Xword st_size; /* 符号的长度 */ } Elf64_Sym;

重定位项

重定位是将ELF文件中未定义符号关联到有效值的处理过程,在test.o中,这意味着对printf的未定义引用必须替换为该进程的虚拟地址空间中适当的机器代码所在的地址

在每个目标文件中,都有一个专门的表,包含了重定位项,标识了需要进行重定位之处。每个表 项都包含下列信息:

一个偏移量,指定了需要修改的项的位置对符号的引用(符号表的索引),提供了需要插入到重定位位置的数据待写:重定位工作原理

动态链接

待写


以上就是关于《ELF 二进制格式(二进制的e是什么意思)》的全部内容,本文网址:https://www.7ca.cn/baike/65526.shtml,如对您有帮助可以分享给好友,谢谢。
标签:
声明

排行榜