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:此节区包含可执行的指令,是进程终止代码的一部分。程序正常退出时,系统将执行这里的代码。

.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:动态链接的二进制文件中需要重定位的变量的信息。

函数定位

参考文献

1、ctf-wiki.org/executable/elf

2、ctf竞赛权威指南pwn篇

posted @ 2021-04-19 19:46  cynault  阅读(363)  评论(0编辑  收藏  举报