https://linux-audit.com/elf-binaries-on-linux-understanding-and-analysis/

我们理所当然的使用一些工具。其中一部分就是 linux 上常见的工具,像 psls。虽然这些命令看起来很简单,当仔细探究就会发现很多东西。这就要讲到 ELF(Executable and Linkable Format)。一个只有少部分人真正了解,但广泛使用的文件格式。让我们通过这篇入门教程去了解它。

通过阅读本手册,你讲学到:

  • 为什么要用 ELF,哪些文件用到 ELF
  • 了解 ELF 结构和详细格式
  • 如何读取和分析 ELF 文件
  • 哪些工具可用于二进制分析

什么是 elf 文件?

ELF 就是 Executable and Linkable Format,它定义了可执行文件、库文件和 core 文件的结构。这种格式能让操作系统正确解释文件中的机器指令。 ELF 文件是编译器或链接器输出的二进制格式。使用合适的工具,可以更好的分析和理解这种文件。

为什么要学习 ELF ?

在深入技术细节之前,最好解释一下,为什么了解 ELF 格式很有用。首先,它帮我们了解操作系统的内部运作。当出现故障,我们最好知道发生了什么(或为什么)。其次,当发生安全漏洞或发现恶意文件,需要研究 ELF 文件。最后一点也很重要,就是对开发有更好的理解。即便你使用高级语言比如 Golang,了解底层的知识对你仍然有好处。

所以为什么要学 ELF?

  • 了解操作系统工作原理
  • 软件开发
  • 数据取证与事件应急响应(DFIR)
  • 恶意软件研究(二进制分析)

从代码到进程

无论使用什么操作系统,它都需要将函数翻译成 CPU 的语言,也就是常说的机器码。一个函数可以是打开磁盘上的文件或显示内容到屏幕上。我们不会直接和 CPU 交流,而是使用编程语言和内部函数。编译器将这些函数翻译成目标文件。这些目标文件最终被链接器链接成可执行程序。结果就是一个二进制文件,它可以在特定平台和 CPU 执行。

开始之前

本文会分享很多命令。不要再生产环境使用它们。最好在测试机上使用。如果你想测试命令,拷贝一份现存的文件去使用它。另外,我们提供了一个小的 C 程序,你可以编译它。毕竟,尝试是最好的学习方式,也可以作比较。

ELF 文件结构解析

通常我们会误认为只有二进制或可执行的文件是 ELF 格式。其实目标文件也是 ELF 格式的,还有动态库、核转储文件,甚至内核和内核模块上也是。
file a.out

结构

由于 ELF 是可扩展的,每个文件的结构都有差异。ELF 文件的组成:
1. ELF 头
2. File 数据
使用 readelf 命令,可以看到文件的结构类似下面这样:
Details of an ELF binary

ELF 文件详情

ELF 头

从上图可知,ELF 头以魔数开头。魔数提供了关于文件的信息。前四个字节表示这是一个 ELF 文件(45=E,4c=L,46=F), 开头固定是 7F。
ELF 头是必须有的,以确保在链接或执行时数据能被正确解读。为了更好的理解 ELF 文件的内部机制,我们先了解一下文件头里包含的信息。

Class

ELF 结构中有个 Class 成员。它决定了文件的架构。它可以是 32-bit(=01) 或 64-bit(=02) 架构。上图显示是 02,readelf 命令将其解释为 ELF64。也就是说,这个文件是 64 位架构的。这很正常,说明这台机器使用的是现代 CPU。

Data

接着是 data 成员。它有两种值: 01 表示 LSB(Least_significant_bit), 就是常说的小端。另一个值是 02,表示 MSB (Most Significant Bit, 大端)。这个数值能够帮助操作系统正确解释文件中其余的内容。这很重要,因为不同的处理器处理不同的指令和数据结构。本例中,使用了 LSB, AMD64 通常都是小端的。
LSB 的影响可通过 hexdump 查看。我们看看 /bin/ls 的文件头。

$ hexdump -n 16 /bin/ps
0000000 457f 464c 0102 0001 0000 0000 0000 0000

0000010

可以看到这些数据和上图不同,那是因为上图正确解释了字节序。

版本

紧接着又是一个 “01”,它表示版本号。目前,只有一个版本号:就是 “01”。没什么可讲的。

OS/ABI

每个操作系统的常用函数上都有很大重叠。但每个函数还有具体的,或最小差异。通过 Application Binary Interface(ABI) 定义一个集合。这样操作系统和应用程序都知道面对的是什么,并将工作正常进行。这两个字段表示文件使用的 ABI 和相关版本。本例中,取值是 00,表示没有特殊扩展。结果显示为 System V

ABI 版本

需要时,可以指定 ABI 版本。

指令集

我们在文件头中也能找到期望的机器类型(amd64) 。

