ELF详解

Wednesday, October 26, 2022
本文共5855字
12分钟阅读时长
pwn , ELF

⚠️本文是作者P3troL1er原创,首发于https://peterliuzhi.top/principle/elf%E8%AF%A6%E8%A7%A3/。商业转载请联系作者获得授权,非商业转载请注明出处!

Myths which are believed in tend to become true. — George Orwell

什么是ELF

ELF(Executable and Linkable Format)是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件的文件格式,目前已经成为Unix和类Unix操作系统的标准二进制格式,目前常见的Linux、Android可执行文件、共享库(.so)、目标文件( .o)以及Core 文件均为此格式

使用readelf命令读取elf文件

readelf -S  查询节头表
        -l  查询程序头表
        -s  查询符号表
        -e  查询ELF文件头数据
        -r  查询重定位入口
        -d  查询动态段

ELF文件类型

使用ELF格式的文件可以被标记为以下五种格式:

  • 未知类型ET_NONE
  • 重定位文件ET_REL,通常是还没有经过链接的一段独立代码,也就是.o文件
  • 可执行文件ET_EXEC,这个就不用说了~
  • 共享目标文件ET_DYN,就是动态链接库
  • 核心文件ET_CORE,程序崩溃或产生SIGSEGV信号的时候会在此文件中记录整个进程的镜像信息,从而被GDB读取复盘

我们可以通过readelf -h命令来查看原始的ELF文件头:

$ readelf -h babyrop
ELF 头:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  类别:                              ELF64
  数据:                              2 补码,小端序 (little endian)
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI 版本:                          0
  类型:                              EXEC (可执行文件)
  系统架构:                          Advanced Micro Devices X86-64
  版本:                              0x1
  入口点地址:               0x4004e0
  程序头起点:          64 (bytes into file)
  Start of section headers:          6768 (bytes into file)
  标志:             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:         31
  Section header string table index: 28

readelf是怎么找到ELF头在哪里呢?答案就是ELF头从二进制文件的0号地址,也就是文件的开头开始,就好像一篇论文在一开始会对整个论文做概述一样,ELF文件头是对剩余部分的一个映射

ELF文件头告诉了我们:

  • 这个文件是啥类型的文件
  • 这个文件的结构
  • 程序开始执行的入口地址
  • 其他ELF头(节头、程序头)的地址(偏移量)

ELF程序头(描述段的头)

在原始的ELF头中我们记录了ELF程序头的地址(e_phoff),这样我们就能得到程序头表

ELF程序头是对二进制文件中段的描述,是程序必需的一部分

我们知道,一个程序想要运行,首先要把它装载到内存上,而这个ELF程序头就相当于一本程序装载的说明书,告诉你每个段是怎样的。

我们可以通过readelf -l来查询程序头表:

$ readelf -l babyrop

Elf 文件类型为 EXEC (可执行文件)
Entry point 0x4004e0
There are 9 program headers, starting at offset 64

程序头:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    0x8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      0x1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x0000000000000814 0x0000000000000814  R E    0x200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x0000000000000240 0x0000000000000248  RW     0x200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     0x8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      0x4
  GNU_EH_FRAME   0x00000000000006e8 0x00000000004006e8 0x00000000004006e8
                 0x0000000000000034 0x0000000000000034  R      0x4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     0x10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      0x1

 Section to Segment mapping:
  段节...
   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 .plt.got .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程序头有一些常见的类型,下面我们来介绍一下它们

PT_LOAD

这类程序头描述的是可以被装载的段,根据这个头,我们将需要的段装载/映射到内存中

一个动态链接的ELF可执行文件通常包括:

  • text段:存放程序代码(通常可读可执行PF_R|PF_X)
  • data段:全局变量和动态链接信息(通常可读可写PF_R|PF_W)

我们可以更改程序头中p_flags处增加PF_W标记来修改text段权限,从而扭曲原本的代码

PT_DYNAMIC(动态链接可执行文件特有)

它包括了:

  • 动态链接的库列表
    • DT_NEEDED:共享库名的字符串表的偏移量
  • GOT表的地址
    • DT_PLTGOT:全局偏移表(GOT)的地址
  • 重定位条目的相关信息
    • DT_SYMTAB:动态符号表的地址,对应.dynsym节
    • DT_HASH:符号散列表的地址,对应.hash节
    • DT_STRTAB:符号字符串表的地址,对应.dynstr节

