为什么你需要了解 ELF 文件?
- 基础知识
- 开发人员
- 蓝队成员
- 事件响应
- 数字取证
- 恶意软件研究员
- 红队成员
- 渗透测试员
从源代码到二进制代码
- 源代码
- 编译
- 汇编代码
- 汇编器
- 目标代码
- 链接器
- 将目标代码与引用的库链接
- 二进制代码
Struct of an ELF file
-
ELF 头部
yanzzp@Yanzzp:~$ readelf -h /bin/ls
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: DYN (Position-Independent Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x6d30
Start of program headers: 64 (bytes into file)
Start of section headers: 140328 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 13
Size of section headers: 64 (bytes)
Number of section headers: 31
Section header string table index: 30
字段 | 含义 |
---|---|
Class | ELF 文件的类(ELF64 表示 64 位格式) |
Data | 数据编码方式(这里为小端格式的二进制补码) |
Version | ELF 文件格式的版本(当前版本为 1) |
OS/ABI | 操作系统/应用程序二进制接口(ABI),此处为 UNIX 的 System V 标准 |
ABI Version | ABI 版本号(通常为 0) |
Type | 文件类型(DYN 表示位置无关可执行文件) |
Machine | 指定适用的处理器架构(此处为 AMD X86-64) |
Version | 文件版本号(通常为 0x1) |
Entry point address | 程序入口地址(用于执行程序的起始地址) |
Start of program headers | 程序头表的起始位置(文件中的字节偏移量) |
Start of section headers | 节头表的起始位置(文件中的字节偏移量) |
Flags | 特定于处理器的标志(通常为 0) |
Size of this header | ELF 头的大小(以字节为单位) |
Size of program headers | 每个程序头的大小(以字节为单位) |
Number of program headers | 程序头的数量 |
Size of section headers | 每个节头的大小(以字节为单位) |
Number of section headers | 节头的数量 |
Section header string table index | 节头字符串表的索引,用于定位节名称 |
yanzzp@Yanzzp:~$ hexdump -C -n 64 /bin/ls
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 03 00 3e 00 01 00 00 00 30 6d 00 00 00 00 00 00 |..>.....0m......|
00000020 40 00 00 00 00 00 00 00 28 24 02 00 00 00 00 00 |@.......($......|
00000030 00 00 00 00 40 00 38 00 0d 00 40 00 1f 00 1e 00 |....@.8...@.....|
00000040
一些有趣的内容,为什么linux中通常是小端的表示方式较多呢
Data: 2's complement, little endian
个人认为是为了向下兼容,例如说64位兼容32位系统,data前部的表示方式是一样的
-
文件数据(专注于二进制)
- 程序头或段
- 节头或节
- 数据
作用:
- 指定内存映射:程序头定义了各段在内存中的布局,包括起始地址、大小和内存访问权限。这帮助加载器将 ELF 文件的特定部分映射到合适的内存区域。
- 加载代码和数据:程序头中的段包含代码段(.text)、数据段(.data)、未初始化数据段(.bss)等,分别对应可执行代码、已初始化的数据和未初始化的数据。加载器会将这些段加载到内存的特定位置以便程序运行。
- 权限控制:每个段在程序头中定义了不同的访问权限,如可执行(execute)、可写(write)、只读(read)等,这些权限用于确保程序的安全性。例如,代码段通常是只读和可执行的,而数据段是可读和可写的。
- 动态链接:如果 ELF 文件是动态链接的(如共享库),程序头还会包含动态链接信息(Dynamic Segment),如依赖的库路径、符号表等,加载器在执行时会根据这些信息完成符号解析。
- 以及一些辅助信息:一些段用于提供运行时的辅助信息,例如
.interp
段,指定了解释器路径(如ld-linux.so
),动态链接器会根据该路径加载解释器来执行文件。
yanzzp@Yanzzp:~$ readelf -l /bin/ls Elf file type is DYN (Position-Independent Executable file) Entry point 0x6d30 There are 13 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align PHDR 0x0000000000000040 0x0000000000000040 0x0000000000000040 0x00000000000002d8 0x00000000000002d8 R 0x8 INTERP 0x0000000000000318 0x0000000000000318 0x0000000000000318 0x000000000000001c 0x000000000000001c R 0x1 [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] LOAD 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x00000000000036f8 0x00000000000036f8 R 0x1000 LOAD 0x0000000000004000 0x0000000000004000 0x0000000000004000 0x0000000000014db1 0x0000000000014db1 R E 0x1000 LOAD 0x0000000000019000 0x0000000000019000 0x0000000000019000 0x00000000000071b8 0x00000000000071b8 R 0x1000 LOAD 0x0000000000020f30 0x0000000000021f30 0x0000000000021f30 0x0000000000001348 0x00000000000025e8 RW 0x1000 DYNAMIC 0x0000000000021a38 0x0000000000022a38 0x0000000000022a38 0x0000000000000200 0x0000000000000200 RW 0x8 NOTE 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8 NOTE 0x0000000000000368 0x0000000000000368 0x0000000000000368 0x0000000000000044 0x0000000000000044 R 0x4 GNU_PROPERTY 0x0000000000000338 0x0000000000000338 0x0000000000000338 0x0000000000000030 0x0000000000000030 R 0x8 GNU_EH_FRAME 0x000000000001e170 0x000000000001e170 0x000000000001e170 0x00000000000005ec 0x00000000000005ec R 0x4 GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 0x0000000000000000 RW 0x10 GNU_RELRO 0x0000000000020f30 0x0000000000021f30 0x0000000000021f30 0x00000000000010d0 0x00000000000010d0 R 0x1 Section to Segment mapping: Segment Sections... 00 01 .interp 02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt 03 .init .plt .plt.got .plt.sec .text .fini 04 .rodata .eh_frame_hdr .eh_frame 05 .init_array .fini_array .data.rel.ro .dynamic .got .data .bss 06 .dynamic 07 .note.gnu.property 08 .note.gnu.build-id .note.ABI-tag 09 .note.gnu.property 10 .eh_frame_hdr 11 12 .init_array .fini_array .data.rel.ro .dynamic .got
当我们执行时会发生什么?
- 头部
- 程序头
- 加载段
- a. 分配空间
- b. 将段数据复制到分配的空间
- 跳转到可执行入口点
静态与动态链接
静态
- 更大
- 可移植
- 包含引用的库
动态
- 更小
- 轻量
- 需要外部组件