Skip to content

Latest commit

 

History

History
556 lines (397 loc) · 28.3 KB

File metadata and controls

556 lines (397 loc) · 28.3 KB

Linux 二进制文件格式 ELF 101

什么是 ELF 文件?

ELF 是 可执行和可链接格式(Executable and Linkable Format)的缩写,它定义了二进制文件、库和核心文件的结构。正式的规范允许操作系统正确解释其底层机器指令。ELF 文件通常是编译器或链接器的输出,并且是一种二进制格式。有了合适的工具,这样的文件就可以被分析和更好地理解。

为什么要学习 ELF 的细节?

在深入更技术性的细节之前,解释一下为什么理解 ELF 格式是有用的。首先,它有助于学习我们操作系统的内部工作方式。当出现问题时,我们可能能更好地理解发生了什么(或为什么)。然后是能够研究 ELF 文件的价值,特别是在安全漏洞或发现可疑文件之后。最后但并非最不重要的是,在开发过程中为了更好地理解。即使大家使用像 Golang 这样的高级语言编程,我们仍然可能从了解幕后发生的事情中受益。

那么,为什么要学习更多关于 ELF 的知识呢?

  • 对操作系统工作方式的通用理解
  • 软件开发
  • 数字取证和事件响应(DFIR)
  • 恶意软件研究(二进制分析)

从源代码到进程

无论我们运行什么操作系统,它都需要将通用功能转换为 CPU 的语言,也称为机器代码。一个函数可能是一些基本的事情,比如在磁盘上打开一个文件或在屏幕上显示一些东西。我们不是直接与 CPU 对话,而是使用编程语言,使用内部函数。然后编译器将这些函数翻译成目标代码。这个目标代码随后被链接器工具链接成一个完整的程序。结果是一个二进制文件,然后可以在特定的平台和 CPU 类型上执行。

注意事项

不要在生产系统上运行相关命令。最好在测试机器上复制一个现有的二进制文件并使用相关命令。此外,我们提供了一个小型的 C 程序,我们可以编译它来进行验证。

相关工具安装:

sudo yum install binutils
sudo yum install pax-utils
sudo yum install prelink

ELF 文件的解剖

一个常见的误解是 ELF 文件只是用于二进制文件或可执行文件。我们已经看到它们可以用于部分片段(目标代码)。另一个例子是共享库或核心转储(那些核心或 a.out 文件)。ELF 规范甚至在 Linux 上用于内核本身和 Linux 内核模块。

file test
test: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=e0e7da04675930bdf7bd0e9fb5a5e1701a8a32b9, not stripped

文件命令显示了这个二进制文件的一些基本信息。

文件结构

由于 ELF 文件的可扩展设计,每个文件的结构都不同。一个 ELF 文件由以下部分组成:

  1. ELF 头部
  2. 文件数据

使用 readelf 命令,我们可以查看文件的结构,它看起来像这样:

readelf -h /usr/bin/ps
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x402f83
  Start of program headers:          64 (bytes into file)
  Start of section headers:          98256 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         9
  Size of section headers:           64 (bytes)
  Number of section headers:         29
  Section header string table index: 28

ELF 二进制文件的细节。

ELF 头部

如上述命令输出所示,ELF 头部以一些魔术开始。这个 ELF 头部魔术提供了关于文件的信息。前四个十六进制部分定义了这是一个 ELF 文件(45=E,4c=L,46=F),前缀是 7f 值。

这个 ELF 头部是强制性的。它确保在链接或执行期间数据被正确解释。为了更好地理解 ELF 文件的内部工作方式,了解这个头部信息是有用的。

类别

在 ELF 类型声明之后,定义了一个类别字段。这个值决定了文件的架构。它可以是 32位(=01)或 64位(=02)架构。魔术显示了一个 02,被 readelf 命令翻译为 ELF64 文件。换句话说,使用 64 位架构的 ELF 文件。

数据

