我们将世界上的某些事物视为理所当然,尽管它们是真正的杰作。 Linux工具(例如ls和ps)就是其中之一。 尽管通常认为它们很简单,但是如果我们查看内部,则情况并非如此。 ELF,可执行和可链接格式也是如此。 一种普遍使用的文件格式,但很少有人理解。 本快速指南将帮助您理解。

阅读本指南后,您将学到:
- 为什么需要ELF格式以及使用什么类型的文件
- ELF文件结构和格式详细信息
- 如何读取和分析ELF文件的二进制内容
- 哪些工具用于分析二进制文件?
什么是ELF文件?
ELF代表可执行和可链接格式,并定义二进制文件,库和核心文件的结构。 格式规范允许操作系统正确解释文件中包含的机器指令。 ELF文件通常是编译器或链接器的输出文件,并且具有二进制格式。 使用适当的工具,可以对其进行分析和研究。
为什么要详细研究ELF?
在深入探讨技术细节之前,先不解释为什么理解ELF格式很有用。 首先,它使您可以研究操作系统的内部操作。 当出现问题时,这些知识将帮助您更好地了解实际发生的情况和原因。 同样,检查ELF文件的功能对于发现安全漏洞和检测可疑文件也很有价值。 最后,以更好地了解开发过程。 即使您使用Go之类的高级语言进行编程,您仍然会更好地了解幕后情况。
那么为什么要学习ELF?
- 大致了解操作系统
- 用于软件开发
- 数字取证和事件响应(DFIR)
- 恶意软件研究(二进制分析)
从源头到流程
无论我们使用哪种操作系统,都必须以某种方式将源代码功能转换为CPU语言-机器代码。 功能可能是最基本的功能,例如,打开磁盘上的文件或在屏幕上显示内容。 代替直接使用CPU语言,我们使用具有标准功能的编程语言。 然后,编译器将这些函数转换为目标代码。 然后,使用链接器将此目标代码链接到完整程序中。 结果是可以在特定平台和特定类型的CPU上执行的二进制文件。
开始之前
这篇文章包含许多团队。 最好在测试计算机上运行它们。 在现有二进制文件上运行这些命令之前,请先对其进行复制。 我们还将编写一个可以编译的小型C程序。 最终,练习是学习某些东西的最佳方法。
ELF文件的剖析
一个常见的误解是ELF文件仅用于二进制或可执行文件。 我们已经说过它们可以用于部分可执行文件(目标代码)。 另一个示例是库文件和核心转储(核心文件和a.out文件)。 ELF规范在Linux上也用于内核和内核模块。

结构形式
由于ELF文件的可扩展性,因此不同文件的结构可能有所不同。 ELF文件包含:
- ELF接头
- 资料
使用readelf命令,我们可以查看文件结构,看起来像这样:

