1 ELF头

ELF头部

每个ELF二进制文件都是从ELF头部开始的,该头部是一系列结构化的字节,这些字节可以告诉你这是一个什么样的ELF二进制文件,以及在文件的什么地方查找其他内容。要找出ELF头部的格式,可以在/usr/include/elf.h或者ELF规范中查找其类型定义(以及其他与ELF相关的类型和常量的定义)。

64位ELF文件头部:

image-20220923112118154

ELF头部在此以C结构体Elf64_Ehdr的形式表示。如果你在/usr/include/elf.h中查找该结构体的定义,你可能会注意到给出的结构体定义包含诸如Elf64_Half和Elf64_word的类型。这些类型只用于uint16_t和uint32_t等整数类型的预定义(typedef)。

e_ident数组

ELF头部(和ELF二进制文件)以被称为e_ident的16字节数组开始。e_ident数组始终以4字节的“幻数”开头,以此标识该文件为ELF二进制文件。幻数由十六进制数字0x7f组成,后跟字母E、L及F的ASCII字符代码。

紧跟在幻数后面,有更多字节提供了有关ELF二进制文件类型规范的详细信息。在elf.h中,这些字节的索引(e_ident数组中4~15的索引)分别被称为EI_CLASS、EI_DATA、EI_VERSION、EI_OSABI、EI_ABIVERSION 及EI_PAD等。

  • EI_CLASS字节代表ELF规范中二进制文件的“类”。这有点用词不当,因为“类”一词如此通用,几乎可以指任何东西。该字节真正表示的是该二进制文件用于32位还是64位体系结构。EI_CLASS字节设置为常量ELFCLASS32(等于1)或ELFCLASS64(等于2);

  • EI_DATA字节指示二进制文件的字节序。值为ELFDATA2LSB(等于1)表示小端字节序,值为ELFDATA2MSB(等于2)表示大端字节序。与架构的位宽相关的是架构的字节序,多字节值(如整数)在内存中是以最低有效字节优先(小端),还是最高有效字节优先(大端);

  • EI_VERSION字节指示创建二进制文件时使用的ELF规范版本。当前唯一的有效值是EV_CURRENT,它被定义为1。

  • EI_OSABI和EI_ABIVERSION字节表示的是关于应用程序二进制接口(Application Binary Interface,ABI)和操作系统(Operating System,OS)的信息。如果EI_OSABI字节设置为非零,则意味着在ELF二进制文件中会使用一些ABI-或者OS-的具体扩展名。这可能会改变二进制文件中某些字段的含义,也可能表示存在非标准节。默认值零表示该二进制文件以UNIX System V ABI为目标。EI_ABIVERSION字节 表示二进制目标EI_OSABI字节指定的ABI的具体版本。通常该值为零,因为使用默认的EI_OSABI时无须指定任何版本信息。

  • EI_PAD字节实际上包含多字节,即e_ident中9~15的索引。这些字节当前都被指定填充。它们被保留供将来之用,但当前设置为零。

image-20220923112859483

在清单2-2中,e_ident数组在标记为Magic❶的行上显示,它以熟悉的4个幻数字节开头,后跟数字2(指示ELFCLASS64),然后是数字 1(ELFDATA2LSB),最后是数字1(EV_CURRENT)。由于EI_OSABI和 EI_ABIVERSION字节为默认值,因此其余字节全部为零,填充字节也为零。某些字节中包含的信息在指定行上重复声明,分别标记为Class、Data、Version、OS/ABI及ABI Version❷等。

e_type、e_machine及e_version字段

在e_ident数组之后,出现了一系列多字节整数字段。其中第一个称为e_type,该字段指定了二进制文件的类型。在这里最常遇到的值是ET_REL(表示可重定位的对象文件)、ET_EXEC(可执行二进制文件)及ET_DYN(动态库,也称为共享对象文件)。

Machine字段❹,表示二进制文件计划在体系结构上运行。如EM_X86_64,EM_386(32位 x86)和EM_ARM(ARM二进制)。

e_version字段的作用与e_ident数组中的EI_VERSION字节相 同。具体来说,它表示创建二进制文件时使用的ELF版本规范。由于该字段为32位宽,你可能会认为有很多可能的值,但实际上,唯一可能的值是1(EV_CURRENT),指定版本规范1。

e_entry字段

e_entry字段表示二进制文件的入口点,这是应该开始执行的虚拟地址。是解释器(通常是指 ld-linux.so)将二进制文件加载到虚拟内存后转移控制权的地方。入口点也是递归反汇编的有用起点。

e_phoff和e_shoff字段

分别指定了程序头表(program header table)和节头表(section header table)距离开始的偏移量。

对于示例二进制文件,偏移量分别为64字节和6632字节(清单2-2中在❼处的两行)。偏移量也可以设置为零,以指示该文件不包含程序头表或者节头表。需要注意,这些字段都是文件偏移量,意味着你应该读取文件获得ELF头部的字节数。换句话说,与前面讨论的e_entry字段不一 样,e_phoff和e_shoff指定的不是虚拟地址。

e_flags字段

e_flags字段保存了二进制文件在特定处理器的标志。

例如,计划在嵌入式平台上运行的ARM二进制文件可以在e_flags字段设置ARM特定标志,以指示关于嵌入式操作系统的其他详细信息(文件格式约定、堆栈组织等)。对于x86二进制文件,e_flags通常设置为零,因此无须过多关注。

e_ehsize字段

e_ehsize字段以字节单位指定了ELF头部的大小。如在readelf输出中看到的那样,对于64位x86二进制文件,ELF头部大小始终为64字节,而对于32位x86二进制文件,ELF头部的大小始终为52字节。

e_phentsize和e_phnum字段\e_shentsize和e_shnum字段

e_phoff和e_shoff字段指向程序头表和节头表开始的文件偏移位置。但是,要用链接器或加载器(或处理ELF二进制文件的其他程序) 遍历这些表,实际上还需要其他信息。具体地说,它们需要知道表中各个程序头或者表中各个节头的大小,以及每个表中程序头和节头的数量。这些信息由程序头表的e_phentsize和e_phnum字段,以及节头表的e_shentsize和e_shnum字段提供。在清单2-2中的示例二进制文件中,一共有9个程序头,每个程序头有56字节;一共有31个节头,每个节头有64字节❾。

e_shstrndx字段

e_shstrndx字段包含一个名为.shstrtab的、与特殊字符串表节(string table section)相关的头索引(在节头表中)。这是一个专用节,其中包含一个以空值结尾的ASCII字符串表,该表将所有节的名称存储在二进制文件中。ELF处理工具(如readelf)使用它来正确显示节的名称。

image-20220923153756288