下一部分是数据字段。它知道两个选项:01 用于 LSB 最低有效位,也称为小端。然后是值 02,用于 MSB(最高有效位,大端)。这个特定值有助于正确解释文件内剩余的对象。这很重要,因为不同类型的处理器以不同的方式处理传入的指令和数据结构。在这种情况下,使用了 LSB,这对于 AMD64 类型的处理器来说是常见的。

LSB 的效果在使用 hexdump 对二进制文件进行操作时变得明显。让我们展示一下 /usr/bin/ps 的 ELF 头部细节。

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

我们可以看到值对是不同的,这是由于字节顺序的正确解释造成的。

版本

接下来是魔术中的另一个“01”,这是版本号。目前,只有一个版本类型:当前版本,其值是“01”。

OS/ABI

每个操作系统在公共功能上有很大一部分重叠。此外,它们都有特定的功能,或者至少在它们之间有细微的差别。正确的设置定义是通过 应用程序二进制接口 (ABI)。这样,操作系统和应用程序都知道该期待什么,函数被正确转发。这两个字段描述了使用了什么 ABI 以及相关的版本。在这种情况下,值是 00,这意味着没有使用特定的扩展。输出显示为 System V

ABI 版本

如果需要,可以指定 ABI 的版本。

机器

我们还可以在头部找到预期的机器类型。

类型

类型 字段告诉我们文件的目的是什么。有一些常见的文件类型。

  • CORE(值 4)
  • DYN(共享对象文件),用于库(值 3)
  • EXEC(可执行文件),用于二进制文件(值 2)
  • REL(可重定位文件),在链接成可执行文件之前(值 1)

查看完整标头详细信息

虽然一些字段已经可以通过 readelf 输出的魔术值显示,但还有更多。例如,文件是针对什么特定的处理器类型。使用 hexdump 我们可以看到完整的 ELF 头部及其值。

hexdump -C -n 64 /usr/bin/ps
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  83 2f 40 00 00 00 00 00  |..>....../@.....|
00000020  40 00 00 00 00 00 00 00  d0 7f 01 00 00 00 00 00  |@...............|
00000030  00 00 00 00 40 00 38 00  09 00 40 00 1d 00 1c 00  |....@.8...@.....|
00000040

上面的高亮字段是定义机器类型的地方。值 3e 是十进制的 62,等于 AMD64。要了解所有机器类型,可以查看这个 ELF 头部文件。

虽然我们可以用十六进制转储做很多事情,但让工具为大家工作是有意义的。dumpelf 工具在这方面可以提供帮助。它显示了一个格式化的输出,非常类似于 ELF 头部文件。非常适合学习哪些字段被使用以及它们的典型值。

澄清了所有这些字段之后,是时候看看真正的魔法发生在哪里,进入下一个头部了!

文件数据

除了 ELF 头部,ELF 文件由三个部分组成。

  • 程序头部(9)
  • 节头部(28)
  • 数据

在我们深入这些头部之前,最好知道 ELF 有两个互补的“视图”。一个用于链接器允许执行(段)。另一个用于对指令和数据进行分类(节)。因此,根据目标,使用相关的头部类型。让我们从程序头部开始,我们在 ELF 二进制文件中找到它们。

程序头

一个 ELF 文件由零个或多个Segments)组成,并描述了如何创建运行时执行的进程/内存映像。当内核看到这些段时,它使用它们将它们映射到虚拟地址空间,使用 mmap(2) 系统调用。换句话说,它将预定义的指令转换为内存映像。如果 ELF 文件是一个普通的二进制文件,它需要这些程序头部。否则,它根本不能运行。它使用这些头部和底层的数据结构来形成一个进程。对于共享库,这个过程是类似的。

readelf -l /usr/bin/ps

