为什么你需要了解 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
    

当我们执行时会发生什么?

  1. 头部
  2. 程序头
  3. 加载段
    • a. 分配空间
    • b. 将段数据复制到分配的空间
  4. 跳转到可执行入口点

静态与动态链接

静态

  • 更大
  • 可移植
  • 包含引用的库

动态

  • 更小
  • 轻量
  • 需要外部组件