转载请注明出处。https://rhirufxmbcyj.gitlab.io
以前工作需要,研究了一段时间的ELF文件,后来用完了就放置了
最近突然想到当时分析ELF文件格式的时候没有类似专门的工具,windows下只能依靠010 Editor的模板和虚拟机里linux的readelf来分析
虽然这两者结合起来挺强大的,但是还是希望自己写一个分析工具,顺便重拾一下原来的知识
什么是ELF文件
ELF的英文全称是The Executable and Linking Format,最初是由UNIX系统实验室开发、发布的ABI(Application Binary Interface)接口的一部分,也是Linux的主要可执行文件格式。
ELF文件三种主要类型
- 可重定位文件(ET_REL):包含了代码和数据,用于与其他目标文件经过链接,创建出可执行文件或共享目标文件(官方介绍)。通俗的来说,就是gcc加-c参数,只编译不链接创建出的文件,对应windows的obj文件。
- 可执行文件(ET_EXEC):包含了程序创建进程映像所需要的信息。对应windows的exe文件。
- 共享目标文件(ET_DYN):官方原话有些看不懂,貌似意思是可以被链接也可以执行,直接理解成动态库算了。
文件格式简介
ELF文件有两个视图,链接视图和执行视图,一开始学习ELF文件格式的时候对没有整体的概念,对这两个视图很是纠结,了解了整个文件结构以后再看这两个视图就容易理解了。
初步了解阶段就先简单粗暴的记住:ELF文件执行可以没有section header table,但是必须有program header table,链接时(包括obj链接和动态库链接)必须有section header table。
- elf header在文件开头,充当了“路线图”来描述文件结构
- program header table,如果是一个可执行的文件(包括共享目标文件,共享目标文件也可以执行,它是加载进其他EXEC文件中执行),程序头表就紧跟elf header后,这里包含的信息是系统创建进程内存映像所需要的信息
- section包含了代码、数据、符号、字符串等各种各样必不可少的信息
- section header table,用于描述section的表,每个section都在这里占一个表项,一般section header table是位于section后边的。如果文件用来链接的话,section header table必须存在。
一些前提:
我通常将section称为节,将program header称为segment(段)
elf执行不需要section header table但必须要program header table,我尝试将所有的section header设置为0,程序依然可以执行
elf链接不需要program header table,但必须要section header table,因为可重定位文件(obj文件)中没有program header table,但是有section header table,并且一个动态库so文件如果将section header table全部设置为0的话,同样链接不成功,因为链接过程中查找符号是依赖section‘查找的。
这也就是为什么上图中,链接视图中program header table是optional(可选的),而执行视图中section header table是optional(可选的)。
ELF中的数据类型
为什么elf文件自己定义数据类型:这是因为这种文件格式支持8位到32位多种类型的处理器(现在有了64位),elf文件使用自己定义的数据类型以便于兼容这些各个平台。
Elf32_Half 2字节
Elf32_Word 4字节
Elf32_Sword 4字节(有符号)
Elf32_Xword 8字节
Elf32_Sxword 8字节(有符号)
Elf32_Addr 4字节
Elf32_Off 4字节
Elf64_Half 2字节
Elf64_Word 4字节
Elf64_Sword 4字节(有符号)
Elf64_Xword 8字节
Elf64_Sxword 8字节(有符号)
Elf64_Addr 8字节
Elf64_Off 8字节
这些数据类型都可以在elf.h中找到定义。
ELF Header
从elf.h中摘出来的elf header结构体定义,这里的是32位的,64位的字段是相同的,不过某些字段(e_entry、e_phoff、e_shoff)的长度不同。
e_ident:介绍为Magic number and other info,总共为16个字节。Magic number就是文件最开始的4个字节0x7F,’E‘,’L‘,’F‘,用于判断是否是ELF文件可以直接将文件缓冲区指针强转为unsigned与宏定义#define ELFMAG “\177ELF”判断是否相等。
#define EI_CLASS 4 / File class byte index /,从宏定义中可看出第5个字节为ei_class,从后边的宏定义可以看出,这个字段是用来标识文件类型的,有ELFCLASS32和ELFCLASS64代表32位文件和64位文件。*
#define EI_DATA 5 / Data encoding byte index /,第6个字节是用来编码格式的,可根据此字节判断大端还是小端。
#define EI_VERSION 6 / File version byte index /,/ Value must be EV_CURRENT /,第7个字节….它说必须是EV_CURRENT就EV_CURRENT吧。。。
#define EI_OSABI 7 / OS ABI identification /,第8个字节,这个。。。看了后边的宏定义,貌似是用来标记系统平台的,有FREEBSD、LINUX、NETBSD、FREEBSD、ARM等。
#define EI_ABIVERSION 8 / ABI version /,第9个字节,介绍ABI的版本,好像都是0,x86、x64上没见过不是0的,其他平台还没看
#define EI_PAD 9 / Byte index of padding bytes /,到这里就没了,还剩7个字节,应该都是用于padding(填充)的
e_type:用于指定文件类型,主要有ET_REL 、ET_EXEC、ET_DYN 三种类型,其他类型在PC上没见过
e_machine:描述此elf文件支持的体系结构,PC上通常是EM_386和EM_X86_64
e_version:有一个版本相关的,目前在elf.h中只有一个EV_CURRENT。
e_entry:程序入口点的虚拟地址,也就是OEP。(此处需要注意,当elf文件的e_type为ET_EXEC时,这里的e_entry存放的是虚拟地址VA,如果e_type是ET_DYN时(不仅仅动态库,可执行文件也会是ET_DYN,即开启了随机基址的可执行程序),那这里存放的就是RVA,加载到内存中后还要加上imagebase)
e_phoff:program header table在文件中的位置(文件偏移)。
e_shoff:section header table在文件中的位置(文件偏移)
e_flags:该成员保存与文件关联的特定于处理器的标志,标志名称形式EF_machine_flag,有关标志定义,请参阅“Machine Information”。(官方介绍)。intel上遇到的都是0
e_ehsize:elf header的大小
e_phentsize:program header table的每个表项的大小
e_phnum:program header table表项的个数
e_shentsize:section header table的每个表项的大小
e_shnum:section header table表项的个数
e_shstrndx:Section header string table index,表示字符串表这个节在section header table的索引(注意:这个字符串表存放的是每个scetion的名称的字符串表,还有其他的字符串表)
到这里,elf header的内容就完了,各位可以参考着写一个解析elf格式的程序,只需要将elf header的信息输出,权当是练手,试着实现一个readelf -h的功能。
提供一个思路:
- 将文件内容读入缓冲区
- 检查文件的合法性(magic number)
- 根据elf header的e_machine或者EI_CLASS来判断32位还是64位
- 将缓冲区指针强转成Elf32_Ehdr或Elf64_Ehdr
- 输出结构体中各个字段的信息
我的项目的地址:https://github.com/rhirufxmbcyj/ElfReader
正在编写中……
参考:
[1] Executable and Linkable Format