Elf file type is EXEC (Executable file)
Entry point 0x402f83
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000016844 0x0000000000016844  R E    200000
  LOAD           0x0000000000016de0 0x0000000000616de0 0x0000000000616de0
                 0x0000000000000640 0x00000000000217a8  RW     200000
  DYNAMIC        0x0000000000016df8 0x0000000000616df8 0x0000000000616df8
                 0x0000000000000200 0x0000000000000200  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x00000000000141fc 0x00000000004141fc 0x00000000004141fc
                 0x0000000000000784 0x0000000000000784  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000016de0 0x0000000000616de0 0x0000000000616de0
                 0x0000000000000220 0x0000000000000220  R      1

 Section to Segment mapping:
  Segment Sections...
   00
   01     .interp
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
   04     .dynamic
   05     .note.ABI-tag .note.gnu.build-id
   06     .eh_frame_hdr
   07
   08     .init_array .fini_array .jcr .dynamic .got

ELF 二进制文件中的程序头部概览。

我们在示例中看到有 9 个程序头部。

PT_LOAD:

这是最常见的段类型,用于定义需要加载到内存的部分。通常分为两个区域:一个只读区域(通常包含代码)和一个可读写区域(通常包含数据)。

PT_DYNAMIC:

包含与动态链接有关的信息,例如共享库的路径、符号表等。动态链接器会使用这个段来加载动态库。

PT_INTERP:

如果程序需要由解释器(例如动态链接器)执行,这个段会指定解释器的路径。

GNU_EH_FRAME

这是一个由 GNU C 编译器(gcc)使用的排序队列。它存储异常处理程序。所以当出现问题时,它可以利用这个区域正确处理。

GNU_STACK

这个头部用于存储堆栈信息。堆栈是一个缓冲区,或临时存放的地方,存储像局部变量这样的项目。这将以 LIFO(后进先出)的方式发生,类似于将箱子堆叠在一起。当一个进程函数开始时,会保留一个块。当函数结束时,它将被标记为再次空闲。现在有趣的部分是堆栈不应该是可执行的,因为这可能会引入安全漏洞。通过操纵内存,人们可以引用这个可执行堆栈并运行预期的指令。

如果 GNU_STACK 段不可用,那么通常使用可执行堆栈。scanelf 和 execstack 是两个展示堆栈详细信息的工具。

scanelf -e /usr/bin/ps
 TYPE   STK/REL/PTL FILE
ET_EXEC RW- R-- RW- /usr/bin/ps
execstack -q /usr/bin/ps
- /usr/bin/ps

查看程序头部的命令。

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

ELF 节(Section)

节头部

节头部定义了文件中的所有节。正如所说,这个“视图”用于链接和重定位。

节可以在 ELF 二进制文件中找到,在 GNU C 编译器将 C 代码转换为汇编语言之后,然后是 GNU 汇编器,它创建了它的对象。

如例子所示,一个段可以有 0 个或多个节。对于可执行文件,有四个主要节:.text.data.rodata.bss。每个这些节都以不同的访问权限加载,这可以通过 readelf -S 看到。

readelf -S /usr/bin/ps
There are 29 section headers, starting at offset 0x17fd0:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       0000000000000050  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002e8  000002e8
       0000000000000a20  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400d08  00000d08
       000000000000042d  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000401136  00001136
       00000000000000d8  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000401210  00001210
       00000000000000a0  0000000000000000   A       6     3     8
  [ 9] .rela.dyn         RELA             00000000004012b0  000012b0
       00000000000000a8  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000401358  00001358
       0000000000000900  0000000000000018  AI       5    23     8
  [11] .init             PROGBITS         0000000000401c58  00001c58
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000401c80  00001c80
       0000000000000610  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000402290  00002290
       000000000000a4fa  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         000000000040c78c  0000c78c
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         000000000040c7a0  0000c7a0
       0000000000007a5a  0000000000000000   A       0     0     32
  [16] .eh_frame_hdr     PROGBITS         00000000004141fc  000141fc
       0000000000000784  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         0000000000414980  00014980
       0000000000001ec4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000616de0  00016de0
       0000000000000008  0000000000000008  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000616de8  00016de8
       0000000000000008  0000000000000008  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000616df0  00016df0
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000616df8  00016df8
       0000000000000200  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000616ff8  00016ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000617000  00017000
       0000000000000318  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000617318  00017318
       0000000000000108  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000617420  00017420
       0000000000021168  0000000000000000  WA       0     0     32
  [26] .gnu_debuglink    PROGBITS         0000000000000000  00017420
       0000000000000010  0000000000000000           0     0     4
  [27] .gnu_debugdata    PROGBITS         0000000000000000  00017430
       0000000000000a90  0000000000000000           0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00017ec0
       000000000000010d  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)

