ELF文件格式解析

前言:由于.KO文件是ELF格式的文件,所以这里先学习一下ELF文件格式

一、ELF文件格式

1.1 ELF文件介绍

ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西。

首先我们需要知道对象文件有哪些:

类型实例
可重定位的对象文件(Relocatable file).o;.a;.ko
可执行的对象文件(Executable file)vi、gdb、及我们用链接器生成的可执行文件、bash shell 程序
可被共享的对象文件(Shared object file).so
核心转储文件(Core Dump File)当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些其他信息转储到核心转储文件 core dump

想要知道一个对象文件属于以上类型的哪一种,我们可以使用file +对象文件名命令来查看,例如:

file sum.o sub.o test.o libsub.so test
sum.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
sub.o:     ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
test.o:    ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped
libsub.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), not stripped
test:      ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped

那对于 file 命令来说,它又能如何知道这些信息?答案是在ELF对象文件的最前面有一个ELF文件头,里面记载了所适用的处理器、对象文件类型等各种信息。
首先,ELF文件格式提供了两种视图,分别是链接视图和执行视图。
在这里插入图片描述
链接视图是以节(section)为单位,执行视图是以段(segment)为单位。链接视图就是在链接时用到的视图,而执行视图则是在执行时用到的视图。上图左侧的视角是从链接来看的,右侧的视角是执行来看的。总个文件可以分为四个部分:

- ELF header: 描述整个文件的组织。
- Program Header Table: 描述文件中的各种segments,用来告诉系统如何创建进程映像的。
- sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。从图中我们也可以看出,segments与sections是包含的关系,一个segment包含若干个section。
- Section Header Table: 包含了文件各个segction的属性信息,我们都将结合例子来解释。

在这里插入图片描述
程序头部表(Program Header Table),如果存在的话,告诉系统如何创建进程映像。
节区头部表(Section Header Table)包含了描述文件节区的信息,比如大小、偏移等。

到这里可能大家伙还是不明白节区跟段的区别与联系,这里在说明一下:
一个程序中最重要的部分是段和节,他们是真正的程序体,存储程序执行所需要的数据,程序中有很多段,常见的有代码段和数据段,段是由节组成的。多个节经过链接之后被合并成一个段。
段和节的信息用header来描述,程序头是program header,节头是section header。
程序中段的大小和数量不固定,节也是如此,因此需要一个专门的数据结构来描述他们,这个就是程序头表和节头表,他们用来存储多个程序头和节头,相当于数组的概念。

2.2 ELF文件详细内容解析

这里我们先分析一个.KO文件的内容,包括以下部分
① ELF Header
② section
③ 节区头部表
在这里插入图片描述
接下来我们具体分析每个结构体:

2.2.1 ELF Header

我们打开/usr/include/elf.h文件,开始处是一个ELF头部(ELF Header),用来描述整个文件的组织,这些信息独立于处理器,也独立于文件中的其余内容。

typedef struct
{
unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
Elf32_Half    e_type;                 /* Object file type */
Elf32_Half    e_machine;              /* Architecture */
Elf32_Word    e_version;              /* Object file version */
Elf32_Addr    e_entry;                /* Entry point virtual address */
Elf32_Off     e_phoff;                /* Program header table file offset */
Elf32_Off     e_shoff;                /* Section header table file offset */
Elf32_Word    e_flags;                /* Processor-specific flags */
Elf32_Half    e_ehsize;               /* ELF header size in bytes */
Elf32_Half    e_phentsize;            /* Program header table entry size */
Elf32_Half    e_phnum;                /* Program header table entry count */
Elf32_Half    e_shentsize;            /* Section header table entry size */
Elf32_Half    e_shnum;                /* Section header table entry count */
Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

我们来分析这个结构体:
① e_ident数组(16 bytes)
给出了ELF的一些标识信息
1) EI_MAG: 魔数(4 bytes): 标志此文件是一个 ELF 目标文件
1.1) e_ident[0]: 0x7f
1.2) e_ident[1]: ‘E’
1.3) e_ident[2]: ‘L’
1.4) e_ident[3]: ‘F’
2) EI_CLASS: 文件的类别(1 byte):
或者说是位宽
2.1) ELFCLASSNONE: 0: 非法类别
2.2) ELFCLASS32: 1: 32位目标
2.3) ELFCLASS64: 2: 64位目标
3) EI_DATA: 处理器特定数据的数据编码方式
3.1) ELFDATANONE: 0: 非法数据编码
3.2) ELFDATA2LSB: 1: 高位在前
3.3) ELFDATA2MSB: 2: 低位在前
4) EI_VERSION: ELF 头部的版本号码,此值必须是EV_CURRENT
5) EI_PAD: 标记e_ident 中未使用字节的开始,初始化为0

