• ELF文件结构


    ELF文件结构

    ELF文件的全称是Executable and Linkable Format,直译为“可执行可链接格式”,包括目标文件(.o)、可执行文件(可以直接运行)、静态链接库、动态链接库、核心转储文件(core dump)。ELF文件的定义可以在/usr/include/elf.h中找到,本文主要介绍ELF64,ELF文件通常由下列部分组成:

    • ELF头(ELF header):放在ELF文件开头,描述该文件信息。

    • 节头表(Section header table):包含对节(section)的描述,对于可重定位文件(relocatable files)是必须的,对于可装载文件(loadable files)是可选的。

    • 程序头表(Program header table):对于可装载文件(loadable files)是必须的,对于可重定位文件(relocatable files)是可选的。用来描述加载程序或动态链接库所需要的段(segments)和其他数据结构。

    • 节或段的内容,包括符号表等。

    ELF头

    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;
    

    Elf64_Ehdr中的数据结构含义如下:

    数据结构名称 大小(byte) 对齐(byte) 目标
    Elf64_Addr 8 8 Unsigned program address
    Elf64_Off 8 8 Unsigned file offset
    Elf64_Half 2 2 Unsigned medium integer
    Elf64_Word 4 4 Unsigned integer
    Elf64_Sword 4 4 Signed integer
    Elf64_Xword 8 8 Unsigned long integer
    Elf64_Sxword 8 8 Signed long integer
    unsigned char 1 1 Unsigned small integer

    我们用readelf -h hello.o看一下从源文件到可执行文件:源文件的预处理、编译、汇编、链接中生成的hello.o文件的ELF头(因为我机器上显示的结果是中文,所以接下来就按照中文来说明,比如ELF头中类别对应Class,类型对应Type)。

    ELF 头:
      Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
      类别:                              ELF64
      数据:                              2 补码,小端序 (little endian)
      Version:                           1 (current)
      OS/ABI:                            UNIX - System V
      ABI 版本:                          0
      类型:                              REL (可重定位文件)
      系统架构:                          Advanced Micro Devices X86-64
      版本:                              0x1
      入口点地址:               0x0
      程序头起点:          0 (bytes into file)
      Start of section headers:          864 (bytes into file)
      标志:             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:         14
      Section header string table index: 13
    

    ELF头一开始的位置是魔术字符(Magic),在ASCII码中,'E'、'L'、'F'分别对应45、4c、46。当文件被映射到内存中,可以通过魔术字符确定映射地址。 ELF头与Elf64_Ehdr存在对应关系:

    Elf64_Ehdr成员 ELF头 含义
    e_ident Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
    e_type 类型: REL (可重定位文件) ELF文件类型(包括Relocatable/Executable/Shared/Core等类型)
    e_machine 系统架构: Advanced Micro Devices X86-64
    e_version 版本: 0x1 版本号,通常为0x1
    e_entry 入口点地址: 0x0 程序入口点的虚拟地址,操作系统在加载完程序后从该地址开始执行进程的指令。可重定位文件没有入口地址,所以为0。用readelf命令查看前文生成的可执行文件可以看到入口地址
    e_phoff 程序头起点:0 (bytes into file) 程序头表偏移(单位:byte)
    e_shoff Start of section headers: 864 (bytes into file) 节头表偏移(单位:byte)
    e_flags 标志:0x0 特定于处理器的标识
    e_ehsize Size of this header: 64 (bytes) ELF头本身的大小(单位:byte)
    e_phentsize Size of program headers: 0 (bytes) 程序头大小(单位:byte)
    e_phnum Number of program headers: 0 程序头个数
    e_shentsize Size of section headers: 64 (bytes) 节头大小(单位:byte)
    e_shnum Number of section headers: 14 节头个数
    e_shstrndx Section header string table index: 13 字符串表在节头表中索引

    对于魔数字符,再展开介绍一下。Magic共16个字节(Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00),第0~3个字节标识文件;第4个字节标识文件类别(类别: ELF64),1表示32位,2标识64位;第5个字节标识文件数据编码(数据: 2 补码,小端序 (little endian)),1标识小端序,2表示大端序;第6个字节标识文件版本(Version: 1 (current)),值为1;第7个字节标识操作系统和ABI(OS/ABI: UNIX - System V),0表示System V ABI,1表示HP-UX operating system,255表示Standalone (embedded) application;第8个字节标识ABI版本(ABI 版本: 0),值为1;剩余字节被保留为将来使用,设置为0。

    节头表

    一个目标文件(包括Relocatable/Executable/Shared/Core等类型)中包含很多节,这些节的信息保存在节头表中,表的每一项都是一个Elf64_Shdr结构体(也称为节描述符),节点信息包括节名、节大小、在文件中的偏移、读写权限等,编译器、链接器、装载器都是通过节头表来定位和访问各个节的属性的。/usr/include/elf.h中的Elf64_Shdr内容如下:

    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;  
    

    通过readelf -S hello.o命令查看hello.o文件的节头表。

    There are 14 section headers, starting at offset 0x360:
    
    节头:
      [号] 名称              类型             地址              偏移量
           大小              全体大小          旗标   链接   信息   对齐
      [ 0]                   NULL             0000000000000000  00000000
           0000000000000000  0000000000000000           0     0     0
      [ 1] .text             PROGBITS         0000000000000000  00000040
           0000000000000027  0000000000000000  AX       0     0     1
      [ 2] .rela.text        RELA             0000000000000000  00000270
           0000000000000060  0000000000000018   I      11     1     8
      [ 3] .data             PROGBITS         0000000000000000  00000067
           0000000000000000  0000000000000000  WA       0     0     1
      [ 4] .bss              NOBITS           0000000000000000  00000067
           0000000000000000  0000000000000000  WA       0     0     1
      [ 5] .rodata           PROGBITS         0000000000000000  00000067
           0000000000000019  0000000000000000   A       0     0     1
      [ 6] .comment          PROGBITS         0000000000000000  00000080
           000000000000002c  0000000000000001  MS       0     0     1
      [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000ac
           0000000000000000  0000000000000000           0     0     1
      [ 8] .note.gnu.propert NOTE             0000000000000000  000000b0
           0000000000000020  0000000000000000   A       0     0     8
      [ 9] .eh_frame         PROGBITS         0000000000000000  000000d0
           0000000000000038  0000000000000000   A       0     0     8
      [10] .rela.eh_frame    RELA             0000000000000000  000002d0
           0000000000000018  0000000000000018   I      11     9     8
      [11] .symtab           SYMTAB           0000000000000000  00000108
           0000000000000138  0000000000000018          12    10     8
      [12] .strtab           STRTAB           0000000000000000  00000240
           0000000000000029  0000000000000000           0     0     1
      [13] .shstrtab         STRTAB           0000000000000000  000002e8
           0000000000000074  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)
    

    正如ELF头中内容所示,hello.o中包含14个节。节名(sh_name)是一个单位为字节的偏移量,表示相对于节名字符串表(section name string table,也就是.shstrtab)起点的偏移,节名实际存放在节名字符串表中,通过查表得到。节类型(sh_type)分为以下几类,readelf命令的结果省略了前缀SHT_。

    节类型 含义
    SHT_NULL 无效节
    SHT_PROGBITS 程序节。代码节、数据节都是这种类型
    SHT_SYMTAB 符号表
    SHT_STRTAB 字符串表
    SHT_RELA 重定位表
    SHT_HASH 符号表的哈希表
    SHT_DYNAMIC 动态链接信息
    SHT_NOTE 提示性信息
    SHT_NOBITS 表示该节在文件中没有内容,不占用空间
    SHT_REL 重定位信息
    SHT_SHLIB 保留
    SHT_DYNSYM 动态链接的符号表
    SHT_LOOS/SHT_HIOS 特定环境使用
    SHT_LOPROC/SHT_HIPROC 特定处理器使用

    节标志位(sh_flags)表示该节在进程虚拟地址空间中的属性,1表示可写,2表示该节在进程空间中需要分配空间,有些包含指示或者控制信息的节不需要在进程分配空间,就没有这个标志,4表示该节在进程空间中可以被执行。

    节链接信息(sh_link、sh_info),如果节的类型是与链接相关的(无论是动态链接还是静态链接),如重定位表、符号表等,则sh_link、sh_info两个成员所包含的意义如下所示。其他类型的节,这两个成员没有意义。

    sh_type sh_link sh_info
    SHT_DYNAMIC 该节所使用的字符串表在节头表中的下标 0
    SHT_HASH 该节所使用的符号表在节头表中的下标 0
    SHT_REL 该节所使用的相应符号表在节头表中的下标 该重定位表所作用的节在节头表中的下标
    SHT_RELA 该节所使用的相应符号表在节头表中的下标 该重定位表所作用的节在节头表中的下标
    SHT_SYMTAB 操作系统相关 操作系统相关
    SHT_DYNSYM 操作系统相关 操作系统相关
    other SHN_UNDEF 0

    重要的节

    • .text节
      .text节是保存了程序代码指令的代码节。一段可执行程序,如果存在Phdr,则.text节就会存在于text段中。由于.text节保存了程序代码,所以节类型为SHT_PROGBITS。

    • .rodata节
      rodata节保存了只读的数据,如一行C语言代码中的字符串。由于.rodata节是只读的,所以只能存在于一个可执行文件的只读段中。因此,只能在text段(不是data段)中找到.rodata节。由于.rodata节是只读的,所以节类型为SHT_PROGBITS。

    • .plt节(过程链接表)
      .plt节也称为过程链接表(Procedure Linkage Table),其包含了动态链接器调用从共享库导入的函数所必需的相关代码。由于.plt节保存了代码,所以节类型为SHT_PROGBITS。

    • .data节
      .data节存在于data段中,其保存了初始化的全局变量和局部静态变量等数据。由于.data节保存了程序的变量数据,所以节类型为SHT_PROGBITS。

    • .bss节
      .bss节存在于data段中,占用空间不超过4字节,仅表示这个节本身的空间。.bss节保存了未进行初始化的全局数据和局部静态变量,程序加载时数据被初始化为0,在程序执行期间可以进行赋值。由于.bss节未保存实际的数据,所以节类型为SHT_NOBITS。

    • .got.plt节(全局偏移表-过程链接表)
      .got节保存了全局偏移表。.got节和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。由于.got.plt节与程序执行有关,所以节类型为SHT_PROGBITS。

    • .dynsym节(动态链接符号表)
      .dynsym节保存在text段中。其保存了从共享库导入的动态符号表。节类型为SHT_DYNSYM。

    • .dynstr节(动态链接字符串表)
      .dynstr保存了动态链接字符串表,表中存放了一系列字符串,这些字符串代表了符号名称,以空字符作为终止符。

    • .rel.*节(重定位表)
      重定位表保存了重定位相关的信息,这些信息描述了如何在链接或运行时,对ELF目标文件的某部分或者进程镜像进行补充或修改。由于重定位表保存了重定位相关的数据,所以节类型为SHT_REL。

    • .hash节
      .hash节也称为.gnu.hash,其保存了一个用于查找符号的散列表。

    • .symtab节(符号表)
      .symtab节是一个ElfN_Sym的数组,保存了符号信息。节类型为SHT_SYMTAB。

    • .strtab节(字符串表)
      .strtab节保存的是符号字符串表,表中的内容会被.symtab的ElfN_Sym结构中的st_name引用。节类型为SHT_STRTAB。

    • .ctors节和.dtors节
      .ctors(构造器)节和.dtors(析构器)节分别保存了指向构造函数和析构函数的函数指针,构造函数是在main函数执行之前需要执行的代码;析构函数是在main函数之后需要执行的代码。

    符号表包括.dynsym.symtab,前者是后者的子集。.dynsym保存了引用自外部文件的符号,只能在运行时被解析(flag为Alloc),而.symtab还保存了本地符号,用于调试和链接,不会被装载到内存中。

    程序头表

    /usr/include/elf.h,程序头表的结构如下:

    typedef struct
    {
      Elf64_Word	p_type;			/* Segment type */
      Elf64_Word	p_flags;		/* Segment flags */
      Elf64_Off	p_offset;		/* Segment file offset */
      Elf64_Addr	p_vaddr;		/* Segment virtual address */
      Elf64_Addr	p_paddr;		/* Segment physical address */
      Elf64_Xword	p_filesz;		/* Segment size in file */
      Elf64_Xword	p_memsz;		/* Segment size in memory */
      Elf64_Xword	p_align;		/* Segment alignment */
    } Elf64_Phdr;
    

    由于hello.o没有程序头表,所以通过readelf -l hello来读取可执行文件hello的程序头表,如下所示。

    Elf 文件类型为 DYN (共享目标文件)
    Entry point 0x1060
    There are 13 program headers, starting at offset 64
    
    程序头:
      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
                     0x00000000000005f8 0x00000000000005f8  R      0x1000
      LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000
                     0x00000000000001f5 0x00000000000001f5  R E    0x1000
      LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000
                     0x0000000000000170 0x0000000000000170  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   0x0000000000002020 0x0000000000002020 0x0000000000002020
                     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:
      段节...
       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 
    

    与节类似,段也有几种不同的类型(p_type),如下:

    段类型 含义
    PT_NULL 无效段
    PT_LOAD 可加载段
    PT_DYNAMIC 动态链接表
    PT_INTERP 程序解释器路径名
    PT_NOTE 信息段
    PT_SHLIB 保留
    PT_PHDR 程序头表
    PT_LOOS / PT_HIOS 特定环境使用
    PT_LOPROC / PT_HIPROC 特定处理机使用

    段标志位(p_flags)表示权限,X表示执行允许,W表示写允许,R表示读允许。

    最后再说一下节(section)和(segment)的关系。每个段包括一个或多个节,因为系统不关心这些节的具体内容,只关心这些节的权限(读、写、执行),将具有相同权限的节放到同一个段中。节是链接视角下的ELF文件,段是运行视角下的ELF文件。

    参考资料

    ELF-64 Object File Format
    计算机那些事(4)—— ELF文件结构
    CTF竞赛权威指南(Pwn篇)(杨超 编著,吴石 eee战队 审校,电子工业出版社)

  • 相关阅读:
    idea添加junit4(单元测试)
    hadoop学习之路(2)
    hadoop学习之路(1)
    MySQL主从配置(两台Linux之间)
    ZooKeeper概念详解,最全整理
    Linux小知识点
    Java Freemarker 实现导出 Word文档
    Oracle学习笔记十六:常用函数
    Oracle学习笔记十五:基本数据类型
    Oracle学习笔记十四:备份与恢复案例
  • 原文地址:https://www.cnblogs.com/husterzxh/p/16245090.html
Copyright © 2020-2023  润新知