ELF文件格式
ELF(executable and linkable format)可执行可链接格式,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。
1.2.1ELF文件类型
ELF主要分为3种文件类型:
1、可重定位文件(relocatable file)后缀“.o” “.rel”:目标文件编译完成,尚未链接。一般多个目标文件链接成一个可执行文件或共享目标文件也,也就是下面两种文件。
2、可执行文件(executable file)后缀“.exec”:linux中执行的程序。
2、共享目标文件(shared object file)后缀".dyn":也就是库文件。用途1:和其他可重定位文件以及共享目标文件链接生成新的可重定位文件;用途2:动态链接时和可执行文件链接在一起运行。
还有一种ELF文件--核心转储文件(Core Dump file):程序异常终止后对地址空间的转储。可以使用gdb读取文件查找异常原因。
1.2.2 ELF文件的结构
目标文件既有链接阶段又有执行阶段,在两个阶段规定的文件格式有所不同。如图1。
图1
在链接视角程序头表是可选,通过节(section)来划分;在运行视角节头表是可选,通过段(segment)来划分(段大都来自链接阶段的节);注意:除ELF Header位置固定外,其他部分位置、大小通过ELF Header的内容来确定。
节一般包含代码节(.text)、数据节(.data)、BSS节(.bss)。代码节用于保存机器指令,数据节保存以初始化全局变量和局部静态变量,BSS节保存未初始化全局变量和局部静态变量。将数据和指令分开存放有利于安全,设置代码节对进程只读、数据和BSS节可读写。
ELF 文件头(ELF Header)
ELF文件头固定于程序的最开始,用于描述ELF文件的基本信息,例如文件类型、程序入口等;注意:文件头存在魔术字符(7f 45 4c 46),可通过搜索该字符来确定映射地址的位置。
以下通过readelf查看Header信息:
ubuntu@primary:~$ readelf -h hello.o 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: 696 (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: 12 Section header string table index: 11 ubuntu@primary:~$ readelf -h hello 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: DYN (Shared object file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x1060 Start of program headers: 64 (bytes into file) Start of section headers: 14712 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 13 Size of section headers: 64 (bytes) Number of section headers: 31 Section header string table index: 30
-h:查看ELF文件头信息
可重定位文件不需要程序头部表,因为其用于告诉操作系统该如何创建进程。
ELF文件头数据结构如下
#define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT];//前四个字节为魔术字符,下面依次是文件类型(16 || 32 || 64位)、编码方式(大小端)、版本、未使用字节开始地址(默认0) ELF32_Half e_type;//目标文件类型 ELF32_Half e_machine;//文件可以运行的机器架构 ELF32_Word e_version;//目标文件的版本 0无效版本 1当前版本 ELF32_Addr e_entry;//虚拟入口地址 ELF32_Off e_phoff;//程序头部表在文件中的字节偏移,若没有为0 ELF32_Off e_shoff;//节头表在文件中的字节偏移,没有为0 ELF32_Word e_flags;//特定处理器相关的标志 ELF32_Half e_ehsize;//ELF 文件头部的字节长度 ELF32_Half e_phentsize;//程序头部表中每个表项的字节长度,表项长度相同 ELF32_Half e_phnum;//程序头部表的项数,没有为0 ELF32_Half e_shentsize;//节头表中每个表项的字节长度,表项长度相同 ELF32_Half e_shnum;//节头表中的项数 ELF32_Half e_shstrndx;//节头表中与节名字符串表相关的表项的索引值 } Elf32_Ehdr;
程序头表(program header table)
程序头表是一个结构体数组,控制操作系统对进程的创建,也可以说是对段(segment)创建的控制,所以 .rel 和 .o文件没有程序头表,只有对于可执行文件和共享目标文件有意义。
以下为其数据结构
typedef struct { ELF32_Word p_type;//段的类型 ELF32_Off p_offset;//从文件开始到该段开头的第一个字节的偏移 ELF32_Addr p_vaddr;//该段第一个字节在内存中的虚拟地址 ELF32_Addr p_paddr;//该段第一个字节在内存中的物理地址(system V没有用此项) ELF32_Word p_filesz;//文件镜像中该段的大小 ELF32_Word p_memsz;//内存镜像中该段的大小 ELF32_Word p_flags;//段相关的标记 ELF32_Word p_align;//段在文件以及内存中的对齐方式。如果该值为 0 或 1 的话,表示不需要对齐。除此之外,p_align 应该是 2 的整数指数次方,并且 p_vaddr 与 p_offset 在模 p_align 的意义下,应该相等。 } Elf32_Phdr;
readelf -l:查看程序头表内容
ubuntu@primary:~$ readelf -l hello Elf file type is DYN (Shared object file) Entry point 0x1060 There are 13 program headers, starting at offset 64 Program Headers: 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 0x0000000000000600 0x0000000000000600 R 0x1000 LOAD 0x0000000000001000 0x0000000000001000 0x0000000000001000 0x0000000000000205 0x0000000000000205 R E 0x1000 LOAD 0x0000000000002000 0x0000000000002000 0x0000000000002000 0x0000000000000160 0x0000000000000160 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 0x0000000000002014 0x0000000000002014 0x0000000000002014 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: Segment Sections... 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
节头表(section header table)
记录在Elf64_Shdr结构体。节头表也是结构体数组,记录了节的名字、长度、偏移、权限。ELF 头中的 e_shoff
项给出了从文件开头到节头表位置的字节偏移。e_shnum
告诉了我们节头表包含的项数;e_shentsize
给出了每一项的字节大小。
typedef struct { ELF32_Word sh_name; ELF32_Word sh_type; ELF32_Word sh_flags;//可写,可执行,需要分配内存 ELF32_Addr sh_addr;//进程镜像中的位置,若在进程中 ELF32_Off sh_offset;//文件开始处之间的偏移 ELF32_Word sh_size; ELF32_Word sh_link; ELF32_Word sh_info; ELF32_Word sh_addralign; ELF32_Word sh_entsize; } Elf32_Shdr;
节(section)
每个节区都有对应的节头来描述它。但是反过来,节区头部并不一定会对应着一个节区。
静态链接:库函数或第三方库被编译和main函数一起。动态链接:库函数和三方库链接在一起,只有当函数被执行,相应的函数地址才被编译。
code section
.init & .init_array:此节区包含可执行指令,是进程初始化代码的一部分。程序开始执行时,系统会在开始调用主程序入口(通常指 C 语言的 main 函数)前执行这些代码。
.text:此节区包含程序的可执行指令。
.fini & .fini_array:此节区包含可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将执行这里的代码。
Data Related Sections
.BSS Section:未初始化的全局变量和局部静态变量对应的节。此节区不占用 ELF 文件空间,但占用程序的内存映像中的空间。当程序开始执行时,系统将把这些数据初始化为 0。bss 其实是 block started by symbol 的简写。
.data Section:这些节区包含初始化了的数据(全局变量和局部静态变量),会在程序的内存映像中出现。
.rodata Section:这些节区包含只读数据(初始化局部变量),这些数据通常参与进程映像的不可写段。
.symtab: Symbol Table (和编译原理有关,管理程序中的符号以便于对函数和变量进行重定位)
.strtab: String Table
该节区描述默认的字符串表,包含了一系列的以 NULL 结尾的字符串,存变量名、函数名
.shstrtab: Section Header String Table:字符串表(节区名)
.interp:程序的解释器节,程序执行开始为应用程序创造执行环境。可以接受一个文件描述符,将段映射到内存中。
.dynamic:程序进行动态链接时,会有的节
/* Dynamic section entry. */ typedef struct { Elf32_Sword d_tag; /* Dynamic entry type */ union { Elf32_Word d_val; /* Integer value */ Elf32_Addr d_ptr; /* Address value */ } d_un; } Elf32_Dyn; extern Elf32_Dyn_DYNAMIC[];
global offset table(got表)
.got:导入变量地址
.got.plt:导入函数地址
通过got表可以访问变量或函数的绝对地址,(可以理解为有基地址寄存器的作用)
Procedure Linkage Table(plt)
.rel(a).dyn & .rel(a).plt:动态链接的二进制文件中需要重定位的变量的信息。
函数定位
参考文献
2、ctf竞赛权威指南pwn篇