② e_type(2 bytes): 目标文件类型
1) ET_NONE: 0: 未知目标文件格式
2) ET_REL: 1: 可重定位文件
3) ET_EXEC: 2: 可执行文件
4) ET_DYN: 3: 共享目标文件
5) ET_CORE: 4: Core文件(转储格式0
6) ET_LOPROC: 0xff00: 特定处理器文件
7) ET_HIPROC: 0xffff: 特定处理器文件
ET_LOPROC和ET_HIPROC之间的取值用来标识与处理器相关的文件格式

③ e_machine(2 bytes): 文件的目标体系结构类型
1) EM_NONE: 0: 未指定
2) EM_M32: 1: AT&T WE 32100
3) EM_SPARC: 2: SPARC
4) EM_386: 3: Intel 80386
5) EM_68K: 4: Motorola 68000
6) EM_88K: 5: Motorola 88000
7) EM_860: 7: Intel 80860
8) EM_MIPS: 8: MIPS RS3000

④ e_version(4 bytes): 目标文件版本
1) EV_NONE: 0: 非法版本
2) EV_CURRENT: 1: 当前版本

⑤ e_entry(4 bytes): 程序入口的虚拟地址,如果目标文件没有程序入口,可以为0
⑥ e_phoff(4 bytes): 程序头部表格(Program Header Table)的偏移量(按字节计算),如果文件没有程序头部表格,可以为0
⑦ e_shoff(4 bytes): 节区头部表格(Section Header Table)的偏移量(按字节计算)。如果文件没有节区头部表格,可以为0
⑧ e_flags(4 bytes): 保存与文件相关的,特定于处理器的标志。标志名称采用EF_machine_flag的格式
⑨ e_ehsize(2 bytes): ELF头部的大小(以字节计算)
⑩ e_phentsize(2 bytes): 程序头部表格的表项大小(按字节计算)
11. e_phnum(2 bytes): 程序头部表格的表项数目,可以为0
12. e_shentsize(2 bytes): 节区头部表格的表项大小(按字节计算)
13. e_shnum(2 bytes): 节区头部表格的表项数目,可以为0
14. e_shstrndx(2 bytes): 节区头部表格中与节区名称字符串表相关的表项的索引。如果文件没有节区名称字符串表,此参数可以为SHN_UNDEF
这里我们展示读取到的头部信息:
在这里插入图片描述

2.2.2 节区

节区满足以下条件:

  1. 目标文件中的每个节区都有对应的节区头部描述它,反过来,有节区头部不意味着有节区
  2. 每个节区占用文件中一个连续字节区域(这个区域可能长度为0)
  3. 文件中的节区不能重叠,不允许一个字节存在于两个节区中的情况发生
  4. 目标文件中可能包含非活动空间(INACTIVE SPACE)。这些区域不属于任何头部和节区,其内容未指定

2.2.3 节区头部表

typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;
  1. sh_name(4 bytes): 节区名称,是"节区头部字符串表节区"(Section Header String Table Section)的索引。名字是一个NULL结尾的字符串。
    所谓"节区头部字符串表节区",就是一段连续的保存每个节区名字的ascii字符的地址空间

  2. sh_type(4 bytes): 为节区的内容和语义进行分类
    1) SHT_NULL: 0: 此值标志节区头部是非活动的,没有对应的节区。此节区头部中的其他成员取值无意义
    2) SHT_PROGBITS: 1: 此节区包含程序定义的信息,其格式和含义都由程序来解释
    3) SHT_SYMTAB: 2: 此节区包含一个符号表。目前目标文件对每种类型的节区都只能包含一个,不过这个限制将来可能发生变化,通常情况下,SHT_SYMTAB节区提供用于链接编辑(指ld而言)的符号,尽管也可用来实现动态
    链接。
    4) SHT_STRTAB: 3: 此节区包含字符串表。目标文件可能包含多个字符串表节区。
    5) SHT_RELA: 4: 此节区包含重定位表项,其中可能会有补齐内容(addend),例如32位目标文件中的Elf32_Rela类型。目标文件可能拥有多个重定位节区
    6) SHT_HASH: 5: 此节区包含符号哈希表。所有参与动态链接的目标都必须包含一个符号哈希表。目前,一个目标文件只能包含一个哈希表,不过此限制将来可能会解除。
    7) SHT_DYNAMIC: 6: 此节区包含动态链接的信息。目前一个目标文件中只能包含一个动态节区,将来可能会取消这一限制。
    8) SHT_NOTE: 7: 此节区包含以某种方式来标记文件的信息。
    9) SHT_NOBITS: 8: 这种类型的节区不占用文件中的空间,其他方面和SHT_PROGBITS相似。尽管此节区不包含任何字节,成员sh_offset中还是会包含概念性的文件偏移
    10) SHT_REL: 9: 此节区包含重定位表项,其中没有补齐(addends),例如32位目标文件中的Elf32_rel类型。目标文件中可以拥有多个重定位节区
    11) SHT_SHLIB: 10: 此节区被保留,不过其语义是未规定的。包含此类型节区的程序与ABI不兼容。
    12) SHT_DYNSYM: 11: 作为一个完整的符号表,它可能包含很多对动态链接而言不必要的符号。因此,目标文件也可以包含一个SHT_DYNSYM节区,其中保存动态链接符号的一个最小集合,以节省空间
    13) SHT_LOPROC(0x70000000)~SHT_HIPROC(0x7FFFFFFF): 这一段(包括两个边界),是保留给处理器专用语义的
    14) SHT_LOUSER(0X80000000): 此值给出保留给应用程序的索引下界
    15) SHT_HIUSER(0X8FFFFFFF): 此值给出保留给应用程序的索引上界

  3. sh_flags(4 bytes): sh_flags字段定义了一个节区中包含的内容是否可以修改、是否可以执行等信息。如果一个标志位被设置,则该位取值为1。未定义的各位都设置为0(这是一种bitmap位图表示法)
    1) SHF_WRITE: 0x1: 节区包含进程执行过程中将可写的数据
    2) SHF_ALLOC: 0x2: 此节区在进程执行过程中占用内存。某些控制节区并不出现于目标文件的内存映像中,对于那些节区,此位应设置为0
    3) SHF_EXECINSTR: 0x4: 节区包含可执行的机器指令
    4) SHF_MASKPROC: 0xF0000000: 所有包含于此掩码中的四位都用于处理器专用的语义

  4. sh_addr(4 bytes): 如果节区将出现在进程的内存映像中,此成员给出节区的第一个字节应处的位置。否则,此字段为0

  5. sh_offset(4 bytes): 此成员的取值给出节区的第一个字节与文件头之间的偏移。不过,SHT_NOBITS类型的节区不占用文件的空间,因此其sh_offset成员给出的是其概念性的偏移

  6. sh_size(4 bytes): 此成员给出节区的长度(字节数)。除非节区的类型是SHT_NOBITS,否则节区占用文件中的sh_size 字节。类型为SHT_NOBITS的节区长度可能非零,不过却不占用文件中的空间

  7. sh_link(4 bytes): 此成员给出节区头部表索引链接。其具体的解释依赖于节区类型
    根据节区类型的不同,sh_link和sh_info 的具体含义也有所不同
    sh_type     sh_link       sh_info
    SHT_DYNAMIC     此节区中条目所用到的字符串表格的节区头部索引 0
    SHT_HASH       此哈希表所适用的符号表的节区头部索引   0
    SHT_REL、SHT_RELA    相关符号表的节区头部索引      重定位所适用的节区的节区头部索引
    SHT_SYMTAB、SHT_DYNSYM 相关联的字符串表的节区头部索引   最后一个局部符号(绑定 STB_LOCAL)的符号表索引值加一
    其它       SHN_UNDEF        0

  8. sh_info(4 bytes): 此成员给出附加信息,其解释依赖于节区类型

  9. sh_addralign(4 bytes): 某些节区带有地址对齐约束。例如,如果一个节区保存一个doubleword,那么系统必须保证整个节区能够按双字对齐。sh_addr对sh_addralign取模,结果必须为0。目前仅允许取值为0和2的幂
    次数。数值0和1表示节区没有对齐约束

  10. sh_entsize(4 bytes): 某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。如果节区中并不包含固定长度表项的表格,此成员取值为0
    这里我们展示读取到的节区头部表:
    在这里插入图片描述