ELF接头
如您在屏幕快照中所见,ELF标头以“幻数”开头。 此“魔术数字”提供有关文件的信息。 前4个字节确定这是一个ELF文件(45 = E,4c = L,46 = F,后跟7f)。
ELF标头是必需的。 为了在链接和执行期间正确解释数据,需要使用该命令。 为了更好地了解ELF文件的内部操作,了解此信息的用途很有用。
班级
声明ELF类型后,将跟随一个class字段。 该值表示文件所针对的体系结构。 它可以是01(32位体系结构)或02(64位)。 在这里,我们看到02,它由readelf命令翻译为ELF64文件,也就是说,此文件使用64位体系结构。 这并不奇怪,我的车上安装了现代处理器。
资料
接下来是“数据”字段,该字段具有两个选项:01-LSB(最低有效位),也称为little-endian,或02-MSB(最高有效位,big-endian)。 这些值有助于解释文件中的其余对象。 这很重要,因为不同类型的处理器对数据结构的处理方式不同。 在本例中,由于处理器具有AMD64架构,所以使用LSB。
在二进制文件上使用hexdump实用程序时,LSB效果变得可见。 让我们看一下/ bin / ps的ELF标头。
$ hexdump -n 16 /bin/ps 0000000 457f 464c 0102 0001 0000 0000 0000 0000 0000010
由于数据顺序的解释,我们看到值对不同。
版本号
然后跟随另一个魔术值“ 01”,即版本号。 当前仅提供版本01,因此该数字并不表示任何有趣的内容。
操作系统/ ABI
每个操作系统都有自己的函数调用方式,它们有很多共同点,但此外,每个系统也有很小的差异。 函数调用的顺序由应用程序二进制接口(ABI)确定。 OS / ABI字段描述了使用哪个ABI及其版本。 在我们的例子中,值是00,这意味着不使用特定的扩展名。 在输出中,这显示为系统V。
ABI版本
如有必要,可能会显示ABI版本。
车子
标题还指示计算机的预期类型(AMD64)。
型式
类型字段指示文件的用途。 以下是一些常见的文件类型。
核心(值4)
DYN(共享对象文件),库(值3)
EXEC(可执行文件),可执行文件(值2)
REL(可重定位文件),链接之前的文件(值1)
查看完整标题
尽管可以通过readelf查看某些字段,但实际上还有更多。 例如,您可以找出文件所针对的处理器。 使用hexdump查看完整的ELF标头和所有值。
7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............| 02 00 3e 00 01 00 00 00 a8 2b 40 00 00 00 00 00 |..>......+@.....| 40 00 00 00 00 00 00 00 30 65 01 00 00 00 00 00 |@.......0e......| 00 00 00 00 40 00 38 00 09 00 40 00 1c 00 1b 00 |....@.8...@.....|
(输出hexdump -C -n 64 / bin / ps)
高亮显示的字段确定机器的类型。 值3e为十进制62,对应于AMD64。 要了解所有文件类型,请参阅
此头文件。
尽管您可以通过十六进制转储来完成所有这些操作,但使用适合您的工作的工具还是有意义的。 dumpelf实用程序可能很有用。 它显示了与ELF标头匹配的格式化输出。 研究使用哪些字段以及它们的典型值将是一个很好的选择。
现在,我们已经解释了这些字段的含义,是时候看看它们背后的真正魔力了,并继续下一个标题!
档案资料
除了标题之外,ELF文件还包括三个部分。
在深入研究这些标题之前,了解ELF文件具有两种不同的“类型”将非常有用。 其中之一是为链接器设计的,并允许代码执行(段)。 另一个用于命令和数据(部分)。 根据目的,使用适当的头类型。 让我们从位于ELF可执行文件中的程序头开始。
节目名称
ELF文件由零个或多个段组成,并描述了如何创建进程(用于运行时执行的内存映像)。 当内核看到这些段时,它将使用mmap(2)系统调用将它们放置在虚拟地址空间中。 换句话说,它将预先准备好的指令转换为内存中的图像。 如果ELF文件是常规二进制文件,则需要这些程序标头,否则它将不起作用。 这些标头与相应的数据结构一起用于形成过程。 对于共享库,过程类似。

