ELF文件格式详解

发布时间:2023-05-22

一、ELF文件格式概述

ELF(Executable and Linkable Format)是一种可执行文件和可链接文件格式,被广泛地应用于Unix和类Unix系统中。ELF格式具有以下特点:

  1. 二进制格式,支持可执行文件、共享目标文件、目标文件等多种类型;
  2. 通过段(Section)概念对文件进行组织,段包含代码、数据、符号表等信息;
  3. 支持动态链接,共享目标文件可以在运行时被加载,共享;
  4. 可通过读取和修改ELF文件中的信息,实现二进制文件的混淆、加密等操作。

二、ELF文件格式组成

一个ELF文件主要包含三个部分:文件头(ELF header)、节区表(Section header table)和节区(Sections)。

1、文件头(ELF Header)

文件头定义了整个ELF文件的组织结构和属性信息,包括标识、目标类型、节区表的位置和大小等。文件头的结构定义如下:

struct Elf32_Ehdr {
   unsigned char e_ident[EI_NIDENT];
   Elf32_Half e_type;
   Elf32_Half e_machine;
   Elf32_Word e_version;
   Elf32_Addr e_entry;
   Elf32_Off e_phoff;
   Elf32_Off e_shoff;
   Elf32_Word e_flags;
   Elf32_Half e_ehsize;
   Elf32_Half e_phentsize;
   Elf32_Half e_phnum;
   Elf32_Half e_shentsize;
   Elf32_Half e_shnum;
   Elf32_Half e_shstrndx;
};

2、节区表(Section Header Table)

节区表是一张记录了整个ELF文件中每个节区的信息的表格。节区表一般位于ELF文件的开头末尾,节区表的信息包括节区的名称、偏移地址、大小等。节区表的结构定义如下:

struct Elf32_Shdr {
   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;
};

3、节区(Sections)

每个节区对应一个文件中的逻辑块,例如代码段、数据段、符号表段、字符串表段等等。节区的定义如下:

struct elf_section {
   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;
   void *data;
};

三、ELF文件格式分析

1、ELF文件标识(ELF Identification)

在ELF文件中,前4个字节称为文件的魔数,标识文件的类型。ELF文件标识的结构定义如下:

#define EI_NIDENT 16
struct Elf32_Ehdr {
   unsigned char e_ident[EI_NIDENT];
   //...
};

e_ident数组前四个字节是文件魔数,分别为0x7F'E''L''F'。文件是否是64位还是32位,处理器类型等信息,也在e_ident表中。

2、节区表(Section Header Table)

节区表是定义每个节(Section)的一张表,表中包含了每个节的信息,如名称、大小、对齐方式等。节区表的格式由Elf32_ShdrElf64_Shdr表示。 ELF文件中,标准的节区表通常包含以下几个节区:

  • 代码段(.text):存放程序的机器代码,只读不可写。
  • 数据段(.data):存放程序中已初始化的全局变量和静态变量,可读可写。
  • 只读数据段(.rodata):存放程序中的只读数据,如字符串常量等,只读不可写。
  • 符号表(.symtab):存放程序中的全局符号信息,符号表中包含符号名、符号类型、符号地址等。
  • 字符串表(.strtab):存放程序中使用的字符串常量的名称。
  • 重定位表(.rel.text):用于对代码段进行重定位操作。

3、程序头表(Program Header Table)

程序头表由Elf32_PhdrElf64_Phdr数组表示,其名称中的“P”代表“Program”,也就是程序头表。在程序执行时,程序头表会被内核解析并用来对整个进程的映像文件进行映像。

struct Elf32_Phdr {
   Elf32_Word p_type;
   Elf32_Off  p_offset;
   Elf32_Addr p_vaddr;
   Elf32_Addr p_paddr;
   Elf32_Word p_filesz;
   Elf32_Word p_memsz;
   Elf32_Word p_flags;
   Elf32_Word p_align;
};

4、重定位(Relocation)

重定位是指将静态编译的程序映射到内存中执行时,因为内存的地址可能与链接时的地址不同,需要对代码中的地址进行调整,以便代码可以在内存中正常运行。这个过程称为重定位(Relocation),在ELF中,重定位相关的信息保存在重定位表(.rel.text)和重定位表(.rel.data)中。

5、链接(Linking)

链接(Linking)是指将多个目标文件和库,最终链接成一个可执行文件或共享目标文件的过程。链接器会将多个目标文件的节合并为一个文件,并进行符号重定位等处理。

四、ELF文件格式示例

1、C语言程序生成ELF文件示例

// demo.c
#include <stdio.h>
void main() {
    printf("Hello World!\n");
}

使用gcc生成可执行程序demo

gcc -o demo demo.c

使用objdump查看demo的汇编代码:

objdump -d demo

通过objdump生成的结果可以看到,ELF二进制文件包含了很多信息,包括符号表、重定位表、反汇编的代码等信息。

2、动态链接库(shared object)生成ELF文件示例

多个进程间同时使用同一个共享库,可以减少内存使用,应用程序所用的内存被调用库的代码部分所共享,因为在内存中只有一份副本。所以,动态链接库的好处就在于节约了内存。以下是使用gcc来创建动态链接库:

// demo.c
#include <stdio.h>
void foo() {
    printf("Hello World");
}
void bar() {
    printf("Hello Bar");
}

编译动态链接库:

gcc -shared -o libdemo.so demo.c

编译完成后会生成一个名为libdemo.so的动态链接库文件,接下来需要将生成的动态库文件放到OS默认的动态库搜索目录中(例如/usr/lib目录)。 程序可以通过使用动态链接库进行编译,而不需要手动将库打包进可执行文件中:

// main.c
#include <dlfcn.h>
#include <stdio.h>
int main() {
    void *handle;
    void (*foo)(), (*bar)();
    handle = dlopen("libdemo.so", RTLD_LAZY);
    if (handle == NULL) {
        printf("Failed to open library.\n");
        return 1;
    }
    foo = dlsym(handle, "foo");
    bar = dlsym(handle, "bar");
    foo();
    bar();
    dlclose(handle);
    return 0;
}

编译可执行程序main

gcc -o main main.c -ldl

动态链接库和可执行程序main都是ELF文件格式。

五、ELF文件格式的应用

在Linux/BSD等类UNIX系统中,ELF文件格式被广泛应用于可执行程序、共享库、目标文件等类型的二进制文件。ELF文件格式将程序代码、数据、符号表、重定位等信息完整地保存在二进制文件中,为程序的开发、调试、优化提供了很好的基础。 同时,ELF文件格式也被用于二进制代码混淆、加密等操作中,通过修改ELF文件中的信息,可以达到有效保护程序机密性的目的。

六、总结

本文对ELF文件格式进行了详细的阐述,介绍了ELF文件格式的组成、特点以及应用。ELF文件格式是Unix和类Unix系统中常用的二进制文件格式,通过ELF格式,程序的开发、调试、优化等方面得到了很好的支持。同时,ELF文件格式也为程序的保护提供了很好的基础。