.text

包含可执行代码。它将被打包到一个具有读写权限的段中。它只加载一次,因为内容不会改变。这可以通过 objdump 实用程序看到。

objdump -d /usr/bin/ps -j .text | head

/usr/bin/ps:     file format elf64-x86-64


Disassembly of section .text:

0000000000402290 <.text>:
  402290: 41 55                 push   %r13
  402292: be c0 c4 40 00        mov    $0x40c4c0,%esi
  402297: 41 54                 push   %r12

.data

初始化数据,具有读写访问权限

.rodata

初始化数据,仅具有读取访问权限(=A)。

.bss

未初始化数据,具有读写访问权限(=WA)

  [24] .data             PROGBITS         0000000000617318  00017318
       0000000000000108  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000617420  00017420
       0000000000021168  0000000000000000  WA       0     0     32

查看节和头部的命令。

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

节组

一些节可以被分组,因为它们形成一个整体,或者换句话说是一个依赖关系。较新的链接器支持这个功能。尽管如此,这并不常见于发现这种情况:

readelf -g /usr/bin/ps

There are no section groups in this file.

虽然这看起来可能不太有趣,但它清楚地表明了研究可用的 ELF 工具包的好处,用于分析。因此,在本文的末尾包含了工具及其主要目标的概述。

静态 vs. 动态二进制文件

在处理 ELF 二进制文件时,了解有两种类型以及它们的链接方式是很好的。类型是静态的还是动态的,指的是所使用的库。出于优化目的,我们经常看到二进制文件是“动态的”,这意味着它需要外部组件才能正确运行。通常这些外部组件是正常的库,包含像打开文件或创建网络套接字这样的公共函数。另一方面,静态二进制文件包含了所有库。这使它们变得更大,但更具可移植性(例如,在另一个系统上使用它们)。

如果我们想检查一个文件是静态编译还是动态编译,使用 file 命令。如果它显示类似的东西:

file /usr/bin/ps
/usr/bin/ps: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=36fb21d67d92790800dc4212bd9f610602e3dc08, stripped

要确定使用了哪些外部库,只需对同一二进制文件使用 ldd:

ldd /usr/bin/ps
 linux-vdso.so.1 (0x00007fff72f72000)
 libprocps.so.4 => /lib64/libprocps.so.4 (0x00007f4e1e354000)
 libsystemd.so.0 => /lib64/libsystemd.so.0 (0x00007f4e1e123000)
 libdl.so.2 => /lib64/libdl.so.2 (0x00007f4e1df1f000)
 libc.so.6 => /lib64/libc.so.6 (0x00007f4e1db65000)
 libcap.so.2 => /lib64/libcap.so.2 (0x00007f4e1d960000)
 libm.so.6 => /lib64/libm.so.6 (0x00007f4e1d5e2000)
 librt.so.1 => /lib64/librt.so.1 (0x00007f4e1d3da000)
...

提示: 要查看底层依赖关系,使用 lddtree 实用程序可能更好。

段与节的区别

  • 用途不同:段是操作系统加载和运行程序时使用的,而节主要是编译器和链接器在生成和管理可执行文件时使用的。段更关注的是程序运行时的内存布局,而节关注的是文件的组织结构。
  • 对应关系:多个节可以被映射到同一个段中。例如,.text 节和 .rodata 节可以映射到同一个只读的 PT_LOAD 段中。而 .data.bss 节可以被映射到同一个可读写的 PT_LOAD 段中。
  • 操作系统处理:操作系统加载可执行文件时只关心,它会将映射到进程的地址空间中,并根据需要设置段的权限(如可执行、只读、可写)。而主要在链接调试过程中使用,操作系统不会直接操作节。

