ELF详解

在读<程序员的自我修养 - 链接,装载,库>时,作者使用各种工具组合来查看了ELF文件的结构与布局.但是如果没有详细了解机制的话读起来还是会晕. 如果你已经了解了ELF文件的解析方法.那反过来读则不会有这种感觉. 因此, 现在我们就抛开各种工具, 从零开始解析一下 ELF文件的内容与布局.

基础介绍


ELF文件是什么



ELF 是 Executable Linkable Format 的简称. 它是Linux平台的可执行文件的存储格式. 它是一种基于 COFF( Common File Format )文件标准的变种.

目标文件是什么



目标文件是编译出来后,没有进行链接前的中间文件. 一般为Linux平台的目标文件的后缀是: .o 而在Windows平台的目标文件的后缀是.obj; 同时, 目标文件的存储格式实际与可执行文件使用的是同一套存储标准,即 .o文件在Linux平台上与可执行文件都是使用的ELF文件格式进行存储的.
实际上,除了目标文件,我们熟悉的动态链接库文件也是使用ELF文件标准进行存储的. 动态链接库在Linux平台下的后缀是:.so ,而在Windows平台下的后缀是:.dll (有Windows平台编程经验的小伙伴应该不陌生);
注: windows平台下的可执行文件标签是PE-COFF ,它是标准的COFF文件的变种.
COFF 文件格式是Unix System V Release 3 提出的标准.后来微软公司基于 COFF制定了 PE 文件格式标准. 并将其应用于Windows NT系统. Unix System V4 在COFF的基础上引入了ELF . 目前流行的Linux系统也是使用ELF格式作为基本的可执行文件格式.

目标文件格式


文件头


一般的二进制文件格式都会制定文件头, 这样我们在解析文件的时候可行先读取文件头.判定文件格式,然后再根据文件头信息进一步的确认后序步骤怎样解析.

 

#define EI_NIDENT 16

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

typedef struct {
        unsigned char   e_ident[EI_NIDENT];
        Elf64_Half      e_type;
        Elf64_Half      e_machine;
        Elf64_Word      e_version;
        Elf64_Addr      e_entry;
        Elf64_Off       e_phoff;
        Elf64_Off       e_shoff;     // 重要: 我们下一步的解析目标就从这里开始,这个段表的文件内偏移
        Elf64_Word      e_flags;
        Elf64_Half      e_ehsize;
        Elf64_Half      e_phentsize;
        Elf64_Half      e_phnum;
        Elf64_Half      e_shentsize;
        Elf64_Half      e_shnum;     // 重要: 这里给出了段表中的表项的个数
        Elf64_Half      e_shstrndx;  // 重要: 这里给出了段表中引用的用于表示段名的字符串表的段表项索引
} Elf64_Ehdr;

 

Linux的文件头定义了两种标准.一种是32位机器的,一种是64位机器的. 它们的内容结构其实是一样的(不是全部,可以基本认为是完全等价的),只是存储宽度不一样而已.

由于我要解析的是64位平台的文件.直接看64位版本即可.


表头中的字段主要描述elf文件服务的目标平台,以及相关的版本等信息. 同时给出了段表(Section Header Table)在文件中的偏移,字段: Elf64_Ehdr.e_shoff ,它表明段表在文件中的偏移. 偏移有了我们要知道有多少个item以及每一个item的大小, 这里的item就是段描述符: section descriptor 每一个描述符代表一个段. 比如在一个ELF文件中,就会存在一个.text的段. 那就会存在一个段描述符与之对应.

 

  • 段描述符的大小为: e_shentsize 它表示一个段描述符的大小.
  • 段描述符的个数为: e_shnum ,它表示我们的ELF文件中有多少个段描述符,同时也说明了一个ELF文件中有多少个段.
注: ELF文件头可以使用命令: readelf -h xxx来直接查看.

文件头中我们可以得到以下重要信息:

  • e_shoff: 段表在文件中的偏移位置.
  • e_shentsize: 段表中的每一个段描述符的大小.
  • e_shnum: 段描述符的个数. 即有多少个段.
  • e_shstrndx: 段表中所描述的段的相关信息所引用的字符串表所在的段表的段描述符的索引号.

有了以上信息,我们就可以接着往下解析. 段头相关的信息我不再赘述. 可以参考原书以及我的代码中的参考文献.

这个时候我们能得到的布局信息如下:

当前我们看到的视图


段表: Section Header Table