PT_NOTE

特定供应商/系统相关的附加信息

实际上这一段只保存了操作系统的规范信息,在运行的时候是不需要它的

还有一种note段的攻击,但作者实在是没找到相关资料,如果各位读者有相关资料可以参考,烦请在评论区给出链接,感谢您的付出!

PT_INTERP

对程序解释器位置的描述,如,/lib/linux-ld.so.2一般指动态连接器的位置,也即程序解释器的位置(.so.2文件不是.so文件)

PT_PHDR

保存了程序头表本身的位置和大小,phdr为program header的缩写

ELF节头(section header)

我们可以使用readelf -S命令查看节头表:

$ readelf -S babyrop
There are 31 section headers, starting at offset 0x1a70:

节头:
  [] 名称              类型             地址              偏移量
       大小              全体大小          旗标   链接   信息   对齐
  [ 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.bu[...] NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000090  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400348  00000348
       000000000000005f  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           00000000004003a8  000003a8
       000000000000000c  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          00000000004003b8  000003b8
       0000000000000030  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             00000000004003e8  000003e8
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400400  00000400
       0000000000000060  0000000000000018  AI       5    24     8
  [11] .init             PROGBITS         0000000000400460  00000460
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         0000000000400480  00000480
       0000000000000050  0000000000000010  AX       0     0     16
  [13] .plt.got          PROGBITS         00000000004004d0  000004d0
       0000000000000008  0000000000000000  AX       0     0     8
  [14] .text             PROGBITS         00000000004004e0  000004e0
       00000000000001b2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         0000000000400694  00000694
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004006a0  000006a0
       0000000000000047  0000000000000000   A       0     0     8
  [17] .eh_frame_hdr     PROGBITS         00000000004006e8  000006e8
       0000000000000034  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400720  00000720
       00000000000000f4  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000038  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601038  00001038
       0000000000000018  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601050  00001050
       0000000000000008  0000000000000000  WA       0     0     1
  [27] .comment          PROGBITS         0000000000000000  00001050
       0000000000000035  0000000000000001  MS       0     0     1
  [28] .shstrtab         STRTAB           0000000000000000  00001964
       000000000000010c  0000000000000000           0     0     1
  [29] .symtab           SYMTAB           0000000000000000  00001088
       0000000000000690  0000000000000018          30    47     8
  [30] .strtab           STRTAB           0000000000000000  00001718
       000000000000024c  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),
  D (mbind), l (large), p (processor specific)

这里面我们可以注意一下每个节的旗标(flag),如果被标记为A,那么说明该节会在程序运行时分配并装载进内存。有一些节在运行时不是必需的,就不会被装载。比如.symtab节,有时候为了节省空间会把它删掉

段和节的区别

  • 段是程序执行的必要组成部分
  • 节是段中代码或数据的划分

段是房子的房间,节是房间中的屏风隔断

而所谓节头表就是对这些节的位置和大小的描述(主要用于链接和调试,没有它程序可以正常运行)

节头

如果程序中缺少了节头,并不意味着节消失了;就好像电话簿上某人的号码消失了,并不代表某人的号码不存在了。我们只是不能再通过节头引用节了而已 因此,我们可以人为地删除节头,程序仍然可以正常运行 但是如果没有节头,gdb和objdump这种工具就没用了

.text节

保存了程序代码指令,一般存在text段中

.rodata节

rodata为read-only data的缩写,意为只读数据,这个节因为数据只读,因此不存在data段中,而是在text段中

.data节

.data节保存了初始化的全局变量等数据,存在data段中

.bss节

202209161425002022-09-16-14-25-01

202209161433152022-09-16-14-33-15

.bss节中保存了

  • 未初始化的全局变量和静态变量
  • 在代码中没有明确初始化的全局变量和静态变量

它存在data段中,一般来说它会被初始化为0,但是在运行期间它的值是可能改变的

它为什么被称为bss呢?请看下图:

202209161427262022-09-16-14-27-27