2.2.4 部分节区说明

1 .rel.xxxxx

对应xxxxx section的relocate表,用于符号重定位,比如说 .rel.text 就是 .text 的重定向表所在的节区

Relocation section '.rel.text' at offset 0xe52c contains 67 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000050  00000e02 R_ARM_ABS32       00000000   .rodata.str1.4
00000054  00005802 R_ARM_ABS32       00000000   memcpy
00000058  00006902 R_ARM_ABS32       00000000   printk
0000005c  00000e02 R_ARM_ABS32       00000000   .rodata.str1.4
00000120  00006c02 R_ARM_ABS32       00000000   _ctype
00000124  00007502 R_ARM_ABS32       00000000   strlen
000001c8  00007502 R_ARM_ABS32       00000000   strlen
000001cc  00006402 R_ARM_ABS32       00000000   match_int
000001d0  00006c02 R_ARM_ABS32       00000000   _ctype
000001d4  00007302 R_ARM_ABS32       00000000   match_hex
000001fc  0000521c R_ARM_CALL        00000128   parse_interger
0000023c  00006302 R_ARM_ABS32       00000000   kallsyms_lookup_name
00000290  0000741c R_ARM_CALL        00000060   str_trim
0000029c  0000521c R_ARM_CALL        00000128   parse_interger

2 .symtab 模块中所有的符号记录都在这里面

eadelf -s ssp.ko | more

Symbol table '.symtab' contains 53529 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 SECTION LOCAL  DEFAULT    1 
     2: 00000000     0 SECTION LOCAL  DEFAULT    3 
     3: 00000000     0 SECTION LOCAL  DEFAULT    5 
     4: 00000000     0 SECTION LOCAL  DEFAULT    7 
     5: 00000000     0 SECTION LOCAL  DEFAULT    8 
     6: 00000000     0 SECTION LOCAL  DEFAULT   10 
     7: 00000000     0 SECTION LOCAL  DEFAULT   13 
     8: 00000000     0 SECTION LOCAL  DEFAULT   23 
     9: 00000000     0 SECTION LOCAL  DEFAULT   24 
    10: 00000000     0 SECTION LOCAL  DEFAULT   31 
    11: 00000000     0 NOTYPE  LOCAL  DEFAULT    1 $a
    12: 00000088     0 NOTYPE  LOCAL  DEFAULT    1 $d
    13: 00000000     0 NOTYPE  LOCAL  DEFAULT   28 $d
    14: 00000098     0 NOTYPE  LOCAL  DEFAULT    1 $a
    15: 00000150     0 NOTYPE  LOCAL  DEFAULT    1 $d
    16: 00000164     0 NOTYPE  LOCAL  DEFAULT    1 $a

3…shstrtab

节区名称字符串表,所有的节区名称字符串都放在这里

readelf -p 35 ssp.ko 

String dump of section '.shstrtab':
  [     1]  .symtab
  [     9]  .strtab
  [    11]  .shstrtab
  [    1b]  .rel.text
  [    25]  .rel.text.unlikely
  [    38]  .rel.init.text
  [    47]  .rel.rodata
  [    53]  .rodata.str1.4
  [    62]  .rel.pv_table
  [    70]  .ARM.extab.text.unlikely
  [    89]  .rel.ARM.exidx.text.unlikely
  [    a6]  .ARM.extab.init.text
  [    bb]  .rel.ARM.exidx.init.text
  [    d4]  .ARM.extab.exit.text
  [    e9]  .rel.ARM.exidx.exit.text
  [   102]  .modinfo
  [   10b]  .ARM.extab
  [   116]  .rel.data
  [   120]  .rel.gnu.linkonce.this_module
  [   13e]  .rel.ARM.exidx
  [   14d]  .note.gnu.build-id
  [   160]  .bss
  [   165]  .comment
  [   16e]  .note.GNU-stack
  [   17e]  .ARM.attributes

4 .strtab

符号名称字符串表,这里记录了所有的符号的名称字符串,其格式即字符串表格式,和节区头部名称字符串表格式相同。

5 .gnu.linkonce.this_module

struct module 实例所在的节区

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值