众所周知,在ELF文件中存在很多的段. 比如.text段,.data以及.bss段.而这些段所在的地方以及他们的基本属性, 这些由段的元数据进行描述,而这些元数据所在的地方就是段表. 这描述了各ELF段的基本信息,比如: 段名,段长度,段偏移,段属性等.
同样,存在两个版本的Shdr分别是32位与64位.

 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;

typedef struct {
	Elf64_Word	sh_name; //1
	Elf64_Word	sh_type;
	Elf64_Xword	sh_flags;
	Elf64_Addr	sh_addr;
	Elf64_Off	sh_offset;
	Elf64_Xword	sh_size;
	Elf64_Word	sh_link;
	Elf64_Word	sh_info;
	Elf64_Xword	sh_addralign;
	Elf64_Xword	sh_entsize;
} Elf64_Shdr;

以上信息重要的摘抄如下

NameValue
SHT_NULL 0
SHT_PROGBITS : 程序/数据/ 1
SHT_SYMTAB : 符号表 2
SHT_STRTAB : 字符串表 3
SHT_RELA : 重定位表 4
SHT_HASH: HASH 表 5
SHT_DYNAMIC: 动态链接 6
SHT_NOTE : 注释 7
SHT_NOBITS: 不存在于文件中 8
SHT_REL : 重定位的一些信息 9
SHT_SHLIB 10
SHT_DYNSYM : 动态链接的符号表 11
SHT_INIT_ARRAY 14
SHT_FINI_ARRAY 15
SHT_PREINIT_ARRAY 16
SHT_GROUP 17
SHT_SYMTAB_SHNDX 18
SHT_LOOS 0x60000000
SHT_HIOS 0x6fffffff
SHT_LOPROC 0x70000000
SHT_HIPROC 0x7fffffff
SHT_LOUSER 0x80000000
SHT_HIUSER 0xffffffff
  • sh_addr 加载到内存后的虚拟地址. 比如.text段.加载到内存后,其起始地址就应该是这里指定的虚拟地址
  • sh_offset: 相对于文件的偏移
  • sh_size: 段内容的大小.

这里先不急着把我们已经掌握的文件布局画出来,我们先把段名字符串表解析后再过来看它.

段名字符串表:

上面有说到一个段的名称在.段描述中进行定义时,是引用的段名字符串表的内容. 这个段就是.shstrtab段.

其实字符串表的内容非常简单. 大家可以理解为把一个个的ASCII字符串直接堆到一起就完事了. 比如下图:

这个是段名字符串表的内容

这个内容分析起来很简单. 第0个字符串是一个空串, 也就是上图中第一行中的黄色的那个\0 ;后序是跟着其它的字符串,紧接着的是:.systab然后是.strtab 用一个表格描述就是: String Table

字符串表内容布局

比如我要引用字符串:variable ,那sh_name 的值应该就是: 7; 所以是偏移.不是索引号.切记.

有了以上信息后,我们可以对ELF文件的大概而已基本就可以画出来了.

整体的ELF文件的大致布局

解析:

代码(github)

github.com/jianhong-li/

 

二进制分析

为了方便分析和对比.这里给出待研究的ELF文件的HEX的内容图:

程序结果:

文件头:

======================================

MAGIC NUM: 0x7F, 0x45, 0x4C, 0x46  
  platform: 64位对象
    endian: 小端
  main ver: 1
  EI_OSABI: 0
EI_ABIVERSION: 0
    e_type: 1  - ETYPE_REL
 e_machine: 62 - EM_X86_64
 e_version: 1
   e_entry: 0  - 备注: 当没有入口,或者是目标文件时,为0值.即无效
   e_phoff: 0
   e_shoff: 0x0000000000000190 , 段表偏移
   e_shnum: 13 , 段表数量
e_shstrndx: 10 , 段表引用注常量表编号
======================================

段表:

段表

符号表:

具体解析过程与细节见源代码, 一些如符号表解析. 相关的字段的取值的枚举定义,请参考官方文档和我的源码. 书中介绍的是32位的. 64位程序解析还是有很多不一样. 上体大家在解析时多参考官方文档.

源代码: github.com/jianhong-li/

红色的红
1 次咨询
5.0
电子科技大学 计算机硕士
2176 次赞同
去咨询

参考:

  1. System V Application Binary Interface - DRAFT - 24 April 2001
  2. 程序员的自我修养 - 书签版
  3. staroceans.org/e-book/e

 

 

 

posted on 2024-01-15 15:31  zxddesk  阅读(98)  评论(0编辑  收藏  举报

导航