bss原本是指 block storage start 块状存储起点(渣翻请勿在意)(或者block started by symbol),但是为了和.data节区分,可以记作 better save space 更优保存空间

可以参考这篇博文

.plt节

plt表,意为procedure linkage table 过程链接表,包含了动态链接器调用从共享库导入的函数所必需的相关代码,保存在text段中

.got与.got.plt节

got意为Global Offsets Table,保存了全局偏移表,保存在text段中,和.plt节一起提供了对导入的共享库函数的访问入口,由动态链接器在运行时进行修改。

.got和.plt的关系类似于:

GOT&PLT.drawio2022-09-16-14-57-36

.got中存储的是一块空间的地址,这块空间中一开始保存了一个offset用于与.plt中的地址进行计算得到真实地址,然后就会把这个真实地址存入.got指向的那块空间之中

Got.plt is actually smaller subset of the .got section. Think of pointing to the tail end of an array of slots. Conceptually it sort of looks like this

int[10] got; int* gotplt = &got[5];

而.got.plt节是.got的一个子集,

Got section basically can contain addresses of Global variables and functions. All the global variables are in the first couple of slots and suffix is all pointers to functions. gotplt is the first slot .got that contains only the addresses of function.

.got表中包含了全局变量和函数的地址,但.got.plt中只有函数

Eventually after function addresses are resolved via means of plt. The resolved address goes into .gotplt which btw is inside .got as I mentioned earlier.

最终由于.plt表的存在,运行的时候.got.plt中的地址会被转化,而这个.got.plt仍然在.got中

更多请查看此问题

为什么有了.plt还要有.got呢?

因为单纯由plt表没法找到函数的真实地址,我们需要通过got表才能真正地调用函数。

既然如此,为什么不一开始就保存函数的真实地址呢?

因为动态链接器采用默认的延迟链接方式时,不会在第一次调用时就对地址进行解析,这样可以提高装载时的性能

got表的结构

got.drawio2022-09-16-17-46-18

.ctors和.dtors节

  • ctors -> Constructor 构造器,是在main函数之前需要执行的函数
  • dtors -> Destructor 析构器,是在main函数之后需要执行的函数

它们保存了构造函数和析构函数的指针

ELF符号

符号是对某些类型的数据或者代码的符号引用,实际上就是一个结构体,可以用于链接、重定位、反汇编和调试

typedef struct elf64_sym {

  Elf64_Word st_name;          
  /* 
  符号名称,字符串表中的索引
  STT_OBJECT  表示符号关联到一个数据对象,如变量、数组或指针;
  STT_FUNC    表示符号关联到一个函数;
  STT_SECTION 表示符号关联到节
  STT_NOTYPE  表示符号类型未指定,用于未定义引用
  */
  unsigned char st_info;         
  /*
  类型和绑定属性:
  STB_LOCAL   本地符号在目标文件之外时不可见的
  STB_GLOBAL  对于所有要合并的目标文件都可见
  STB_WEAK    类似于STB_GLOBAL,不过优先级较弱,可能会被为标记为STB_WEAK的同名符号覆盖
  */
  unsigned char st_other;      // 语义未定义,0
  Elf64_Half st_shndx;           // 相关节的索引,符号将绑定到该节,此外SHN_ABS指定符号是绝对值,不因重定位而改变,SHN_UNDEF标识未定义符号。
  Elf64_Addr st_value;           // 符号的值
  Elf64_Xword st_size;          // 符号的长度,如一个指针的长度或struct对象中包含的字节数。
}Elf64_Sym;

我们可以使用readelf -s命令查看程序符号表:

$ readelf -s babyrop

Symbol table '.dynsym' contains 6 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND [...]@GLIBC_2.2.5 (2)
     4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     5: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __[...]@GLIBC_2.7 (3)