二进制ELF文件中的程序头
在此示例中,我们看到9个程序标题。 首先,很难理解它们的含义。 让我们深入研究细节。
GNU_EH_FRAME
这是GCC编译器使用的排序队列。 它存储异常处理程序。 如果出现问题,则可以使用它们来正确处理情况。
GNU_STACK
该头文件用于保存堆栈信息。 一个有趣的功能是堆栈不必是可执行的,因为这可能带来安全漏洞。
如果缺少GNU_STACK段,则使用可执行堆栈。 scanelf和execstack实用程序显示堆栈设备的详细信息。
查看程序头的命令:
- dumpelf(pax-utils)
- elfls -S / bin / ps
- eu-readelf –程序标题/ bin / ps
ELF部分
节标题
节标题定义文件的所有节。 如前所述,此信息用于链接和重定位。
在GNU C编译器将C代码转换为汇编器,然后GNU汇编器创建对象之后,节出现在ELF文件中。
如上图所示,一个段可以具有0个或多个段。 可执行文件有四个主要部分:.text,.data,.rodata和.bss。 这些部分中的每一个都以不同的权限引导,可以使用readelf -S查看。
.text
包含可执行代码。 它将被打包在具有读取和执行权限的段中。 它被下载一次,其内容不会更改。 这可以通过objdump实用程序看到。
12 .text 0000a3e9 0000000000402120 0000000000402120 00002120 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE
。数据
具有读写权限的初始化数据。
.rodata
具有只读权限的初始化数据。 (= A)。
.bss
具有读/写权限的未初始化数据。 (= WA)
[24] .data PROGBITS 00000000006172e0 000172e0 0000000000000100 0000000000000000 WA 0 0 8 [25] .bss NOBITS 00000000006173e0 000173e0 0000000000021110 0000000000000000 WA 0 0 32
查看部分和标题的命令。
- 垃圾堆
- elfls -p / bin / ps
- eu-readelf –section-headers / bin / ps
- readelf -S / bin / ps
- objdump -h / bin / ps
组组
可以将某些部分分组,就像它们形成一个整体一样。 新的链接器支持此功能。 但这并不常见。
尽管这看起来可能并不十分有趣,但是对ELF文件分析工具的了解会带来很多好处。 出于这个原因,本文末尾将对这些工具及其用途进行概述。
静态和动态二进制文件
在处理ELF二进制文件时,了解如何链接这两种类型的文件将很有用。 它们可以是静态的,也可以是动态的,这适用于它们使用的库。 如果二进制文件是“动态的”,则表示它使用包含一些常用功能的外部库,例如打开文件或创建网络套接字。 相反,静态二进制文件包含所有必需的库。
如果要检查文件是静态文件还是动态文件,请使用file命令。 她将显示以下内容:
$ file /bin/ps /bin/ps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), <b>dynamically linked (uses shared libs)</b>, for GNU/Linux 2.6.24, BuildID[sha1]=2053194ca4ee8754c695f5a7a7cff2fb8fdd297e, stripped
要确定使用了哪些外部库,只需在同一二进制文件上使用ldd即可:
$ ldd /bin/ps linux-vdso.so.1 => (0x00007ffe5ef0d000) libprocps.so.3 => /lib/x86_64-linux-gnu/libprocps.so.3 (0x00007f8959711000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f895934c000) /lib64/ld-linux-x86-64.so.2 (0x00007f8959935000)
提示:要查看更多依赖关系,最好使用lddtree实用程序。
二进制分析工具
如果要分析ELF文件,首先查看现有工具绝对有用。 有一些用于反向开发二进制文件和可执行代码的工具包。 如果您不熟悉ELF文件分析,请从静态分析开始。 静态分析意味着我们在不启动文件的情况下检查文件。 当您开始更好地了解他们的工作时,请继续进行动态分析。 运行示例并查看其实际行为。
热门工具
拉达雷2
Radare2工具箱由Sergi Alvarez创建。 数字2表示与第一个版本相比,该代码已被完全重写。 现在,它被许多研究人员用来研究代码的操作。
软体套件
大多数Linux系统都安装了binutils。 其他软件包可以帮助您查看更多信息。 正确的工具包将简化您的工作,尤其是在分析ELF文件时。 我在这里汇编了用于分析ELF文件的软件包和实用程序的列表。
小精灵/ usr / bin / eu-addr2line
/ usr / bin / eu-ar-ar的替代,用于创建和处理归档文件
/ usr / bin / eu-elfcmp
/ usr / bin / eu-elflint-检查是否符合gABI和psABI规范
/ usr / bin / eu-findtextrel-搜索文本重定位
/ usr / bin / eu-ld-结合对象和归档文件
/ usr / bin / eu-make-debug-archive
/ usr / bin / eu-nm-显示对象和可执行文件的符号
/ usr / bin / eu-objdump-显示目标文件中的信息
/ usr / bin / eu-ranlib-创建存档文件索引
/ usr / bin / eu-readelf-以可读形式显示ELF文件
/ usr / bin / eu-size-显示每个部分的大小(文本,数据,bss等)
/ usr / bin / eu-stack-显示当前进程或内核转储的堆栈
/ usr / bin / eu-strings-显示文本字符串(如strings实用程序)
/ usr / bin / eu-strip-从ELF文件中删除字符表
/ usr / bin / eu-unstrip-向二进制文件添加符号和调试信息
注意:elfutils软件包将是一个好的开始,它包含大多数分析工具
elfkickers/ usr / bin / ebfc-Brainfuck语言编译器
/ usr / bin / elfls-显示带有标志的程序头和节头
/ usr / bin / elftoc-将二进制文件转换为C程序
/ usr / bin / infect-注入滴管的实用程序会在/ tmp中创建setuid文件
/ usr / bin / objres-从常规或二进制数据创建对象
/ usr / bin / rebind-更改ELF文件中字符的绑定和可见性
/ usr / bin / sstrip-从ELF文件中删除不必要的组件
注意:ELFKickers程序包的作者侧重于处理ELF文件,使用“错误的” ELF二进制文件时,它可使您获得更多信息。
pax-utils/ usr / bin / dumpelf-内部ELF结构的转储
/ usr / bin / lddtree-类似于ldd,设置显示的依赖级别
/ usr / bin / pspax-显示有关正在运行的进程的ELF / PaX信息
/ usr / bin / scanelf-广泛的信息,包括PaX详细信息
/ usr / bin / scanmacho-显示Mach-O二进制文件的详细信息(Mac OS X)
/ usr / bin / symtree-显示树字符
注意:此软件包中的某些实用程序可以递归扫描目录,非常适合分析目录的全部内容。 重点是PaX研究工具。 除了支持ELF,您还可以从Mach-O二进制文件中提取信息。
输出例子
scanelf -a /bin/ps TYPE PAX PERM ENDIAN STK/REL/PTL TEXTREL RPATH BIND FILE ET_EXEC PeMRxS 0755 LE RW- R-- RW- - - LAZY /bin/ps
预链接/ usr / bin / execstack-您可以查看或更改有关堆栈是否可执行的信息
/ usr / bin / prelink-在ELF文件中重新定位调用以加快处理过程
常见问题
什么是ABI?
ABI是应用程序二进制接口,在操作系统和可执行代码之间定义了一个低级接口。
什么是ELF?
ELF是一种可执行且可链接的格式。 这是一种格式规范,定义了如何以可执行代码编写指令。
如何查看文件类型?
在分析的第一阶段使用file命令。 此命令能够显示从“魔术”数字和标题中提取的详细信息。
结论
ELF文件用于执行和链接。 根据目的,它们包含必要的段和节。 操作系统的内核扫描段并将其映射到内存(使用mmap)。 节由创建可执行文件或共享对象的链接器查看。
ELF文件非常灵活,并且支持各种类型的CPU,计算机体系结构和操作系统。 它也是可扩展的,根据所需的部分,每个文件的设计都不同。 通过使用正确的工具,您可以弄清楚文件的用途并检查二进制文件的内容。 您可以查看文件中包含的功能和行。 对于那些研究恶意软件或了解该进程为何以某种方式运行(或不运行)的人来说,这是一个良好的开端。
进一步研究的资源
如果您想了解有关ELF和逆向工程的更多信息,可以查看我们在Linux Security Expert中所做的工作。 作为课程的一部分,我们有
一个逆向工程模块,其中包括动手实验室工作。
对于那些喜欢阅读的人,一个精深的文档:
ELF格式和Brian Brian
撰写的
论文 ,也被称为ELFkickers。 对于那些想了解源代码的人,请查看Apple
记录的ELF标头 。
提示:
如果要更好地分析文件,请开始使用当前可用的
流行分析工具 。