文件类型

type 字段,定义了文件类型。这是常见的几种。
- CORE(value 4)
- DYN(Shared object file),for libraries(value 3)
- EXEC (Executable file), for binaries (value 2)
- REL (Relocatable file), before linked into an executable file (value 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。想知道所有的机器类型,可以查看 ELF header file
通过 16 进制输出可以做很多事情,可以找工具帮你的忙。dumpelf 就是很好的工具,它的输出和 ELF 文件头基本一致。最好学习有哪些字段和每个字段的典型值。
文件头的这些字段都清楚之后,我们来看看文件数据部分的结构!

File data

除了 ELF 头,ELF 文件还包含三部分:
- Program Headers or Segments (9)
- Section Headers or Sections (28)
- Data
深入具体内容之前,先说说, ELF 有两个互补 ”视角“。一种被链接器使用的(segments)。 另一种是为指令和数据分类的(sections)。根据目标不同,使用不同的头类型。我们从 program headers 开始看,它从 ELF 可执行文件中获取。

Program headers

一个 ELF 文件包含 0 到多个 segments,它描述了如何创建运行时进程/内存映像。当内核遇到这些 segments,它通过 mmap(2) 系统调用将它们映射到虚拟地址空间 。也就是说,它将预定义的指令转换到内存中。如果 ELF 文件是可执行文件,它必须包含 program headers.否则,它就无法运行。操作系统使用这些头,底层的数据结构,构建进程。shared libraries 也是这样处理的。
An overview of program headers in an ELF binary

An overview of program headers in an ELF binary
可以看到例子中有 9 个 program headers。当第一次看到时,可能不知道这些都是什么。因此我们好好研究一下。
GNU_EH_FRAME

这是供 GNU C compiler(gcc) 使用的有序队列。它存储了异常处理函数。当出现异常,可以通过这个区域去处理。

GNU_STACK

这个头用来存储栈信息。栈是缓冲区,或储物间, 这里存储这一些项目,比如局部变量。它是后进先出,类似将盒子依次垒起来。当进程中的函数启动时会申请一部分空间。当函数退出,这块空间将被释放。有趣的是栈区是不可执行的,因为这会引入安全漏洞。比如,通过操纵内存,人们可以指向可执行栈并运行目标指令。
如果 GNU_STACK segment 不可用,就会使用可执行的栈。scanelfexecstack 工具可以显示详细的栈信息。

# scanelf -e /bin/ps
 TYPE   STK/REL/PTL FILE 
ET_EXEC RW- R-- RW- /bin/ps

# execstack -q /bin/ps
- /bin/ps

一些查看 program headers 的命令:

  • dumpelf (pax-utils)
  • elfls -S /bin/ps
  • eu-readelf –program-headers /bin/ps

ELF sections

Section headers

section headers 定义了文件中的所有 sections。如前所诉,这个视图被用来做链接和重定位。
Sections 出现在 gcc 将 c 代码转换成汇编的过程中,接着汇编器生成了各个 sections 的具体内容。
从前面的图中可以看到,一个 segments 可以有 0 或者多个 sections。对于可执行文件有四个主要的 section:.text、.data、.rodata 和 .bss。通过 readelf -S 可以看到。每个 sections 用不同的访问权限加载。

.text

包含可执行代码。它用读取和执行权限打包进 segment 。它只加载一次,也即内容不会变。可以用 objdump 工具查看。

12 .text 0000a3e9 0000000000402120 0000000000402120 00002120 2**4
CONTENTS, ALLOC, LOAD, READONLY, CODE
.data

初始化数据,可读写

.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

一些查看 section 和 heaers:

  • dumpelf
  • elfls -p /bin/ps
  • eu-readelf –section-headers /bin/ps
  • readelf -S /bin/ps
  • objdump -h /bin/ps
Section groups

一些 section 可以分组,成为一个整体,或者依赖。较新的链接器支持这个功能。目前,不容易找带 group section 的。

# readelf -g /bin/ps

There are no section groups in this file.

这不是很有趣,它显示了研究 ELF 工具集的好处。因此,有必要看看文末的工具集。

静态 vs 动态

处理 ELF 文件,有两种类型的链接方式。静态和动态指的是它们使用的库。为了优化,我们看到二进制一般是 ”动态“的,意味着它需要外部组件,才能运行。一般这些组件是通用的库,包含通用的函数,像打开文件或创建 socket。静态文件,包含了所有的库。体积更大,也更可移植(例如,在另一个系统使用)。

如果你想检查一个文件是静态还是动态,使用 file 命令。如下所示:

$ file /bin/ps
/bin/ps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), 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 恶意软件或固件的新手,可以考虑先学静态分析。就是分析文件而不执行。学好了之后,再学动态分析。通过你跑样本分析它们的行为。无论分析什么类型,确保在专用系统,最好在严格控制网络。尤其在分析未知样本或恶意软件时,非常必要。

