ELF 文件格式¶
请确保你已经了解了什么是虚拟内存,可以参考 分页内存简述
ELF(Executable and Linkable Format)是 Unix 系统实验室在与 Sun Microsystems 合作开发 SVR4(UNIX System V Release 4.0)时设计的。因此,ELF 首次出现在基于 SVR4 的 Solaris 2.0(又称 SunOS 5.0)中。该格式是在 System V ABI 中指定的。这是一种用途非常广泛的文件格式,既可用作可执行文件,也可用作共享库文件。
ELF 文件概述¶
ELF 文件大体上由文件头和数据组成,它还可以加上额外的调试信息。
事实上大部分的文件都是类似的结构
一般来说,ELF 有以下几个部分
- ELF 文件头
- Section header table,为 relocatable files 所必须,loadable files 可选,链接器需要 Section Table 进行链接
- Program header table,为 loadable files 所必需,但 relocatable files 可选,Program header table 描述了所有可加载的 segments 和其他数据结构,这或许会是我们遇见最多的
- 有文件头还得有内容,即 section 和 segment,这包括了各种可加载的数据,字符串表,符号表等等。每个 segment 里可以包含多个 sections。
ELF Header¶
ELF header 会出现在每个 ELF 文件的开头,它的定义如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
其中的数据类型在之后也会遇到
1 2 3 4 5 6 7 8 9 |
|
-
e_ident
,即 ELF identification,描述了“这是一个 ELF 文件”1 2
➜ xiao hexdump -C ./this_is_an_elf_file | head -1 00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
这 16 个 bytes 表示了不同的意思,接下来通过写一个简单的 ELF parser 来描述这一段内容吧!
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <assert.h> #include <fcntl.h> #include <unistd.h> #include <elf.h> #define EI_MAG0 0 /* e_ident[] indexes */ #define EI_MAG1 1 #define EI_MAG2 2 #define EI_MAG3 3 #define EI_CLASS 4 #define EI_DATA 5 #define EI_VERSION 6 #define EI_OSABI 7 #define EI_PAD 8 #define ELFMAG0 0x7f /* EI_MAG */ #define ELFMAG1 'E' #define ELFMAG2 'L' #define ELFMAG3 'F' #define ELFMAG "\177ELF" #define SELFMAG 4 int main() { int fd = open("./this_is_an_elf_file", 0, 0); uint8_t ident[0x10] = { 0 }; read(fd, &ident, 0x10); // the first 4 bytes uint8_t *magic = (uint8_t *)ident; // identify the ELF file assert( magic[0] == ELFMAG0 && magic[1] == ELFMAG1 && magic[2] == ELFMAG2 && magic[3] == ELFMAG3 ); // ELF class if (ident[EI_CLASS] == ELFCLASS64) { printf("[*] 64 bit files\n"); } else if (ident[EI_CLASS] == ELFCLASS32) { printf("[*] 32 bit files\n"); } // ELF encoding if (ident[EI_DATA] == ELFDATA2LSB) { printf("[*] little endian ELF\n"); } else if (ident[EI_DATA] == ELFDATA2MSB) { printf("[*] big endian ELF\n"); } // ELF OS ABI if (ident[EI_OSABI] == ELFOSABI_SYSV) { printf("[*] System V ABI\n"); } else if (ident[EI_OSABI] == ELFOSABI_HPUX) { printf("[*] HP-UX operating system ABI\n"); } else if (ident[EI_OSABI] == ELFOSABI_STANDALONE) { printf("[*] Standalone (embedded) application\n"); } printf("[*] API version: %d\n", ident[EI_VERSION]); return 0; }
-
e_type
描述 ELF 的类型,包括:ET_NONE
没有类型也是类型ET_REL
Relocatable fileET_EXEC
Executable fileET_DYN
Shared object fileET_CORE
Core file, Coredump 也是 ELF 类型
-
e_machine
描述目标平台 -
e_version
描述版本 -
e_entry
储存 ELF 文件的入口虚拟地址 -
e_phoff
储存 ELF Program header 的 offset,也就是说,Program header 储存在距离文件开头e_phoff
的位置 -
e_shoff
储存 ELF Section header 的 offset -
e_flags
处理器特定的 flags -
e_ehsize
ELF 文件头的大小 -
e_phentsize
ELF Program header entry 的大小 -
e_phnum
ELF Program header 的数量 -
e_shentsize
类似e_phentsize
但是是 Section -
e_shnum
同上类推 -
e_shstrndx
Section 中字符串表的 index
Section Header¶
Section 保存了 ELF 文件中的各种信息,Section header 的定义如下
1 2 3 4 5 6 7 8 9 10 11 12 |
|
-
sh_flags
描述了 Section 的一些属性,包括SHF_WRITE
,SHF_ALLOC
,SHF_EXECINSTR
等等 -
sh_type
描述了 Section 的类型,包括了储存 dynamic linking table 的SHT_DYNAMIC
,存放 linker symbol table 的SHT_SYMTAB
,由程序定义的SHT_PROGBITS
等等使用
readelf -S
可以观察程序的 section headers1 2 3 4 5 6 7 8 9 10 11 12
root@da070736a297:/# readelf -S /bin/sh There are 28 section headers, starting at offset 0x1d358: 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 0000000000000238 00000238 000000000000001c 0000000000000000 A 0 0 1 [ 2] .note.ABI-tag NOTE 0000000000000254 00000254 0000000000000020 0000000000000000 A 0 0 4
Program Header¶
在 ELF 程序中,多个 sections 可以存放在一个 segments 中用以加载。Program header table 就储存了用来描述 segment 的 Program header,其定义如下
1 2 3 4 5 6 7 8 9 10 |
|
-
p_type
表示 segment 的类型,包括有PT_LOAD
,PT_DYNAMIC
,PT_INTERP
等等。 -
p_flags
包括有PF_X
,PF_W
,PF_R
等等,通过不同的 bit 表达不同的信息,可以相互组合。这决定了 segment 映射时的权限。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
root@da070736a297:/# readelf -l /bin/sh Elf file type is DYN (Shared object file) Entry point 0x4a20 There are 9 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000001f8 0x00000000000001f8 R 0x8 INTERP 0x0000000000000238 0x0000000000000238 0x0000000000000238 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x000000000001b268 0x000000000001b268 R E 0x200000 LOAD 0x000000000001bf50 0x000000000021bf50 0x000000000021bf50 0x00000000000012d0 0x0000000000003f00 RW 0x200000 DYNAMIC 0x000000000001cb28 0x000000000021cb28 0x000000000021cb28 0x00000000000001f0 0x00000000000001f0 RW 0x8 NOTE 0x0000000000000254 0x0000000000000254 0x0000000000000254 0x0000000000000044 0x0000000000000044 R 0x4 GNU_EH_FRAME 0x00000000000179e4 0x00000000000179e4 0x00000000000179e4 0x00000000000007dc 0x00000000000007dc R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x000000000001bf50 0x000000000021bf50 0x000000000021bf50 0x00000000000010b0 0x00000000000010b0 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 .plt.got .text .fini .rodata .eh_frame_hdr .eh_frame 03 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 04 .dynamic 05 .note.ABI-tag .note.gnu.build-id 06 .eh_frame_hdr 07 08 .init_array .fini_array .data.rel.ro .dynamic .got
控制 ELF 的结构¶
以下的程序会把 a
和 function()
放入对应的 section 中:
1 2 |
|
你可以尝试使用 readelf
观察编译后的程序。
同时,也可以使用 Linker Script 来控制 ELF 结构:
1 2 3 4 5 6 7 8 |
|
使用以下命令编译
1 |
|
观察结果,你也可以使用 readelf
,这里使用 gdb
插件 gef
的vmmap
命令来观察,也可以直接观察 /proc/pid/
1 2 3 4 5 6 7 8 9 |
|
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 44 45 46 47 48 49 50 51 52 |
|