ELF 二进制文件格式

2018-09-09 12:59:47

为了能反编译(逆向工程) linux 二进制文件,你必须了解二进制格式。ELF 目前是类 unix 系统上的标准二进制格式,ELF 格式可用于可执行文件共享库目标文件coredump 文件内核引导镜像文件

ELF 跟程序加载动态链接符号表查询和其他许多紧密结合的组件一样,是计算机科学非常重要的组成部分。

ELF 文件格式

一个 ELF 文件有以下几种格式:

ET_NONE:未知类型,也就是该文件类型未定义
ET_REF:可重定位,也就是该文件被标记为可重定位的代码片段,有时称为目标文件。一般为位置无关代码,还没有被链接进可执行程序。比如我们看到的 .o 文件,里面包含了可创建可执行文件的代码和数据。
ET_EXEC:可执行文件,这类型文件也叫作程序,是一个进程开始运行的入口点。
ET_DYN:共享目标文件,动态可链接目标文件,即为共享库。这类共享库在程序运行时会被加载和链接到进程镜像中。
ET_CORE:core 文件,该文件是在程序崩溃或者当进程收到 SIGSEGV 信号(内存段冲突)时对整个进程镜像信息的转储。GDB 可以读取这些文件来调试并找出是什么原因导致程序崩溃。

如果想查看 ELF 文件头,可以执行如下指令:

查看可重定向文件ELF头

[root@192 c]# readelf -h lib.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:          536 (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:         11
  Section header string table index: 8

ELF 文件头从偏移量 0 开始,是对除了文件头以外部分的映射。该头部主要是为了标记 ELF 类型、结构、程序开始执行的入口点地址,同时也提供了其他 ELF 头(节头和程序头)类型的偏移。

查看可执行文件ELF头

[root@192 c]# readelf -h a.out 
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:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x400440
  Start of program headers:          64 (bytes into file)
  Start of section headers:          6592 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         30
  Section header string table index: 27

ELF 头结构定义

#define EI_NIDENT 16
typedef struct {
    unsigned char e_ident[EI_NIDENT];
    uint16_t e_type;
    uint16_t e_machine;
    uint32_t e_version;
    ElfN_Addr e_entry;
    ElfN_Off e_phoff;
    ElfN_Off e_shoff;
    uint32_t e_flags;
    uint16_t e_ehsize;
    uint16_t e_phentsize;
    uint16_t e_phnum;
    uint16_t e_shentsize;
    uint16_t e_shnum;
    uint16_t e_shstrndx;
} ElfN_Ehdr;


ELF 程序头

ELF 程序头是描述二进制里的段(segments),是程序加载所必须的。在程序加载时段会被内核解析,用来描述磁盘上可执行文件在内存里的布局和如何加载到内存。程序头表可以通过引用原始 ELF 头中的 e_phoff(程序头表偏移) 来访问。

程序头主要是为了描述程序执行时在内存中的布局。  

下面将会讨论5种常见的程序头。程序头描述了可执行文件(包含共享库)的段和段的类型(即数据或者代码的类型)。首先,我们来看下 Elf32_Phdr 结构体,用来标记 32-bit 可执行文件的程序头表里程序头入口点。

typedef struct {
    uint32_t p_type;     //段类型
    Elf32_Off p_offset;  //该段的第一个字节到文件开头的偏移量
    Elf32_Addr p_vaddr;  //该段的第一个字节处在的虚拟内存地址
    Elf32_Addr p_paddr;  //物理地址相关,被保留
    uint32_t p_filesz;   //文件镜像中该段的大小
    uint32_t p_memsz;    //内存镜像中该段的大小
    uint32_t p_flags;    //位掩码,PF_X-可执行段,PF_W-可写段,PF_R-可读段
    uint32_t p_align;    //内存对齐,可装载的进程段的 p_vaddr 和 p_offset 对页大小(一般为4096)取模必须等价。
                         //0或者1代表不需要对齐,否则 a_align 必须为正数,2的整数次幂,p_vaddr 和 p_offset
                         //对 a_align 取模必须等价
} Elf32_Phdr;

PT_LOAD

一个可执行文件至少包含一个 PT_LOAD 类型的段,这种类型的程序头描述的是可装载的段,表明这个段将被装载或者映射到内存。

例如,一个需要动态链接的可执行文件通常包含2个可装载的段(类型为 PT_LOAD):

1.存放程序代码的 text 段
2.存放全局变量和动态链接信息的 data 段

上面的两个段将会映射进内存,并通过 p_align 值来对齐。读者最好通过 man elf 来获取 Elf32_Phdr 更详细的解释。

通常 text 段(也叫代码段)的一般拥有的权限为 PF_X | PF_R。
data 段的一般拥有的权限为 PF_W | PF_R
一个文件如果感染了千面人病毒(polymorphic virus)可能会改变这些权限


©著作权归作者所有
收藏
推荐阅读
简介
天降大任于斯人也,必先苦其心志。