流行工具

Radare2

Radare2 工具被 Sergi Alvarez 创造。’2‘ 表示相对第一版,完全重写,它被逆向工程师用来学习二进制如何工作。也可以拆解固件,恶意软件,和其他可执行格式。

Software packages

大多数 linux 系统已经安装二进制工具包。别的包可能会显示的更详细。用合适的工具可以简化你的工作,尤其是分析或学习 ELF 文件。所以我们收集了 一些相关工具。

elfutils

/usr/bin/eu-addr2line
/usr/bin/eu-ar – alternative to ar, to create, manipulate archive files
/usr/bin/eu-elfcmp
/usr/bin/eu-elflint – compliance check against gABI and psABI specifications
/usr/bin/eu-findtextrel – find text relocations
/usr/bin/eu-ld – combining object and archive files
/usr/bin/eu-make-debug-archive
/usr/bin/eu-nm – display symbols from object/executable files
/usr/bin/eu-objdump – show information of object files
/usr/bin/eu-ranlib – create index for archives for performance
/usr/bin/eu-readelf – human-readable display of ELF files
/usr/bin/eu-size – display size of each section (text, data, bss, etc)
/usr/bin/eu-stack – show the stack of a running process, or coredump
/usr/bin/eu-strings – display textual strings (similar to strings utility)
/usr/bin/eu-strip – strip ELF file from symbol tables
/usr/bin/eu-unstrip – add symbols and debug information to stripped binary
:elfutils 包是个很好的开始,它包含分析用的大多数工具

elfkickers

/usr/bin/ebfc – compiler for Brainfuck programming language
/usr/bin/elfls – shows program headers and section headers with flags
/usr/bin/elftoc – converts a binary into a C program
/usr/bin/infect – tool to inject a dropper, which creates setuid file in /tmp
/usr/bin/objres – creates an object from ordinary or binary data
/usr/bin/rebind – changes bindings/visibility of symbols in ELF file
/usr/bin/sstrip – strips unneeded components from ELF file
: ELFKickers 包专注操作 ELF 文件,分析畸形 ELF 文件会很有用。

pax-utils

/usr/bin/dumpelf – dump internal ELF structure
/usr/bin/lddtree – like ldd, with levels to show dependencies
/usr/bin/pspax – list ELF/PaX information about running processes
/usr/bin/scanelf – wide range of information, including PaX details
/usr/bin/scanmacho – shows details for Mach-O binaries (Mac OS X)
/usr/bin/symtree – displays a leveled output for symbols
: 包里面的工具可以搜索整个文件。大型分析有用,目标集中在搜集 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
prelink

/usr/bin/execstack – display or change if stack is executable
/usr/bin/prelink – remaps/relocates calls in ELF files, to speed up the process

example

如果你想创建一个可执行文件,只需写个小的C程序,编译它。这里有个例子,它打开 /tmp/test.txt, 读出它的内容并显示。确保创建 /tmp/test.txt 文件。

#include <stdio.h>

int main(int argc, char **argv)
{
   FILE *fp;
   char buff[255];

   fp = fopen("/tmp/test.txt", "r");
   fgets(buff, 255, fp);
   printf("%s\n", buff);
   fclose(fp);

   return 0;
}

这个程序可以这么编译:gcc -o test test.c

FAQ

What is ABI?

ABI 表示 Application Binary Interface ,它规定了操作系统和可执行文件之间的底层接口。

What is ELF?

ELF 表示 Executable and Linkable Format。它规定了可执行文件中如何存储指令。

如何知道未知文件的类型?

使用 file 命令。它会显示文件详情,基于文件头信息或 magic 数据。

结语

ELF 文件是可执行或可链接,取决于最初的目的,它包含需要的 segments 或 sections. Segments 被内核用于映射到内存。Sections 被链接器用来创建可执行文件或共享目标文件。
ELF 文件类型灵活,支持多种CPU类型、机器架构和操作系统。它也是可扩展的:每个文件根据需要,决定包含的部分。
头结构是文件中重要的部分,描述了 ELF 文件的内容。使用合适的工具,你可以获取文件的基本信息。接下来,你可以深入分析二进制文件,可以分析相关函数,或里面的字符。这是一个很好的开始对于恶意软件研究,或想了解程序行为(或别的什么)。

资源

如果你想知道更多 ELF 和逆向工程,你也许喜欢我们在 Linux Secutiry Expert 的工作。作为训练项目的一部分,我们有一个 reverse engineering module 和 lab 工作任务。
想读文档:可以看看 ELF format , Brian Raiter写的
想读源码,可以看看苹果 ELF structue header file
提示:如果你想更好的分析文件和样本,可以使用流行的 binary analysis tool

Logo

更多推荐