假设一个简单的 ELF 文件有以下节和段:

  • .text 节存放程序的代码
  • .data 节存放已初始化的数据
  • .bss 节存放未初始化的数据

在运行时,操作系统会将这些组织成

  • .text 节被加载到一个只读且可执行的 PT_LOAD 段中
  • .data.bss 节则被加载到一个可读写的 PT_LOAD 段中

二进制分析工具

当我们想要分析 ELF 文件时,首先寻找可用的工具肯定是有用的。一些软件包提供了一个工具包,用于逆向工程二进制文件或可执行代码。如果读者是分析 ELF 恶意软件或固件的新手,请考虑首先学习 静态分析。这意味着我们在不实际执行文件的情况下检查文件。当大家更好地了解它们的工作原理时,然后转向 动态分析。现在我们将运行文件样本,并在低级代码作为实际处理器指令执行时查看它们的实际行为。无论我们进行什么类型的分析,请确保在专用系统上进行,最好有严格的网络规则。这特别是当我们处理未知样本或与恶意软件相关时。

流行工具

Radare2

Radare2 工具包由 Sergi Alvarez 创建。版本中的“2”指的是与第一版本相比的完整重写。它现在被许多逆向工程师用来了解二进制文件的工作原理。它可以用来解剖固件、恶意软件和任何其他看起来是可执行格式的东西。

软件包

大多数 Linux 系统已经安装了 binutils 包。其他包可能有助于显示更多细节。拥有正确的工具包可能会简化大家的工作,特别是当我们进行分析或更多地了解 ELF 文件时。因此,我们收集了一个包列表和相关实用程序。

elfutils
  • /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/ebfcBrainfuck 编程语言的编译器
  • /usr/bin/elfls – 显示带有标志的程序头部和节头部
  • /usr/bin/elftoc – 将二进制文件转换为 C 程序
  • /usr/bin/infect – 注入一个 dropper 的工具,在 /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 /usr/bin/ps
 TYPE    PAX   PERM ENDIAN STK/REL/PTL TEXTREL RPATH BIND FILE
ET_EXEC PeMRxS 0755 LE RW- R-- RW-    -      -   LAZY /usr/bin/ps
prelink
  • /usr/bin/execstack – 显示或更改堆栈是否可执行
  • /usr/bin/prelink – 重新映射/重新定位 ELF 文件中的调用,以加快进程

示例二进制文件

如果我们想自己创建一个二进制文件,只需创建一个小的 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 来编译

常见问题解答

什么是 ABI?

ABI 是 Application Binary Interface 的缩写,它指定了操作系统和一段可执行代码之间的低级接口。

什么是 ELF?

ELF 是 Executable and Linkable Format 的缩写。它是一个正式的规范,定义了指令在可执行代码中的存储方式。

我怎样才能看到一个未知文件的文件类型?

使用 file 命令进行第一轮分析。该命令可能能够根据头部信息或魔术数据显示详细信息。

结论

ELF 文件用于执行或链接。根据主要目标,它包含所需的段或节。段由内核查看并映射到内存中(使用 mmap)。节由链接器查看,以创建可执行代码或共享对象。

ELF 文件类型非常灵活,为多种 CPU 类型、机器架构和操作系统提供支持。它也非常可扩展:每个文件的构建方式不同,取决于所需的部分。

头部是文件的一个重要部分,准确描述了 ELF 文件的内容。通过使用正确的工具,我们可以对文件的目的有一个基本的了解。从那里开始,我们可以进一步检查二进制文件。这可以通过确定它使用的函数或存储在文件中的字符串来完成。对于那些从事恶意软件研究或想要更好地了解进程行为(或不行为!)的人来说,这是一个很好的开始。

参考文章