ELF 是 可执行和可链接格式(Executable and Linkable Format)的缩写,它定义了二进制文件、库和核心文件的结构。正式的规范允许操作系统正确解释其底层机器指令。ELF 文件通常是编译器或链接器的输出,并且是一种二进制格式。有了合适的工具,这样的文件就可以被分析和更好地理解。
在深入更技术性的细节之前,解释一下为什么理解 ELF 格式是有用的。首先,它有助于学习我们操作系统的内部工作方式。当出现问题时,我们可能能更好地理解发生了什么(或为什么)。然后是能够研究 ELF 文件的价值,特别是在安全漏洞或发现可疑文件之后。最后但并非最不重要的是,在开发过程中为了更好地理解。即使大家使用像 Golang 这样的高级语言编程,我们仍然可能从了解幕后发生的事情中受益。
那么,为什么要学习更多关于 ELF 的知识呢?
- 对操作系统工作方式的通用理解
- 软件开发
- 数字取证和事件响应(DFIR)
- 恶意软件研究(二进制分析)
无论我们运行什么操作系统,它都需要将通用功能转换为 CPU 的语言,也称为机器代码。一个函数可能是一些基本的事情,比如在磁盘上打开一个文件或在屏幕上显示一些东西。我们不是直接与 CPU 对话,而是使用编程语言,使用内部函数。然后编译器将这些函数翻译成目标代码。这个目标代码随后被链接器工具链接成一个完整的程序。结果是一个二进制文件,然后可以在特定的平台和 CPU 类型上执行。
不要在生产系统上运行相关命令。最好在测试机器上复制一个现有的二进制文件并使用相关命令。此外,我们提供了一个小型的 C 程序,我们可以编译它来进行验证。
相关工具安装:
sudo yum install binutils
sudo yum install pax-utils
sudo yum install prelink一个常见的误解是 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 文件由以下部分组成:
- ELF 头部
- 文件数据
使用 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: 28ELF 二进制文件的细节。
如上述命令输出所示,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”。
每个操作系统在公共功能上有很大一部分重叠。此外,它们都有特定的功能,或者至少在它们之间有细微的差别。正确的设置定义是通过 应用程序二进制接口 (ABI)。这样,操作系统和应用程序都知道该期待什么,函数被正确转发。这两个字段描述了使用了什么 ABI 以及相关的版本。在这种情况下,值是 00,这意味着没有使用特定的扩展。输出显示为 System V。
如果需要,可以指定 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 .gotELF 二进制文件中的程序头部概览。
我们在示例中看到有 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/psexecstack -q /usr/bin/ps
- /usr/bin/ps查看程序头部的命令。
dumpelf(pax-utils)elfls -S /usr/bin/pseu-readelf –program-headers /usr/bin/ps
节头部定义了文件中的所有节。正如所说,这个“视图”用于链接和重定位。
节可以在 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)包含可执行代码。它将被打包到一个具有读写权限的段中。它只加载一次,因为内容不会改变。这可以通过 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初始化数据,具有读写访问权限
初始化数据,仅具有读取访问权限(=A)。
未初始化数据,具有读写访问权限(=WA)
[24] .data PROGBITS 0000000000617318 00017318
0000000000000108 0000000000000000 WA 0 0 8
[25] .bss NOBITS 0000000000617420 00017420
0000000000021168 0000000000000000 WA 0 0 32查看节和头部的命令。
dumpelfelfls -p /usr/bin/pseu-readelf –section-headers /usr/bin/psreadelf -S /usr/bin/psobjdump -h /usr/bin/ps
一些节可以被分组,因为它们形成一个整体,或者换句话说是一个依赖关系。较新的链接器支持这个功能。尽管如此,这并不常见于发现这种情况:
readelf -g /usr/bin/ps
There are no section groups in this file.虽然这看起来可能不太有趣,但它清楚地表明了研究可用的 ELF 工具包的好处,用于分析。因此,在本文的末尾包含了工具及其主要目标的概述。
在处理 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 工具包由 Sergi Alvarez 创建。版本中的“2”指的是与第一版本相比的完整重写。它现在被许多逆向工程师用来了解二进制文件的工作原理。它可以用来解剖固件、恶意软件和任何其他看起来是可执行格式的东西。
大多数 Linux 系统已经安装了 binutils 包。其他包可能有助于显示更多细节。拥有正确的工具包可能会简化大家的工作,特别是当我们进行分析或更多地了解 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 包是一个很好的开始,因为它包含了大多数用于执行分析的实用程序。
/usr/bin/ebfc– Brainfuck 编程语言的编译器/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 二进制文件时了解更多非常有帮助。
/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/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 是 Application Binary Interface 的缩写,它指定了操作系统和一段可执行代码之间的低级接口。
ELF 是 Executable and Linkable Format 的缩写。它是一个正式的规范,定义了指令在可执行代码中的存储方式。
使用 file 命令进行第一轮分析。该命令可能能够根据头部信息或魔术数据显示详细信息。
ELF 文件用于执行或链接。根据主要目标,它包含所需的段或节。段由内核查看并映射到内存中(使用 mmap)。节由链接器查看,以创建可执行代码或共享对象。
ELF 文件类型非常灵活,为多种 CPU 类型、机器架构和操作系统提供支持。它也非常可扩展:每个文件的构建方式不同,取决于所需的部分。
头部是文件的一个重要部分,准确描述了 ELF 文件的内容。通过使用正确的工具,我们可以对文件的目的有一个基本的了解。从那里开始,我们可以进一步检查二进制文件。这可以通过确定它使用的函数或存储在文件中的字符串来完成。对于那些从事恶意软件研究或想要更好地了解进程行为(或不行为!)的人来说,这是一个很好的开始。