Symbol table '.symtab' contains 70 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1 .interp
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2 .note.ABI-tag
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3 .note.gnu.build-id
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4 .gnu.hash
     5: 00000000004002b8     0 SECTION LOCAL  DEFAULT    5 .dynsym
     6: 0000000000400348     0 SECTION LOCAL  DEFAULT    6 .dynstr
     7: 00000000004003a8     0 SECTION LOCAL  DEFAULT    7 .gnu.version
     8: 00000000004003b8     0 SECTION LOCAL  DEFAULT    8 .gnu.version_r
     9: 00000000004003e8     0 SECTION LOCAL  DEFAULT    9 .rela.dyn
    10: 0000000000400400     0 SECTION LOCAL  DEFAULT   10 .rela.plt
    11: 0000000000400460     0 SECTION LOCAL  DEFAULT   11 .init
    12: 0000000000400480     0 SECTION LOCAL  DEFAULT   12 .plt
    13: 00000000004004d0     0 SECTION LOCAL  DEFAULT   13 .plt.got
    14: 00000000004004e0     0 SECTION LOCAL  DEFAULT   14 .text
    15: 0000000000400694     0 SECTION LOCAL  DEFAULT   15 .fini
    16: 00000000004006a0     0 SECTION LOCAL  DEFAULT   16 .rodata
    17: 00000000004006e8     0 SECTION LOCAL  DEFAULT   17 .eh_frame_hdr
    18: 0000000000400720     0 SECTION LOCAL  DEFAULT   18 .eh_frame
    19: 0000000000600e10     0 SECTION LOCAL  DEFAULT   19 .init_array
    20: 0000000000600e18     0 SECTION LOCAL  DEFAULT   20 .fini_array
    21: 0000000000600e20     0 SECTION LOCAL  DEFAULT   21 .jcr
    22: 0000000000600e28     0 SECTION LOCAL  DEFAULT   22 .dynamic
    23: 0000000000600ff8     0 SECTION LOCAL  DEFAULT   23 .got
    24: 0000000000601000     0 SECTION LOCAL  DEFAULT   24 .got.plt
    25: 0000000000601038     0 SECTION LOCAL  DEFAULT   25 .data
    26: 0000000000601050     0 SECTION LOCAL  DEFAULT   26 .bss
    27: 0000000000000000     0 SECTION LOCAL  DEFAULT   27 .comment
    28: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    29: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   21 __JCR_LIST__
    30: 0000000000400510     0 FUNC    LOCAL  DEFAULT   14 deregister_tm_clones
    31: 0000000000400550     0 FUNC    LOCAL  DEFAULT   14 register_tm_clones
    32: 0000000000400590     0 FUNC    LOCAL  DEFAULT   14 __do_global_dtors_aux
    33: 0000000000601050     1 OBJECT  LOCAL  DEFAULT   26 completed.7594
    34: 0000000000600e18     0 OBJECT  LOCAL  DEFAULT   20 __do_global_dtor[...]
    35: 00000000004005b0     0 FUNC    LOCAL  DEFAULT   14 frame_dummy
    36: 0000000000600e10     0 OBJECT  LOCAL  DEFAULT   19 __frame_dummy_in[...]
    37: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS babyrop.c
    38: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    39: 0000000000400810     0 OBJECT  LOCAL  DEFAULT   18 __FRAME_END__
    40: 0000000000600e20     0 OBJECT  LOCAL  DEFAULT   21 __JCR_END__
    41: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS
    42: 0000000000600e18     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_end
    43: 0000000000600e28     0 OBJECT  LOCAL  DEFAULT   22 _DYNAMIC
    44: 0000000000600e10     0 NOTYPE  LOCAL  DEFAULT   19 __init_array_start
    45: 00000000004006e8     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR
    46: 0000000000601000     0 OBJECT  LOCAL  DEFAULT   24 _GLOBAL_OFFSET_TABLE_
    47: 0000000000400690     2 FUNC    GLOBAL DEFAULT   14 __libc_csu_fini
    48: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]
    49: 0000000000601038     0 NOTYPE  WEAK   DEFAULT   25 data_start
    50: 0000000000601050     0 NOTYPE  GLOBAL DEFAULT   25 _edata
    51: 0000000000400694     0 FUNC    GLOBAL DEFAULT   15 _fini
    52: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND system@@GLIBC_2.2.5
    53: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND printf@@GLIBC_2.2.5
    54: 0000000000601048     8 OBJECT  GLOBAL DEFAULT   25 binsh
    55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]
    56: 0000000000601038     0 NOTYPE  GLOBAL DEFAULT   25 __data_start
    57: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    58: 0000000000601040     0 OBJECT  GLOBAL HIDDEN    25 __dso_handle
    59: 00000000004006a0     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used
    60: 0000000000400620   101 FUNC    GLOBAL DEFAULT   14 __libc_csu_init
    61: 0000000000601058     0 NOTYPE  GLOBAL DEFAULT   26 _end
    62: 00000000004004e0    42 FUNC    GLOBAL DEFAULT   14 _start
    63: 0000000000601050     0 NOTYPE  GLOBAL DEFAULT   26 __bss_start
    64: 00000000004005d6    69 FUNC    GLOBAL DEFAULT   14 main
    65: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
    66: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __isoc99_scanf@@[...]
    67: 0000000000601050     0 OBJECT  GLOBAL HIDDEN    25 __TMC_END__
    68: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]
    69: 0000000000400460     0 FUNC    GLOBAL DEFAULT   11 _init

我们可以看到.symtab节中保存了节、数据对象、包括main在内的函数等,这些对我们的调试、反汇编都是非常重要的。

当我们使用objdump时,每一段代码前面会有一个标注,如下:

$ objdump -d babyrop

babyrop:     文件格式 elf64-x86-64


Disassembly of section .init:

0000000000400460 <_init>:
  400460:       48 83 ec 08             sub    $0x8,%rsp
  400464:       48 8b 05 8d 0b 20 00    mov    0x200b8d(%rip),%rax        # 600ff8 <__gmon_start__>
  40046b:       48 85 c0                test   %rax,%rax
  40046e:       74 05                   je     400475 <_init+0x15>
  400470:       e8 5b 00 00 00          call   4004d0 <__gmon_start__@plt>
  400475:       48 83 c4 08             add    $0x8,%rsp
  400479:       c3                      ret
...

过程入口是每个函数的起点,因此通过检测过程序言(procedure prologue)可以帮助我们找到一个新韩淑的起始位置

如果段是公司中不同的部门,节就是部门中的各小组,而符号表就是一张登记表,把各小组及其成员都登记在表上,如果没有符号表,老板就不知道谁是谁了

.symtab节

保存了符号信息,用于调试和链接,保存了可执行文件的本地符号(全局变量、本地函数等)

.symtab中保存了所有的符号,有很多都是很重要的,如果把他们去掉,虽然严格意义上不会妨碍程序的运行,但是对人来说就难读了很多

.strtab节

保存了符号字符串表,该内容会被.symtab结构引用

.dynsym节

dynsym,dynamic symbols,保存了从共享库导入的动态符号信息,保存在text段中

它是 .symtab的子集,它只保存动态/全局符号

与symtab不同的是,动态链接的二进制文件运行时不能缺少它;symtab可以删掉但它不能。但是如果是静态链接程序,用strip命令清理后就不会有符号表(dynsym也变成非必需的了)

.dynstr节

保存了动态符号字符串表,就是动态符号的名字

.rel.*节

rel->reacation,重定位节,保存了重定位相关的信息,这些信息描述了如何在链接或运行过程中对部分内容或进程镜像进行补充或修改

.hash节

保存了一个用于查找符号的哈希表(散列表)

.shstrtab节

保存了节头字符串表,也就是每个节头的名字

ELF重定位 Relocation

重定位就是将符号定义和符号引用进行连接的过程。

在重定位文件中,重定位记录保存了如何对给定的符号对应代码进行补充的相关信息,实际上是一种给二进制文件打补丁的机制。

例如,1.o的代码中引用了2.o中的代码,当链接的时候就会对1.o中的重定位记录进行分析,目标文件中的代码会被重定位到可执行文件的段中一个给定的地址,符号引用被解析成了符号定义。

对于共享库的函数,由于延迟链接,只有当一个函数真正被调用时,才会进行GOT重定位

基于二进制修补的重定位代码注入

既然重定位是将符号引用实现的过程,那么我们可以更改可执行文件的符号表,让其指向我们给定的目标文件让其重定位,这样我们的代码就变成了可执行文件的一部分


更多的资料敬请阅读Linux ELF 手册


上一页 汇编与寄存器