ELF 文件格式

😊

1 目标文件

1.1 目标文件

ELF 是 Executable Linkable Format 的简称,是为 Executable files(可执行文件), object files(目标文件), shared libraries(动态链接库), 和 core dumps(内核转储)准备的标准文件格式。 Linux 和很多类 Unix 操作系统都使用这个格式。

在 ELF 规范中,ELF 文件统称为目标文件,主要分为以下几类:

  1. 共享目标文件(shared object file)。即动态链接库文件,以 .so 为文件扩展名(PE 动态链接库文件是 dll)。
  2. 可重定位文件(relocatable file)。通过编译和汇编,以 .o 扩展名结尾(还未链接为可执行文件,即可执行文件的前身)。可以和其他目标文件链接后生成可执行文件或动态链接库。
  3. 可执行文件(executable file)。已经经过链接的,可直接执行的文件(Linux 下可不指定文件扩展名,Windows 下为 .exe/.dll/.sys 等)。
  4. 核心转储文件(Core dump file)。当进程意外终止时,系统可以将该进程的地址空间的内容及终止时的一些信息转储到核心转储文件。

Linux 下文件编译、汇编、链接过程如下图:

1

1.2 ELF 文件格式概览

ELF 文件有两种视角可供选择,一种是链接视角,通过节(Section)来进行划分;另一种是运行视角,通过段(Segment)来进行划分。(ELF 文件格式定义在 /usr/include/elf.h

1.png

  • File header。必须位于文件的最开始处,包含有整个文件的结构信息。其结构定义为 Elf32_Ehdr/Elf64_Ehdr
  • Section header table(节表头)。用来描述重定位文件各个节的信息。对于重定位文件来说是必须的,对可执行文件来说是可选的。
  • Program header table(程序表头)。该表描述了加载程序准备执行所需的可加载段和其他数据结构。
  • Section/Segment。包括可加载数据、重定位以及字符串和符号表。

常用的查看 ELF 文件的几种方式:

  1. 使用 readelf 命令。可查看 ELF 文件整体格式。
  2. 使用 hexdump 命令。
  3. 使用 file 命令。

关于 hexdump 命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
hexdump [选项] [文件]
==============================================================
其中,选项可以如下:
选项释义:
常用:
-C 输出规范的十六进制和ASCII码。
-n length 只格式化输出length个字节。
-s addr 从偏移量开始输出。

不常用:
-b 单字节八进制显示。
-c 单字节字符显示。
-d 双字节十进制显示。
-o 双字节八进制显示。
-x 双字节十六进制显示。
-e 指定格式字符串,格式字符串包含在一对单引号中,格式字符串形如:'a/b "format1" "format2"'。

// 举例
hexdump -C -n 16 /usr/bin/ls

// https://blog.csdn.net/Ace_Shiyuan/article/details/118567212

ELF 文件格式概览如下:

4.png

5.png

6.png

参考:

1.3 ELF 中的数据类型

ELF 文件格式定义在 /usr/include/elf.h 中,文件中分别为 ELF32、ELF64 的文件定义了很多数据类型(如 ELF64_Addr),这些数据类型是使用的 uint32_t 等数据类型进行定义的(uint32_t 等类型在 /usr/include/stdint.h 中进行定义)。

2.png

3.png

2 ELF 文件格式解析

1.png

2.1 File Header

File Header 文件头必须位于 ELF 文件的开头,含有整个文件的一些基本信息。ELF32 对应的文件头为 Elf32_Ehdr 结构,ELF64 对应的文件头为 Elf64_Ehdr 结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
typedef struct
{
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf64_Half e_type; /* Object file type */
Elf64_Half e_machine; /* Architecture */
Elf64_Word e_version; /* Object file version */
Elf64_Addr e_entry; /* Entry point virtual address */
Elf64_Off e_phoff; /* Program header table file offset */
Elf64_Off e_shoff; /* Section header table file offset */
Elf64_Word e_flags; /* Processor-specific flags */
Elf64_Half e_ehsize; /* ELF header size in bytes */
Elf64_Half e_phentsize; /* Program header table entry size */
Elf64_Half e_phnum; /* Program header table entry count */
Elf64_Half e_shentsize; /* Section header table entry size */
Elf64_Half e_shnum; /* Section header table entry count */
Elf64_Half e_shstrndx; /* Section header string table index */
} Elf64_Ehdr;

2.2 e_ident 数组

e_ident 是一个 16 字节的数组,它将文件标识为 ELF 目标文件,并提供有关目标文件结构的数据表示的信息。 该数组最后剩余字节保留供将来使用,并且应设置为 0。 数组的每个字节都使用 EI_ 开头名称作为索引:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*  /usr/include/elf  */

#define EI_MAG0 0 /* File identification byte 0 index */
#define EI_MAG1 1 /* File identification byte 1 index */
#define EI_MAG2 2 /* File identification byte 2 index */
#define EI_MAG3 3 /* File identification byte 3 index */
#define EI_CLASS 4 /* File class byte index */
#define EI_DATA 5 /* Data encoding byte index */
#define EI_VERSION 6 /* File version byte index */
#define EI_OSABI 7 /* OS ABI identification */
#define EI_ABIVERSION 8 /* ABI version */
#define EI_PAD 9 /* Byte index of padding bytes */
#define EI_NIDENT (16) /* 数组大小 */

解释:

索引名称
EI_MAG0(0)~EI_MAG3(3) 文件的最前面 4 字节 eident[EI_MAGO]~eident[EI_MAG3] 的内容被称为“魔数(Magic)”,用于标识这是一个 ELF 文件。这四个字节存放的内容是固定的
7.png
EI_CLASS(4) e_ident[EI_CLASS] 指明文件是 ELF32 还是 ELF64 可执行文件。
8.png
EI_DATA(5) e_ident[EI_DATA] 指明了目标文件中的数据编码格式,小端序大端序。在 Intel 架构中,e_ident[EI_DATA] 取值为 ELFDATA2LSB
9.png
EI_VERSION(6) e_ident[EI_VERSION] 指明 ELF 文件头的版本,目前这个版本号是 EV_CURRENT(1)
EI_OSABI(7) e_ident[El_OSABI] 指示该 ELF 文件适用的操作系统和 ABI 规范。
10.png
EI_ABIVERSION(8) e_ident[El_ABIVERSION] 标识该对象文件的 ABI 的版本。 该字段用于区分 ABI 的不兼容版本。 此版本号的解释取决于 El_OSABI 字段标识的 ABI。
对于符合 System V ABl 第三版的应用程序,该字段应为 0
EI_PAD(9)~EI_NIDENT-1(15) 这 7 个字节目前暂时不使用,留作以后扩展,在实际的文件中应被填 0 补充。

2.3 其余字段

  • e_type:指示本目标文件属于哪种类型。

    ET_LOPROC~ ET_HIPROC(Oxff00~Oxffff)这一范围内的文件类型是为特定处理器而保留的,如果需要为某种处理器专门设定文件格式,可以从这一范围内选取一个做为标识。

    11.png

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    /* Legal values for e_type (object file type).  */

    #define ET_NONE 0 /* No file type */
    #define ET_REL 1 /* Relocatable file */
    #define ET_EXEC 2 /* Executable file */
    #define ET_DYN 3 /* Shared object file */
    #define ET_CORE 4 /* Core file */
    #define ET_NUM 5 /* Number of defined types */
    #define ET_LOOS 0xfe00 /* OS-specific range start */
    #define ET_HIOS 0xfeff /* OS-specific range end */
    #define ET_LOPROC 0xff00 /* Processor-specific range start */
    #define ET_HIPROC 0xffff /* Processor-specific range end */
  • e_machine:指定该文件适用的处理器体系结构。Intel x86 结构对应于 EM_386(3),x86-64(Intel/AMD)都是 EM_X86_64(62)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #define EM_NONE		 0		/* No machine */
    #define EM_M32 1 /* AT&T WE 32100 */
    #define EM_SPARC 2 /* SUN SPARC */
    #define EM_386 3 /* Intel 80386 */
    #define EM_68K 4 /* Motorola m68k family */
    #define EM_88K 5 /* Motorola m88k family */
    #define EM_860 7 /* Intel 80860 */
    #define EM_MIPS 8 /* MIPS R3000 big-endian */
    #define EM_S370 9 /* IBM System/370 */
    ...
    #define EM_X86_64 62 /* AMD x86-64 architecture */
  • e_version:此字段指明目标文件的版本。相同于 e_ident[EI_VERSION],当前取值为 EV_CURRENT(1)

    1
    2
    3
    4
    5
    /* Legal values for e_version (version).  */

    #define EV_NONE 0 /* Invalid ELF version */
    #define EV_CURRENT 1 /* Current version */
    #define EV_NUM 2
  • e_entry:程序入口的虚拟地址EOP)。即当文件被加载到进程空间里后,入口程 序在进程地址空间里的地址。对于可执行程序文件来说,当 ELF 文件完成加载之 后,程序将从这里开始运行;而对于其它文件来说,这个值应该是 0

  • e_phoff:指示程序头表(program header table)开始处在文件中的偏移量。如果没有程序头表,该值应设为 0

  • e_shoff:指示节头表(section header table)开始处在文件中的偏移量。如果没有节头表,该值应设为 0

  • e_flags:处理器特定的标志位。对于 Intel 架构的处理器来说,它没有定义任何标志位,所以 e_flags == 0

  • e_ehsize:指明 ELF文件头(File Header)的大小,以字节为单位。

  • e_phentsize:指明在程序头表中每一个表项条目(Entry)的大小,以字节为单位。

  • e_phnum:指明程序头表中总共有多少个表项条目。如果一个目标文件中没有程序头表,该值应设为 0

  • e_shentsize:指明节头表中每一个表项条目(Entry)的大小,以字节为单位。

  • e_shnum:指明节头表中总共有多少个表项条目。如果一个目标文件中没有节头表, 该值应设为 0

  • e_shstrndx:String Table Index,该值是一个索引值。在节区表中有一个存储各节区名称的节区 .shstrtab(通常是最后一个),这里表示名称表在第几个节区。如果文件没有节名称表,此值应设置为 SHN_UNDEF(0)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /* Special section indices.  */

    #define SHN_UNDEF 0 /* Undefined section */
    #define SHN_LORESERVE 0xff00 /* Start of reserved indices */
    #define SHN_LOPROC 0xff00 /* Start of processor-specific */
    #define SHN_BEFORE 0xff00 /* Order section before all others (Solaris). */
    #define SHN_AFTER 0xff01 /* Order section after all others (Solaris). */
    #define SHN_HIPROC 0xff1f /* End of processor-specific */
    #define SHN_LOOS 0xff20 /* Start of OS-specific */
    #define SHN_HIOS 0xff3f /* End of OS-specific */
    #define SHN_ABS 0xfff1 /* Associated symbol is absolute */
    #define SHN_COMMON 0xfff2 /* Associated symbol is common */
    #define SHN_XINDEX 0xffff /* Index is in extra table. */
    #define SHN_HIRESERVE 0xffff /* End of reserved indices */

    举例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [root@centos-7 ~]# readelf -h test.exe 
    ELF Header:
    Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
    ...
    Section header string table index: 29 // 索引值为 29

    // 查看节区,可以看到索引值 29 对应的节区名称为 .shstrtab
    [root@centos-7 ~]# readelf -S test.exe
    Section Headers:
    [Nr] Name Type Address Offset
    Size EntSize Flags Link Info Align
    ...
    [29] .shstrtab STRTAB 0000000000000000 00001819
    0000000000000108 0000000000000000 0 0 1

3 Sections

在目标文件中可以包含很多“节”(section),所有这些“节”都登记在一张称为 “节头表”(section header table)的数组里。节头表的每一个表项是一个 Elf32_Shdr/Elf64_Shdr 结构,通过每一个表项可以定位到对应的节。

除了文件头、程序头表、节表的信息外,ELF 文件的其他所有信息都包含在节中。ELF 文件所有节的信息都保存在一个节表数组中,数组中的每一项对应一个节,该项的成员描述对应的节。

节表是一个数组,节表在文件中的位置由 Elf64_Ehdr.e_shoff 指向(从文件开头的文件偏移),节表数组的项数由 Elf64_Ehdr.e_shnum 指定,节表每一项的大小由 Elf64_Ehdr.e_shentsize 指定。

3.1 节索引号(Section indices)

节表是一个数组,数组中的每一项都有一个对应的索引值,从 0 开始(项数由 Elf64_Ehdr.e_shnum 决定)。但是 00xFF00~0xFFFF 等索引值是保留的,具有特殊意义,如下表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Special section indices.  */

#define SHN_UNDEF 0 /* Undefined section */ 未定义的索引值
#define SHN_LORESERVE 0xff00 /* Start of reserved indices */ 被保留索引区间号的下限值(0xFF00-0xFFFF)
#define SHN_LOPROC 0xff00 /* Start of processor-specific */ 为处理器定制节所保留的索引区间号的下限值
#define SHN_BEFORE 0xff00 /* Order section before all others. */ 在此之前的为可用索引号
#define SHN_AFTER 0xff01 /* Order section after all others. */ 在此之后的为保留索引号
#define SHN_HIPROC 0xff1f /* End of processor-specific */ 为处理器定制节所保留的索引区间号的上限值
#define SHN_LOOS 0xff20 /* Start of OS-specific */ 特殊操作系统使用的节索引号的起始值
#define SHN_HIOS 0xff3f /* End of OS-specific */ 特殊操作系统使用的节索引号的结束值
#define SHN_ABS 0xfff1 /* Associated symbol is absolute */ 此节中所定义的符号地址固定,这个值不会因重定位而改变
#define SHN_COMMON 0xfff2 /* Associated symbol is common */ 此节中所定义的符号是公共的
#define SHN_XINDEX 0xffff /* Index is in extra table. */
#define SHN_HIRESERVE 0xffff /* End of reserved indices */ 被保留索引区间号的上限值

注释:

  1. 节表数组的第一项(索引为 0)必须保留,且该项为 0
  2. 节表数组 0xFF00-0xFFFF 这个区间的索引号是保留值。

如下查看 ELF 文件的节头表,可以看到索引区间为 0-29

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@centos-7 ~]# readelf -S test.exe
There are 30 section headers, starting at offset 0x1928:

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
000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 000002b8
0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 00000318
000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400356 00000356
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
...
[28] .strtab STRTAB 0000000000000000 00001650
00000000000001c9 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 00001819
0000000000000108 0000000000000000 0 0 1

3.2 节头表(数组)

节区主要存放各种特定类型的信息,比如程序的正文区(代码)、数据区(初始化和未初始化的数据)、调试信息、以及用于动态链接的一些节区,比如解释器(.interp)节区将指定程序动态装载 / 链接器 ld-linux.so 的位置,而过程链接表(plt)、全局偏移表(got)、重定位表则用于辅助动态链接过程。

https://github.com/tinyclub/open-c-book/blob/master/zh/chapters/02-chapter4.markdown

节表中的每一项都是一个条目(节表数组的项数由 Elf64_Ehdr.e_shnum 指定),每个条目的结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct
{
Elf64_Word sh_name; /* Section name (string tbl index) */
Elf64_Word sh_type; /* Section type */
Elf64_Xword sh_flags; /* Section flags */
Elf64_Addr sh_addr; /* Section virtual addr at execution */
Elf64_Off sh_offset; /* Section file offset */
Elf64_Xword sh_size; /* Section size in bytes */
Elf64_Word sh_link; /* Link to another section */
Elf64_Word sh_info; /* Additional section information */
Elf64_Xword sh_addralign; /* Section alignment */
Elf64_Xword sh_entsize; /* Entry size if section holds table */
} Elf64_Shdr;
成员 解释
sh_name 本节名字的在“节名称表”中的索引值。整个名字的字符串并不存储在这里,它仅是一个索引号,指向“字符串表”节中的某个位置,那里存储了一个以 \0结尾的字符串。
sh_type 本节的类型。如下代码 3-1。
sh_flags 本节的一些属性,由一系列标志比特位组成,各个比特定义了节的不同属性,当某种属性被设置时,相应的标志位被设为 1,反之则设为 0。如下代码 3-2。
sh_addr 如果本节的内容需要映射到进程空间中去,此成员指定映射的起始地址;如果不需要映射,此值为 0。
sh_offset 该节的文件偏移。该值是节的第一个字节在文件中的位置,即相对于文件开头的偏移量。单位是字节。
sh_size 本节的大小,单位是字节。
sh_link 此成员是一个索引值,指向节头表中本节所在的位置。根据节的类型不同,本成员的意义也有所不同。如下表 3-1.
sh_info 本节的附加信息,根据节的类型不同,本成员的意义也有所不同。如下表 3-1.
sh_addralign 指示 sh_addr 虚拟地址应该向多少字节对齐。ELF64 文件对齐值必须是 2 的倍数。如果该成员为 0/1,则表示该虚拟地址没有对齐的要求。
sh_entsize 某些节区中包含固定大小的项目,如符号表。对于这类节区,此成员给出每个表项的长度字节数。 如果节区中并不包含固定长度表项的表格,此成员取值为 0。

节表每一项的大小由 Elf64_Ehdr.e_shentsize 指定,ELF64 每个节表条目的大小为 64 bytes。

节的类型 sh_type,代码 3-1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* Legal values for sh_type (section type).  */

#define SHT_NULL 0 /* Section header table entry unused */ 无效的、无意义的节
#define SHT_PROGBITS 1 /* Program data */ 本节内容的格式和含义都由程序来决定
#define SHT_SYMTAB 2 /* Symbol table */ 该节是完整的链接符号的表,子集动态链接符号表为 SHT_DYNSYM
#define SHT_STRTAB 3 /* String table */ 该节保存连接符号名称的表或保存节名称的表
#define SHT_RELA 4 /* Relocation entries with addends */ 重定位节,含有带明确加数(addend)的重定位项
#define SHT_HASH 5 /* Symbol hash table */ 该节是哈希表
#define SHT_DYNAMIC 6 /* Dynamic linking information */ 表明本节包含的是动态连接信息
#define SHT_NOTE 7 /* Notes */ 表明本节包含的信息用于以某种方式来标记本文件
#define SHT_NOBITS 8 /* Program space with no data (bss) */ 表明本节的内容是空的,节并不占用实际的空间
#define SHT_REL 9 /* Relocation entries, no addends */ 重定位节,不带明确加数(addend)的重定位项
#define SHT_SHLIB 10 /* Reserved */ 此值是一个保留值,暂未指定语义
#define SHT_DYNSYM 11 /* Dynamic linker symbol table */ 链接符号的表,是 SHT_SYMTAB 的子集
#define SHT_INIT_ARRAY 14 /* Array of constructors */ 构造函数数组
#define SHT_FINI_ARRAY 15 /* Array of destructors */ 析构函数数组
#define SHT_PREINIT_ARRAY 16 /* Array of pre-constructors */ 预构造函数数组
#define SHT_GROUP 17 /* Section group */ 节组
#define SHT_SYMTAB_SHNDX 18 /* Extended section indeces */ 符号表扩展节的索引
#define SHT_NUM 19 /* Number of defined types. */ 已定义类型的数量
#define SHT_LOOS 0x60000000 /* Start OS-specific. */ 特定于操作系统的起始值
#define SHT_GNU_ATTRIBUTES 0x6ffffff5 /* Object attributes. */ 对象属性
#define SHT_GNU_HASH 0x6ffffff6 /* GNU-style hash table. */ GNU 风格的哈希表
#define SHT_GNU_LIBLIST 0x6ffffff7 /* Prelink library list */ 预链接库列表
#define SHT_CHECKSUM 0x6ffffff8 /* Checksum for DSO content. */DSO 内容的校验和
#define SHT_LOSUNW 0x6ffffffa /* Sun-specific low bound. */ Sun 处理器下限值
#define SHT_SUNW_move 0x6ffffffa
#define SHT_SUNW_COMDAT 0x6ffffffb
#define SHT_SUNW_syminfo 0x6ffffffc
#define SHT_GNU_verdef 0x6ffffffd /* Version definition section. */定义节的版本
#define SHT_GNU_verneed 0x6ffffffe /* Version needs section. */ 节需要的版本
#define SHT_GNU_versym 0x6fffffff /* Version symbol table. */ 符号表的版本
#define SHT_HISUNW 0x6fffffff /* Sun-specific high bound. */ Sun 处理器上限值
#define SHT_HIOS 0x6fffffff /* End OS-specific type */ 特定于操作系统的结束值
#define SHT_LOPROC 0x70000000 /* Start of processor-specific */ 为特殊处理器保留的节类型索引值的下边界
#define SHT_HIPROC 0x7fffffff /* End of processor-specific */ 为特殊处理器保留的节类型索引值的上边界
#define SHT_LOUSER 0x80000000 /* Start of application-specific */ 为应用程序保留节类型索引值的下边界
#define SHT_HIUSER 0x8fffffff /* End of application-specific */ 为应用程序保留节类型索引值的上边界

每个节都有相关的属性,如下代码 3-2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define SHF_WRITE	     (1 << 0)	/* Writable */				本节所包含的内容在进程运行过程中是可写的
#define SHF_ALLOC (1 << 1) /* Occupies memory during execution */ 本节内容在进程运行过程中要占用内存单元
#define SHF_EXECINSTR (1 << 2) /* Executable */ 此节内容是指令代码
#define SHF_MERGE (1 << 4) /* Might be merged */ 此节可能会被合并
#define SHF_STRINGS (1 << 5) /* Contains nul-terminated strings */ 包含以 null 结尾的字符串
#define SHF_INFO_LINK (1 << 6) /* `sh_info' contains SHT index */ ‘sh_info’包含SHT索引
#define SHF_LINK_ORDER (1 << 7) /* Preserve order after combining */ 合并后保留顺序
#define SHF_OS_NONCONFORMING (1 << 8) /* Non-standard OS specific handling required */需要非标准操作系统特定处理
#define SHF_GROUP (1 << 9) /* Section is member of a group. */ 本节是组的成员
#define SHF_TLS (1 << 10) /* Section hold thread-local data. */ 保存线程本地数据的节
#define SHF_MASKOS 0x0ff00000 /* OS-specific. */ 特定于操作系统的节
#define SHF_MASKPROC 0xf0000000 /* Processor-specific */ 保留给特殊处理器扩展用的
#define SHF_ORDERED (1 << 30) /* Special ordering requirement */ 由特殊顺序要求
#define SHF_EXCLUDE (1 << 31) /* Section is excluded unless
referenced or allocated (Solaris).*/ 除非引用或分配,否则节被排除 (Solaris)

表 3-1.

sh_type sh_link sh_info
SHT_DYNAMIC 表示该节所使用的字符串表在节表中的下标(索引)。
类似于 shstrtab 节使用到的字符串在节表的索引值为 e_shstrndx
0
SHT_HASH 表示该节所使用的字符串表在节表中的下标(索引)。 0
SHT_RELA,SHT_RE 相应符号表在节头表中的索引值 本重定位节所所作用的节在节头表中的索引值(需要被重定位的数据所在的节的索引)
SHT_SYMTAB,SHT_DYNSYM 相关字符串表的节头素引 符号表中最后一个本地符号的索引值加 1
其他 SHN_UNDEF(0) 0

参考:ELF:Sections

3.3 系统使用的节

更多节信息可查看 Various sections hold program and control information

  1. 以“.”开头的节区名称是系统保留的。应用程序可以使用没有前缀的节区名称,以避免与系统节区冲突。
  2. 目标文件中也可以包含多个名字相同的节区。
  3. 保留给处理器体系结构的节区名称一般构成为:处理器体系结构名称简写 + 节区名称。
  4. 处理器名称应该与 e_machine 中使用的名称相同。例如 .FOO.psect 街区是由 FOO 体系结构定义的 psect 节区。

常见的表的属性:

名字 类型 属性 意义
.init SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节包含进程初始化时要执行的程序指令。当程序开始运行时,系统会在进 入主函数之前执行这一节中的代码。
.fini SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节包含进程终止时要执行的程序指令。当程序正常退出时,系统会执行这 一节中的代码。
.bss SHT_NOBITS SHF_ALLOC+SHF_WRITE 本节中包含目标文件中未初始化的全局变量。一般情况下,可执行程序在开 始运行的时候,系统会把这一段内容清零。但是,在运行期间的 bss 段是由系统初 始化而成的,在目标文件中.bss 节并不包含任何内容,其长度为 0,所以它的节类 型为 SHT_NOBITS。
.comment SHT_PROGBITS 本节包含版本控制信息
.data/.data1 SHT_PROGBITS SHF_ALLOC+SHF_WRITE 这两个节用于存放程序中被初始化过的全局变量。在目标文件中,它们是占 用实际的存储空间的,与.bss 节不同。
.debug SHT_PROGBITS 调试信息,内容格式没有统一规定。所有以”.debug”为前缀的节名 字都是保留
.line SHT_PROGBITS 本节也是一个用于调试的节,它包含那些调试符号的行号,为程序指令码与 源文件的行号建立起联系。其内容格式没有统一规定。
.dynamic SHT_DYNAMIC 见下文 本节包含动态连接信息,并且可能有 SHF_ALLOC 和 SHF_WRITE 等属性。 是否具有 SHF_WRITE 属性取决于操作系统和处理器。
.dynstr SHT_STRTAB SHF_ALLOC 此节含有用于动态连接的字符串,一般是那些与符号表相关的名字
.dynsym SHT_DYNSYM SHF_ALLOC 此节含有动态连接符号表
.got SHT_PROGBITS SHF_ALLOC + SHF_WRITE 此节包含全局偏移量表
.hash SHT_HASH SHF_ALLOC 本节包含一张符号哈希表
.interp SHT_PROGBITS 见下文 此节含有 ELF 程序解析器的路径名。如果此节被包含在某个可装载的段中, 那么本节的属性中应置 SHF_ALLOC 标志位,否则不置此标志。
.note SHT_NOTE 注释节
.plt SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 此节包含函数连接表
.relname/.relaname SHT_REL/SHT_RELA 见下文 这两个节含有重定位信息。如果此节被包含在某个可装载的段中,那么本节 的属性中应置 SHF_ALLOC 标志位,否则不置此标志。注意,这两个节的名字 中”name”是可替换的部分,执照惯例,对哪一节做重定位就把”name”换成哪一节 的名字。比如,.text 节的重定位节的名字将是.rel.text 或.rela.text。
.rodata/.rodata1 SHT_PROGBITS SHF_ALLOC 本节包含程序中的只读数据,在程序装载时,它们一般会被装入进程空间中 那些只读的段中去
.shstrtab SHT_STRTAB 本节是“节名字表”,含有所有其它节的名字
.strtab SHT_STRTAB 见下文 本节用于存放字符串,主要是那些符号表项的名字。如果一个目标文件有一 个可装载的段,并且其中含有符号表,那么本节的属性中应该有 SHF_ALLOC
.symtab SHT_SYMTAB 见下文 本节用于存放符号表。如果一个目标文件有一个可载入的段,并且其中含有 符号表,那么本节的属性中应该有 SHF_ALLOC。
.text SHT_PROGBITS SHF_ALLOC + SHF_EXECINSTR 本节包含程序指令代码

.dynsym & .symtab 符号表

  • 符号表记录了目标文件中所用到的所有符号信息,通常分为.dynsym和.symtab,前者是后者的子集。
  • .dynsym保存了引用自外部文件的符号,只能在运行时被解析
  • .symtab还保存了本地符号,用于调试和链接。
  • 目标文件通过一个符号在表中的索引值来使用该符号。索引值从0开始计数,但值为0的表项不具有实际的意义,它表示未定义的符号STN_UNDEF。每个符号都有一个符号值(symbol value),对于变量和函数,该值就是符号的地址。

3.4 字符串表

ELF 文件中的字符串表是包含有若干个 null 字符的序列,即字符串表。在 ELF 文件中,这些字符串通常上节的名字符号的名字。在目标文件的其他位置,当需要使用到某个字符串时,只需要提供该字符串在字符串表中的起始位置(索引)即可。

字符串表的特征:字符串表的第一个字节永远是空(\0null),由于每一个字符串都是以 null 结尾,所以字符串表的最后一字节也是 null。在节的类型(sh_type)中,有 SHT_STRTAB 标志的节都是字符串表,都具有这一特征。

如下图,即为一个长度为 22 字节的字符串表:

12.png

索引 1 表示的字符串为 helloworld,索引 12 表示的字符串为 Myvariable

字符串表所在的节带有 SHT_STRTAB 类型标志,常用的两个字符串表所在的节:.shstrtab.strtab

  • .shstrtab,为 STRTAB 类型。保存所有节表的名称,如 .text.data 等。文件头的 e_shstrtab 成员是一个索引值,指出 .shstrtab 节在节头表数组的索引下标。每个节表条目中 sh_name 成员都是一个索引,使用该索引到该节指定的位置即可找到对应的字符串名称。
  • .strtab,为 STRTAB 类型。保存符号字符串,其字符串表中的内容会被 .symtab 中每个条目的 st_name 成员进行索引,使用该索引到该节指定的位置即可找到对应的字符串名称。
  • .dynstr ,为 STRTAB 类型。保存动态链接符号字符串。其字符串表中的内容会被 .dynsym 中每个条目的 st_name 成员进行索引。

3.5 符号表(数组)

对于可执行文件除了编译器引入的一些符号外,主要就是用户自定义的全局变量,函数等。而对于可重定位文件仅仅包含用户自定义的一些符号。

在 ELF文件中一般有两节 .symtab.dynsym 保存符号表,节的类型为 SHT_SYMTAB。我们将函数、变量等统称为符号(Symbol),函数名、变量名统称为符号名(Symbol name)。符号的引入是便于动态链接、静态链接,比如 A 文件使用到 B 文件中定义的一个函数,A 文件正是通过自身的符号表找到对应的引用函数。

每一个目标文件都会有符号表,表里面记录了该目标文件所使用到的所有符号,可以通过符号表中的 符号值 定位到符号保存的地址。

符号的分类:本地全局符号(在本文件中定义的全局符号)、外部符号(在其他文件中定义的符号,在本文件中引用)、节名/段名、局部符号、行号(源代码的代码行和指令对应的关系)。

类型为 SHT_SYMTAB、SHT_DYNSYM 的节表示符号表,ELF64 符号表的结构如下:

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf64_Word st_name; /* Symbol name (string tbl index) */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf64_Section st_shndx; /* Section index */
Elf64_Addr st_value; /* Symbol value */
Elf64_Xword st_size; /* Symbol size */
} Elf64_Sym;

符号表也是一个数组,每一个元素都是 Elf64_Sym 结构,其中数组下标 0 的元素为 0。每一个 Elf64_Sym 结构对应一个符号。

  • st_name:指明当前符号在字符串表中的索引。对于 .symtab(SHT_SYMTAB) 的节,其对应的字符串表在 Elf64_Shdr.sh_link 为索引的节中。

  • st_shndx:索引值。如果该结构对应的符号是在本文件中定义的,st_shndx 表示该符号所在的节在节头表中的索引。如果该结构对应的符号不是在本文件中定义的,st_shndx 表示一个特殊索引值:

    • SHN_ABS(0xfff1):表示该值是一个绝对的值,不会因为重定位前后发生改变。
    • SHN_COMMON(0xfff2):一般表示未初始化的全局变量符号。该符号一般位于 .bss 节中。
    • SHN_UNDEF(0):符号未定义。表示该符号在本文件中进行引用,但是在其他文件中进行定义的。
  • st_value:一个数值或地址,具体如下:

    • 在重定位文件中,如果 st_shndx 索引指向的节类型为 SHN_COMMON(0xfff2),该值为索引指向的这个节的对齐值。如果 st_shndx 索引指向的节类型不为 SHN_COMMON(0xfff2),该值表示对应的符号在以 st_shndx 为索引的节中的偏移量。
    • 在可执行文件或共享库文件中,st_value 即表示该符号的虚拟地址。
  • st_size:符号所占空间大小(字节单位)。 比如 double 类型的变量占用 8 字节,地址也是占用 8 字节,char 字符串长度等。0 表示大小为 0 或未知。

  • st_info:符号绑定(以 STB_ 开头)和符号类型(以 STT_ 开头)。低 4 位表示符号类型,高 4 位表示绑定信息。提取 st_info 中的绑定信息和类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* How to extract and insert information held in the st_info field.  */
// 提取 st_info 中的绑定信息和类型
#define ELF32_ST_BIND(val) (((unsigned char) (val)) >> 4)
#define ELF32_ST_TYPE(val) ((val) & 0xf)
#define ELF32_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf))

/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */
#define ELF64_ST_BIND(val) ELF32_ST_BIND (val)
#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val)
#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type))

/* Legal values for ST_BIND subfield of st_info (symbol binding). */

#define STB_LOCAL 0 /* Local symbol */
#define STB_GLOBAL 1 /* Global symbol */
#define STB_WEAK 2 /* Weak symbol */
#define STB_NUM 3 /* Number of defined types. */
#define STB_LOOS 10 /* Start of OS-specific */
#define STB_GNU_UNIQUE 10 /* Unique symbol. */
#define STB_HIOS 12 /* End of OS-specific */
#define STB_LOPROC 13 /* Start of processor-specific */
#define STB_HIPROC 15 /* End of processor-specific */

/* Legal values for ST_TYPE subfield of st_info (symbol type). */

#define STT_NOTYPE 0 /* Symbol type is unspecified */
#define STT_OBJECT 1 /* Symbol is a data object */
#define STT_FUNC 2 /* Symbol is a code object */
#define STT_SECTION 3 /* Symbol associated with a section */
#define STT_FILE 4 /* Symbol's name is file name */
#define STT_COMMON 5 /* Symbol is a common data object */
#define STT_TLS 6 /* Symbol is thread-local data object*/
#define STT_NUM 7 /* Number of defined types. */
#define STT_LOOS 10 /* Start of OS-specific */
#define STT_GNU_IFUNC 10 /* Symbol is indirect code object */
#define STT_HIOS 12 /* End of OS-specific */
#define STT_LOPROC 13 /* Start of processor-specific */
#define STT_HIPROC 15 /* End of processor-specific */

符号表是一个 Elf64_Sym 结构的数组,name应该如何计算该数组元素的个数呢?

  1. 找到 .symtab 符号表所在的节。
  2. 该节 Elf64_Shdr.sh_entsize 表示每个元素的大小,Elf64_Shdr.sh_size 表示该节的总大小。则$(Elf64\_Shdr.sh\_size) / (Elf64\_Shdr.sh\_entsize)$ 为元素的个数。

符号表 .symtab.dynsym 的区别:

  • .symtab:保存有本文件所有的符号。
  • .dynsym:保存的是外部符号。是 .symtab 的子集。

3.6 重定位表(数组)

重定位文件存在静态链接、动态链接,都涉及到重定位。

静态链接时重定位由链接器来完成,装载时重定位是在模块被装载时,操作系统来完成。

当某个可执行文件由多个目标文件编译链接时,目标文件中的部分数据需要进行静态重定位,比如:

1
2
00040010:  e8 fc ff ff ff    call 7
00040014:

链接器会对偏移为 7 的地方进行重定位(根据重定位表中对应的条目),当目标文件被链接成为可执行文件后,对应的代码可能会被重定位成如下代码:

1
080480de:e8 05 00 00 00    call 080480e8

静态链接的工作由链接器来完成,实际上我们并不关心具体的实现静态重定位逻辑,在构造利用代码时也基本用不到。而动态链接工作是在目标文件被加载时,会对目标文件数据段或代码段中引用的绝对地址等进行重定位,我们关注的是动态重定位

重定位是将符号引用与符号定义进行链接的过程。因此链接是处理可重定位文件,把它们的各种符号引用和符号定义转换为可执行文件中的合适信息(一般是虚拟内存地址)的过程。

链接又分为静态链接和动态链接,前者是程序开发阶段程序员用 ldgcc 实际上在后台调用了 ld)静态链接器手动链接的过程,实际上我们并不关心具体的实现静态重定位逻辑,在构造利用代码时也基本用不到。而动态链接则是程序运行期间系统调用动态链接器(ld-linux.so)自动链接的过程,动态链接工作是在目标文件被加载时,会对目标文件数据段或代码段中引用的绝对地址等进行重定位,我们关注的是动态重定位。我们关注的是动态重定位

——Gcc 编译的背后-链接

目标文件中,有专门的节来保存文件重定位时需要被修正的地方,这样的节叫做重定位表(节类型为 SHT_RELSHT_RELA)。

重定位表是一个数组,数组的每个元素为 Elf64_RelElf64_Rela 结构,一个元素对应一个需要被修正的数据。重定位表分两种类型,包含使用显示加数r_addend)的 Elf64_Rela 结构,和使用隐式加数Elf64_Rel 结构(x86 仅使用 Elf32_Rel 重定位项,64 位的可执行文件一般只使用 Elf64_Rela 结构)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Relocation table entry without addend (in section of type SHT_REL).  */
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
} Elf64_Rel;

/* Relocation table entry with addend (in section of type SHT_RELA). */
typedef struct
{
Elf64_Addr r_offset; /* Address */
Elf64_Xword r_info; /* Relocation type and symbol index */
Elf64_Sxword r_addend; /* Addend */
} Elf64_Rela;
成员 说明
r_offset 可重定位文件:是一个偏移值,相对于要重定位数据所在的节的文件起始位置。
可执行文件或共享动态链接文件:要被重定位的位置的虚拟地址。
r_info ELF64 中,高 32 位指明要重定位的符号在符号表数组中的下标(不是在节头表中的索引),低 32 位指明重定位的类型(说明如何修改重定位的位置)。
r_addend 是一个常量加数,用来计算要重定位的位置。

关于 r_info

1
2
3
4
5
6
7
8
9
/* How to extract and insert information held in the r_info field.  */

#define ELF32_R_SYM(val) ((val) >> 8)
#define ELF32_R_TYPE(val) ((val) & 0xff)
#define ELF32_R_INFO(sym, type) (((sym) << 8) + ((type) & 0xff))

#define ELF64_R_SYM(i) ((i) >> 32)
#define ELF64_R_TYPE(i) ((i) & 0xffffffff)
#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) << 32) + (type))

重定位的类型 ELF64_R_TYPE(r_info) 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* AMD x86-64 relocations.  /usr/include/elf.h line 2850 */
#define R_X86_64_NONE 0 /* No reloc */
#define R_X86_64_64 1 /* Direct 64 bit */
#define R_X86_64_PC32 2 /* PC relative 32 bit signed */
#define R_X86_64_GOT32 3 /* 32 bit GOT entry */
#define R_X86_64_PLT32 4 /* 32 bit PLT address */
#define R_X86_64_COPY 5 /* Copy symbol at runtime */
#define R_X86_64_GLOB_DAT 6 /* Create GOT entry */
#define R_X86_64_JUMP_SLOT 7 /* Create PLT entry */
#define R_X86_64_RELATIVE 8 /* Adjust by program base */
#define R_X86_64_GOTPCREL 9 /* 32 bit signed PC relative offset to GOT */
#define R_X86_64_32 10 /* Direct 32 bit zero extended */
#define R_X86_64_32S 11 /* Direct 32 bit sign extended */
#define R_X86_64_16 12 /* Direct 16 bit zero extended */
#define R_X86_64_PC16 13 /* 16 bit sign extended pc relative */
#define R_X86_64_8 14 /* Direct 8 bit sign extended */
#define R_X86_64_PC8 15 /* 8 bit sign extended pc relative */
#define R_X86_64_DTPMOD64 16 /* ID of module containing symbol */
#define R_X86_64_DTPOFF64 17 /* Offset in module's TLS block */
#define R_X86_64_TPOFF64 18 /* Offset in initial TLS block */
#define R_X86_64_TLSGD 19 /* 32 bit signed PC relative offset to two GOT entries for GD symbol */
#define R_X86_64_TLSLD 20 /* 32 bit signed PC relative offset to two GOT entries for LD symbol */
#define R_X86_64_DTPOFF32 21 /* Offset in TLS block */
#define R_X86_64_GOTTPOFF 22 /* 32 bit signed PC relative offset to GOT entry for IE symbol */
#define R_X86_64_TPOFF32 23 /* Offset in initial TLS block */
#define R_X86_64_PC64 24 /* PC relative 64 bit */
#define R_X86_64_GOTOFF64 25 /* 64 bit offset to GOT */
#define R_X86_64_GOTPC32 26 /* 32 bit signed pc relative offset to GOT */
#define R_X86_64_GOT64 27 /* 64-bit GOT entry offset */
#define R_X86_64_GOTPCREL64 28 /* 64-bit PC relative offset to GOT entry */
#define R_X86_64_GOTPC64 29 /* 64-bit PC relative offset to GOT */
#define R_X86_64_GOTPLT64 30 /* like GOT64, says PLT entry needed */
#define R_X86_64_PLTOFF64 31 /* 64-bit GOT relative offset to PLT entry */
#define R_X86_64_SIZE32 32 /* Size of symbol plus 32-bit addend */
#define R_X86_64_SIZE64 33 /* Size of symbol plus 64-bit addend */
#define R_X86_64_GOTPC32_TLSDESC 34 /* GOT offset for TLS descriptor. */
#define R_X86_64_TLSDESC_CALL 35 /* Marker for call through TLS descriptor. */
#define R_X86_64_TLSDESC 36 /* TLS descriptor. */
#define R_X86_64_IRELATIVE 37 /* Adjust indirectly by program base */
#define R_X86_64_RELATIVE64 38 /* 64-bit adjust by program base */
#define R_X86_64_NUM 39

重定位节会引用其他两个节:符号表(由 sh_link 进行索引)和要修改的节(由 sh_info 进行索引)。

重定位计算:以下表示法用于说明重定位计算。

  • A—用于计算可重定位字段的值的加数。
  • B—执行过程中将共享目标文件装入内存的基本地址。通常,生成的共享目标文件的基本虚拟地址为 0。但是,共享目标文件的执行地址不相同。请参见程序头
  • G—执行过程中,重定位项的符号地址所在的全局偏移表中的偏移。请参见全局偏移表(特定于处理器)

GOT—全局偏移表的地址。请参见全局偏移表(特定于处理器)

  • L—符号的过程链接表项的节偏移或地址。请参见过程链接表(特定于处理器)
  • P—使用 r_offset 计算出的重定位的存储单元的节偏移或地址。
  • S—索引位于重定位项中的符号的值。
  • Z—索引位于重定位项中的符号的大小。

x64: ELF 重定位类型与计算方式:

名称 字段 计算
R_AMD64_NONE 0
R_AMD64_64 1 word64 S + A
R_AMD64_PC32 2 word32 S + A - P
R_AMD64_GOT32 3 word32 G + A
R_AMD64_PLT32 4 word32 L + A - P
R_AMD64_COPY 5 请参阅此表后面的说明。
R_AMD64_GLOB_DAT 6 word64 S
R_AMD64_JUMP_SLOT 7 word64 S
R_AMD64_RELATIVE 8 word64 B + A
R_AMD64_GOTPCREL 9 word32 G + GOT + A - P
R_AMD64_32 10 word32 S + A
R_AMD64_32S 11 word32 S + A
R_AMD64_16 12 word16 S + A
R_AMD64_PC16 13 word16 S + A - P
R_AMD64_8 14 word8 S + A
R_AMD64_PC8 15 word8 S + A - P
R_AMD64_PC64 24 word64 S + A - P

3.7 Hash 表

ELF 文件中节类型为 SHT_SYMTAB、SHT_DYNSYM 的节表包含大量的符号(后者是前者的子集),在进行共享库的加载、或符号查找时,如果对符号进行线性搜索是非常慢的。所以在生成可执行文件或共享库文件时,链接器会生成对应的 SHT_HASHSHT_GNU_HASH 哈希表,以便从 ELF 的符号表中快速查找符号,加快链接。

哈希(散列)表的原理:在链接生成可执行文件或共享库文件时,对符号表每一个字符串进行哈希运算(不包含 \0 字符)得到对应的哈希值,然后将所有哈希值保存在哈希表节中。之后有对字符串查找的需求时,使用相同的散列算法生成哈希值,拿着哈希值从 SHT_SYMTAB、SHT_DYNSYM 找到对应的符号。

SHT_HASHSHT_GNU_HASH 是有区别的(后者从 2006 年开始使用),后者是对前者的改进,但是需要注意的是:DT_HASH 格式由 System V ABI 规范强制执行的,而 ABI 规范中没有对 DT_GNU_HASH 格式进行记录(非强制使用),但是在后来的 ELF 文件中基本都是使用 DT_GNU_HASH 而启用 SHT_HASH

一、SHT_HASH

System V ABI 规范 Hash Table 中规定,哈希表结构如下(x86 和 x86-64 通用):

1
2
3
4
5
6
struct elf_hash_table {
uint32_t nbucket; // 数组 bucket 的项数
uint32_t nchain; // 数组 chain 的项数
uint32_t bucket[nbucket]; // 符号表的索引值
uint32_t chain[nchain]; // 索引值。字符串的哈希值相同时,用该链进行遍历
};

这里有一个规定:数组 chain 的项数 nchain,必须和符号表 SHT_SYMTAB 数组的项数相等,且一一对应。

计算字符串哈希值的函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned long elf_Hash(const unsigned char *name)	// 参数为输入的字符串
{
unsigned long h = 0, g;

while (*name)
{
h = (h << 4) + *name++;
if (g = h & 0xf0000000)
h ^= g >> 24;
h &= ~g;
}
return h;
};

字符串查找的过程:给定一个符号名字,输入到函数 elf_Hash 返回一个哈希值 x,然后由 bucket[x%nbucket] 得到一个符号表索引 y,如果索引 y 对应的符号表项不是想要的符号,则由 chain[y] 得到下一个符号表索引 z,如果仍不是想要的符号,继续 chain[z]…直到 chain[xxx] == STN_UNDEF,表示目标文件中不包含想要查找的符号。伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
unsigned long x = 0;
unsigned long y = 0;
Elf_Sym* symbols = NULL;

x = elf_Hash(pname);
y = bucket [x % nbucket];

for(; chain[y] != STN_UNDEF; )
{
if(strcmp(symbols[y], pname) == 0) return &symbols[y];
else y = chain[y];
}

DT_HASH 的缺点:虽然通过 DT_HASH 可以找到现有的符号,但它对不存在的符号查找表现不佳。对于不存在的符号,可能需要遍历 chain 链多次并比较字符串,直到碰撞撞到 STN_UNDEF 才停止。这在现实生活中变得更加糟糕,动态加载链接时需要在多个共享库中搜索符号,因此必须走过多个随机链。

1990s 年代使用 DT_HASH,此后引入了一个名为 DT_GNU_HASH 的新哈希表,该表现在几乎无处不在,不再使用 DT_HASH。可惜的是,DT_GNU_HASH 实际上没有标准化,除了BFD(二进制文件描述符库)源代码外,甚至没有在任何地方进行描述。

参考:

二、SHT_GNU_HASH

在几乎任何 Linux 发行版的系统中,使用 gcc 或 clang 编译的程序中都会使用到到 DT_GNU_HASH。但是除了在 GNU binutilsglibc 源代码外,没有在任何地方记录它。引入了 Bloom filter

引入 DT_GNU_HASH 的主要目的是解决对不存在符号查找的优化,提升符号查找的效率。

参考 Re: GNU_HASH section format 的描述,x86-64 下 DT_GNU_HASH 的结构为:

1
2
3
4
5
6
7
8
9
struct gnu_hash_table {
uint32_t nbuckets; // buckets 数组的元素个数
uint32_t symoffset; // 符号表中可被访问的第一个符号的索引(一般为 1),索引 0 = STN_UNDEF
uint32_t bloom_size;
uint32_t bloom_shift;
uint64_t bloom[bloom_size]; /* uint32_t for 32-bit binaries */
uint32_t buckets[nbuckets]; // 存放字符串的哈希值
uint32_t *chain;
};

计算字符串哈希值的函数如下(该函数可以在 bfd_elf_gnu_hashdl_new_hash 中找到):

1
2
3
4
5
6
7
8
9
10
11
#include <stdint.h>

uint32_t gnu_hash(const uint8_t* name) {
uint32_t h = 5381;

for (; *name; name++) {
h = (h << 5) + h + *name;
}

return h;
}

字符串过滤(目的:判断目标字符串是否存在于符号表中):

  1. ELFCLASS32ELFCLASS64 二进制文件查找计算不同,先定义变量 ELFCLASS_BITS

    • ELFCLASS32:ELFCLASS_BITS = 32
    • ELFCLASS64:ELFCLASS_BITS = 64
  2. 计算字符串哈希值:hash = gnu_hash(name)

  3. 计算字符串过滤值:filter_value = bloom[(hash / ELFCLASS_BITS) % bloom_size]

  4. 计算掩码值:bitmask = (1 << (hash % ELFCLASS_BITS)) | (1 << ((hash >> bloom_shift) % ELFCLASS_BITS))

  5. 如果字符串过滤值没有对应的掩码位,则字符串一定不存在于符号表中。

    1
    if ((filter_value & bitmask) != bitmask) return null;

过滤完之后如果不返回,说明符号表中存在要查找的字符串。先判断从 buckets 桶取得的符号表的初始索引值,该索引值不应该小于 symoffset

1
2
uint32_t symix = buckets[namehash % nbuckets];
if (symix < symoffset) return null;

最后才是查找字符串(即遍历 chain 链)。

参考:

4 Segments

4.1 程序头表(数组)

可执行文件和动态链接库文件的程序头表是一个数组(类似于节头表),每个元素对应于一个段(Segment),每个段对应一个或多个节,程序头只针对可执行文件和动态链接库(shared object file)有意义,其他类型的目标文件可忽略。

文件头 Elf64_Ehdr.e_phoff 指出程序头表数组起始的文件偏移位置, Elf64_Ehdr.e_phnum 指出程序头表数组元素的个数。

程序头表数组类型如下:

1
2
3
4
5
6
7
8
9
10
11
typedef struct
{
Elf64_Word p_type; /* Segment type */
Elf64_Word p_flags; /* Segment flags */
Elf64_Off p_offset; /* Segment file offset */
Elf64_Addr p_vaddr; /* Segment virtual address */
Elf64_Addr p_paddr; /* Segment physical address */
Elf64_Xword p_filesz; /* Segment size in file */
Elf64_Xword p_memsz; /* Segment size in memory */
Elf64_Xword p_align; /* Segment alignment */
} Elf64_Phdr;
  • p_type:段的类型。

    名称 含义
    PT_NULL 0 该数组元素未使用。
    PT_LOAD 1 可加载的段
    PT_DYNAMIC 2 指示该段是动态链接信息的段,指出动态链接时要做的事。主要包括以下三类信息:
    运行时需要链接的动态链接库列表(名称)。
    全局偏移表(GOT)的地址。
    重定位条目的相关信息。
    PT_INTERP 3 该段的内容(p_offset)是一个 null 结尾的字符串,该字符串将被当作目标程序加载器调用(一般是 [/lib64/ld-linux-x86-64.so.2](https://ld-linux.so/) 的完整路径)。这种段类型仅对与可执行文件有意义(尽管也可能在共享目标文件上发生)。在一个文件中最多只能出现一次。如果存在这种类型的段,它必须在所有可加载段的前面。
    PT_NOTE 4 该段指出 SHT_NOTE 节的位置和大小,包含与特定供应商或者系统相关的附加消息。
    PT_SHLIB 5 此段类型被保留,语义未指定。包含这种类型的段的程序与 ABI不符。
    PT_PHDR 6 如果存在,则指定程序头表本身在文件和程序内存映像中的位置和大小。此段类型在一个文件中只能出现一次。而且,只有当程序头表是程序的内存映像的一部分时才可能发生。如果它存在,它必须在任何可加载段项之前。
    PT_TLS 7 本地线程存储段。参考链接程序和库指南
    PT_NUM 8 已经定义的类型总数(Number of defined types)
    PT_LOOS - PT_HIOS 0x60000000~0x6fffffff 此范围内的值保留用于特定于操作系统。
    PT_LOPROC - PT_HIPROC 0x70000000~0x7fffffff 此范围内的值(包括这两个值)保留用于特定于处理器。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /* Legal values for p_type (segment type).  */

    #define PT_NULL 0 /* Program header table entry unused */
    #define PT_LOAD 1 /* Loadable program segment */
    #define PT_DYNAMIC 2 /* Dynamic linking information */
    #define PT_INTERP 3 /* Program interpreter */
    #define PT_NOTE 4 /* Auxiliary information */
    #define PT_SHLIB 5 /* Reserved */
    #define PT_PHDR 6 /* Entry for header table itself */
    #define PT_TLS 7 /* Thread-local storage segment */
    #define PT_NUM 8 /* Number of defined types */
    #define PT_LOOS 0x60000000 /* Start of OS-specific */
    #define PT_GNU_EH_FRAME 0x6474e550 /* GCC .eh_frame_hdr segment */
    #define PT_GNU_STACK 0x6474e551 /* Indicates stack executability */
    #define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */
    #define PT_LOSUNW 0x6ffffffa
    #define PT_SUNWBSS 0x6ffffffa /* Sun Specific segment */
    #define PT_SUNWSTACK 0x6ffffffb /* Stack segment */
    #define PT_HISUNW 0x6fffffff
    #define PT_HIOS 0x6fffffff /* End of OS-specific */
    #define PT_LOPROC 0x70000000 /* Start of processor-specific */
    #define PT_HIPROC 0x7fffffff /* End of processor-specific */
  • p_offset:该段的文件偏移(相对于文件起始位置)。

  • p_vaddr:段在内存中的虚拟地址。

  • p_paddr:段的物理地址,物理地址是不确定的,大部分操作系统保留该值。

  • p_filesz:该段数据在文件中的大小。

  • p_memsz:该段数据在内存中的大小。

  • p_align:指定本段内容如何在文件、内存中进行对齐。此成员可提供一个值,用于在内存和文件中根据该值对齐各段。值 0 和 1 表示无需对齐。另外,p_align 应为 2 的正整数幂,并且 p_vaddr 和 p_offset应该同余(以 p_align 为模数)。

  • p_flags:该段的相关属性。系统将可装入段加载入内存映像时,将会授予如 p_flags 成员中所指定的访问权限。

    名称 含义
    PF_X 0x1 执行
    PF_W 0x2
    PF_R 0x4
    PF_MASKOS 0x0ff00000 未指定,所有位都保留用于特定操作系统
    PF_MASKPROC 0xf0000000 未指定,所有位都保留用于特定处理器

    如果权限值为 0,表示无任何权限。实际的读写权限还要依赖于内存管理器,在不同的操作系统上,内存管理单元的做法可能会不同。但是如果 p_flags 中没有指定 PF_W 的话,系统一定不会给出写权限

    下表给出了在一些权限组合的情况:

    14.png

    权限总结:

    1. 可读与可执行是通用的,有其中一个就等于也有了另一个
    2. 可写权限是最高权限,可以覆盖另外两个,有了可写权限,所有权限就都有了

4.2 动态段

需要进行动态链接的目标文件,在程序头中都会包含一个 PT_DYNAMIC 动态链接段。动态链接段提供很多链接信息,比如当前目标文件需要依赖哪些共享目标文件、动态链接符号表的位置、动态链接重定位表的位置等。动态段包含所有的 SHT_DYNAMIC 动态节,主要是 .dynamic 节。

SHT_DYNAMIC 动态节是一个数组,数组的类型为 Elf64_Dyn

1
2
3
4
5
6
7
8
9
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type */
union {
Elf64_Xword d_val; /* Integer value Elf32_Word 类型的整数 */
Elf64_Addr d_ptr; /* Address value 是一个进程空间里的地址,Elf32_Addr 类型*/
} d_un;
} Elf64_Dyn;
extern Elf64_Dyn _DYNAMIC[];

d_tag 控制着 d_un 的解析,d_tag 不同的取值决定着 d_un 的取值:

d_tag d_un 可执行文件 共享目标文件 意义
DT_NULL 0 忽略 必需 必需 用于标记 DYNAMIC 数组的结束
DT_NEEDED 1 d_val 可选 可选 此元素指明了一个所依赖的库的名字。不过此元素本身并不是一个字符串,它是一个索引值,是由 DT_STRTAB 所标记的字符串表中的索引,在该字符串表中,此索引处是一 个以 null 结尾的字符串,这个字符串就是库的名字。在动态数组中可以包含若干个此类型的项,这些项出现的相对顺序是不能随意调换的。
DT_PLTRELSZ 2 d_val 可选 可选 此元素含有与函数链接表(PLT)相关联的所有重定位项的总大小,以字节为单位。如果存在 DT_JMPREL 类型的条目,必须有与之配合的 DT_PLTRELSZ 条目。
DT_PLTGOT 3 d_ptr 可选 可选 此元素包含与函数链接表或全局偏移量表相应的虚拟地址(动态库和可执行文件)。在 Intel 架构中,这一项的 d_ptr 成员给出全局偏移量表(GOT)中第一项的地址。如下文所述,全局偏移量表中前三项都是保留的,其中两项用于持有函数连接表信息。
got[0]:本ELF动态段(.dynamic段)的装载地址
got[1]:本ELF的 link_map 数据结构描述符地址
got[2]_dl_runtime_resolve 函数的地址
DT_HASH 4 d_ptr 必需 必需 此元素含有符号哈希表的地址(对于共享库文件:字符串表的文件偏移,对于可执行文件:字符串表的虚拟地址)。这里所指的哈希表与 DT_SYMTAB 所指的哈希表是同一个。
DT_STRTAB 5 d_ptr 必需 必需 对于共享库文件:字符串表的文件偏移,对于可执行文件:字符串表的虚拟地址。此表中包含符号名、库名等等。
DT_SYMTAB 6 d_ptr 必需 必需 此元素包含符号表的地址(对于共享库文件:字符串表的文件偏移,对于可执行文件:字符串表的虚拟地址),符号表数组类型为 Elf64_Sym
DT_RELA 7 d_ptr 必需 可选 此元素包含一个重定位表的地址,在重定位表中存储的是显式的“加数”, 比如对于 32 位文件来说,这种加数就是 Elf32_Rela。在一个目标文件中可以存在多个重定位节,当为可执行文件或共享目标文件创建重定位表的时候,连接编辑器会把这些重定位节连接在一起,最后形成一张大的重定位表。当连接编辑器为一个可执行文件创建进程空间,或者把一个共享目标添加到进程空间中去的时候,它会去读重定位表并执行相应的操作。
如果在动态结构中包含有 DT_RELA 元素的话, 就必须同时还包含 DT_RELASZDT_RELEANT 元素。
如果一个文件需要重定位的话,DT_RELADT_REL 至少要出现一个。
DT_RELASZ 8 d_val 必需 可选 此元素持有 DT_RELA 相应的重定位表的大小,以字节为单位。
DT_RELAENT 9 d_val 必需 可选 此元素持有 DT_RELA 相应的重定位表项的大小,以字节为单位。
DT_STRSZ 10 d_val 必需 必需 此元素持有字符串表的大小,以字节为单位。
DT_SYMENT 11 d_val 必需 必需 此元素持有符号表项的大小,以字节为单位。
DT_INIT 12 d_ptr 可选 可选 此元素持有初始化函数的地址。参见下文”初始化和终止函数”内容。
DT_FINI 13 d_ptr 可选 可选 此元素持有终止函数的地址。参见下文”初始化和终止函数”内容。
DT_SONAME 14 d_val 忽略 可选 是一个字符串表中的偏移量(索引),该位置存储了一个以 ’null’ 结尾的字符串,是一个共享目标的名字。相应的字符串表由 DT_STRTAB 指定。
DT_RPATH 15 d_val 可选 忽略 是一个字符串表中的偏移量(索引),该位置存储了一个以 ’null’ 结尾的字符串,是一个用于搜索库文件的路径名。相应的字符串表由 DT_STRTAB 指定。
它的用途已被 DT_RUNPATH 所取代。https://docs.oracle.com/cd/E26926_01/html/E25910/chapter6-42444.html
https://refspecs.linuxbase.org/elf/gabi4+/ch5.dynamic.html
DT_SYMBOLIC 16 忽略 忽略 可选 在共享目标文件中,此元素的出现与否决定了动态连接器解析符号时所用的算法。如果此元素不出现的话,动态连接器先搜索可执行文件再搜索库文件;如果此元素出现的话,顺序刚好相反,动态连接器会先从本共享目标文件开始,后搜索可执行文件。
DT_REL 17 d_ptr 必需 可选 此元素与 DT_RELA 相似,只是它所指向的重定位表中,“加数”是隐含的而不是显式的。
DT_RELSZ 18 d_val 必需 可选 此元素持有 DT_REL 相应的重定位表的大小,以字节为单位。
DT_RELENT 19 d_val 必需 可选 此元素持有 DT_REL 相应的重定位表项的大小,以字节为单位。
DT_PLTREL 20 d_val 可选 可选 本成员指明了函数链接表(PLT)所引用的重定位项的类型。d_val 成员含有 DT_RELDT_RELA。函数链接表中的所有重定位类型都是相同的。
DT_DEBUG 21 d_ptr 可选 忽略 本成员用于调试,格式未明确定义。
DT_TEXTREL 22 忽略 可选 可选 如果此元素出现的话,在重定位过程中如果需要修改的是只读段的话,连接 编辑器可以做相应的修改;而如果此元素不出现的话,在重定位过程中,即使需 要,也不能修改只读段。
DT_JMPREL 23 d_ptr 可选 可选 此类型元素如果存在的话,其 d_ptr 成员包含与函数链接表(PLT)关联的重定位项地址。把多个重定位项分开可以让动态链接器在初始化的时候忽略它们(以便延迟加载),当然前提条件是“后期绑定”是激活的。如果此元素存在的话,DT_PLTRELSZDT_PLTREL 也应该出现。

实际上就是指示着有导入表,实际上就是 .rela.plt 节的地址(Elf64_Rela 类型)。
.rela.plt = (Elf64_Rela*)d_ptr
DT_BIND_NOW 24 忽略 可选 可选 如果此元素存在的话,动态链接器必须在程序开始执行以前,完成所有包含此项的目标的重定位工作。如果此元素存在,即使程序应用了“后期绑定”,它对于此项所指定的目标也不适用,动态连接器仍需事先做好重定位。
DT_INIT_ARRAY 25 d_ptr 可选 可选 初始化函数的指针数组的地址。此元素要求同时存在 DT_INIT_ARRAYSZ 元素。请参见初始化节和终止节
DT_FINI_ARRAY 26 d_ptr 可选 可选 终止函数的指针数组的地址。此元素要求同时存在 DT_FINI_ARRAYSZ 元素。请参见初始化节和终止节
DT_INIT_ARRAYSZ 27 d_val 可选 可选 DT_INIT_ARRAY 数组的总大小(以字节为单位)。
DT_FINI_ARRAYSZ 28 d_val 可选 可选 DT_FINI_ARRAY 数组的总大小(以字节为单位)。
DT_RUNPATH 29 d_val 可选 可选 以空字符结尾的库搜索路径字符串的 DT_STRTAB 字符串表偏移。请参见运行时链接程序搜索的目录
DT_FLAGS 30 d_val 可选 可选 特定于此目标文件的标志值。请参见表 13-9
DT_ENCODING 32 未定义 未定义 未定义 大于或等于 DT_ENCODING、小于或等于 DT_LOOS 的动态标记值遵循 d_un 联合的解释规则。
DT_LOOS 0x6000000d 未定义 未定义 未定义 DT_LOOS - DT_HIOS 此范围内包含的值(包括这两个值)保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。
DT_HIOS 0x6ffff000 未定义 未定义 未定义 DT_LOOS - DT_HIOS 此范围内包含的值(包括这两个值)保留用于特定于操作系统的语义。所有这类值都遵循 d_un 联合的解释规则。
DT_LOPROC 0x70000000 未定义 未定义 未定义 这一区间的值是为处理器保留的。
DT_HIPROC 0x7fffffff 未定义 未定义 未定义 这一区间的值是为处理器保留的。

需要特别说明的几个字段:

  1. d_und_ptr 成员时,有以下两种情况:
    • 对于 d_tag = DT_PLTGOT、DT_INIT_ARRAY、DT_FINI_ARRAY 时,对于共享库和可执行文件都是虚拟地址;
    • 其余 d_tag 取值,对于共享库来说是文件偏移,对于可执行文件来说是虚拟地址
  2. 对于 DT_NEEDEDDT_SONAME 两个取值和 DT_SONAME 的关系,可参考《Linux 编程基础—3.3 SO-NAME》。

注意:在解析动态段时,计算 Elf64_Dyn 数组元素的个数时,不是像节 Section那样( 元素个数 = 节大小/每一项大小)。在动态段中,当数组 Elf64_Dyn 元素的值为 DT_NULL(0) 时,即表示数组最后一个元素(最后一个元素为全 0)。

4.3 动态加载

需要进行动态链接的可执行文件,有一个 PT_INTERP 类型的程序头项(实际上就是 .interp 节)。该节/段的内容是一个路径字符串,表示ELF解析器。当执行一个程序时,系统函数 exec(BA_OS) 会被调用,这个函数会去读取 PT_INTERP 段获取解析器。系统去初始化该解析器的进程镜像,把进程空间暂时借给解析器,然后解析器继续执行(通过解释程序文件段创建初始进程映像,解释程序负责从系统接收控制并为应用程序提供环境)。

关于解析器:

  • 一般是一个共享目标文件,且段内容位置不相关,一般系统会用 mmap 在动态区域为解析器创建段镜像。
  • 它也可以是独立的可执行文件,那系统就要按照此可执行文件的程序头来加载它。

1.
2. 将可执行文件依赖(使用)的共享文件(动态链接库)的 PT_LOAD 段到加载到进行空间
3. 将可执行文件、动态链接库进行重定位
4. 关闭可执行文件 的文件描述符
5. 把控制权交给程序,从可执行文件入口点开始执行

其中第 2 步,加载依赖的动态链接库,从哪里加载,加载哪些库?Windows 下,因为有导入表的存在,这些都很方便。但是在 Linux 下没有导入表,必须使用依靠 PT_DYNAMIC 动态链接段(该段内容对应于 .dynamic 节)来找到相关的信息。

4.4 GOT、PLT

上一节讲了动态加载可执行文件的简单过程,对于可执行文件中,引用的外部函数,同样需要进行动态加载。ELF 可执行文件动态加载外部函数必须借助 PLT、GOT 表(Windows 借助函数导入表)。

要想获取外部函数的地址:

  1. 一个用来存储外部函数地址的数据段(GOT)
  2. 一段用来加载外部函数的代码(PLT)

PE 文件中,导入表中内容在函数导入前后不一样,当函数被引用后,导入表中对应的位置就是函数的地址。在 ELF 文件中没有导入表,使用 PLT、GOT 表来使用导入函数。

  • GOT(Global Offset Table),每一个表项条目用来存储导入函数或全局变量的地址。GOT 表一般被拆成了两个节(Section),不需要延迟绑定,用于存储全局变量,加载到内存中只需要被读取的 .got,以及存储外部函数地址的 .got.plt
  • PLT(Procedure Linkage Table),用来获取导入函数的地址,然后将地址填充到 GOT 表中。PLT 表每一项都是一小段代码,对应于本运行模块要引用的一个全局函数。程序对某个函数的访问都被调整为对 PLT 入口的访问。一个 PLT 入口项对应一个 GOT 项,执行函数实际上就是跳转到相应 GOT 项存储的地址。

静态分析

举例代码:

1
2
3
4
5
6
7
#include <stdio.h>

int main(int argc, char* argv[], char* enpv[])
{
printf("Hello world.\n");
return 0;
}

编译成 x64 代码:

  1. 查看 printf() 函数。可以看到函数的调用被汇编成 call puts@plt

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    [root@centos-7 program]# objdump -d -M intel 1022_printf.exe 

    Disassembly of section .text:
    ...
    000000000040052d <main>:
    40052d: 55 push rbp
    40052e: 48 89 e5 mov rbp,rsp
    400531: 48 83 ec 20 sub rsp,0x20
    400535: 89 7d fc mov DWORD PTR [rbp-0x4],edi
    400538: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi
    40053c: 48 89 55 e8 mov QWORD PTR [rbp-0x18],rdx
    400540: 48 8d 3d a9 00 00 00 lea rdi,[rip+0xa9] // 4005f0 <__dso_handle+0x8>
    400547: e8 c4 fe ff ff call 400410 <puts@plt> // printf() 函数
    40054c: b8 00 00 00 00 mov eax,0x0
    400551: c9 leave
    400552: c3 ret
    400553: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
    40055a: 00 00 00
    40055d: 0f 1f 00 nop DWORD PTR [rax]
  2. 继续查看 puts@plt(0x400410) 地址处的代码。

    1
    2
    3
    4
    5
    6
    7
    8
    [root@centos-7 program]# objdump -d --start-address=0x400410 -M intel 1022_printf.exe 

    Disassembly of section .plt:

    0000000000400410 <puts@plt>:
    400410: ff 25 02 0c 20 00 jmp QWORD PTR [rip+0x200c02] // 601018 <puts@GLIBC_2.2.5>
    400416: 68 00 00 00 00 push 0x0
    40041b: e9 e0 ff ff ff jmp 400400 <.plt>

    可以看到,代码位于 .plt 节。

  3. 反汇编整个 .plt 节的代码(-d 反汇编,-j 指定节)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    [root@centos-7 program]# objdump -d -j .plt -M intel 1022_printf.exe 

    Disassembly of section .plt:

    0000000000400400 <.plt>:
    400400: ff 35 02 0c 20 00 push QWORD PTR [rip+0x200c02] // 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
    400406: ff 25 04 0c 20 00 jmp QWORD PTR [rip+0x200c04] // 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
    40040c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]

    0000000000400410 <puts@plt>:
    400410: ff 25 02 0c 20 00 jmp QWORD PTR [rip+0x200c02] // 601018 <puts@GLIBC_2.2.5>
    400416: 68 00 00 00 00 push 0x0
    40041b: e9 e0 ff ff ff jmp 400400 <.plt>

    0000000000400420 <__libc_start_main@plt>:
    400420: ff 25 fa 0b 20 00 jmp QWORD PTR [rip+0x200bfa] // 601020 <__libc_start_main@GLIBC_2.2.5>
    400426: 68 01 00 00 00 push 0x1
    40042b: e9 d0 ff ff ff jmp 400400 <.plt>

    0000000000400430 <__gmon_start__@plt>:
    400430: ff 25 f2 0b 20 00 jmp QWORD PTR [rip+0x200bf2] // 601028 <__gmon_start__>
    400436: 68 02 00 00 00 push 0x2
    40043b: e9 c0 ff ff ff jmp 400400 <.plt>

    可以看到,.plt 节的特征:

    • 表项,每个引用的函数都被反汇编为一个片段(或者叫做表项),也叫做 stub
    • 公共 common 表项,需要特别注意的是第一个函数表项(名称为 .plt),是一个公共的表项,会被后面的每一个其他表项调用。
  4. 继续分析 PLT 中 0x400410<puts@plt>

    1
    2
    3
    4
    0000000000400410 <puts@plt>:
    400410: ff 25 02 0c 20 00 jmp QWORD PTR [rip+0x200c02] // 601018 <puts@GLIBC_2.2.5>
    400416: 68 00 00 00 00 push 0x0
    40041b: e9 e0 ff ff ff jmp 400400 <.plt>

    可以看到,跳转到 0x601018 = 0x200C02 + 0x400416注意:x86-64 中,使用 RIP-relative 寻址,即编码x = 跳转目标 - 返回地址。并不是 0x200c02 + 0x400410

  5. 查看目标地址 0x601018 的内容。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@centos-7 program]# objdump -D --start-address=0x601018 -M intel 1022_printf.exe 

    Disassembly of section .got.plt:

    0000000000601018 <_GLOBAL_OFFSET_TABLE_+0x18>:
    601018: 16 (bad)
    601019: 04 40 add al,0x40
    60101b: 00 00 add BYTE PTR [rax],al
    60101d: 00 00 add BYTE PTR [rax],al
    ...

    可以看到目标地址位于 .got.plt 节,属于数据区,反汇编出来的内容是乱码。则查看整个 .got.plt 区域内容:

    1
    2
    3
    4
    5
    6
    [root@centos-7 program]# objdump -s -j .got.plt -M intel 1022_printf.exe 

    Contents of section .got.plt:
    601000 280e6000 00000000 00000000 00000000 (.`.............
    601010 00000000 00000000 16044000 00000000 ..........@.....
    601020 26044000 00000000 36044000 00000000 &.@.....6.@.....

    可以看到,地址 0x601018 里面存储的值为 0x400416。在上面步骤 4,400410: ff 25 02 0c 20 00,指令 ff 25 为间接跳转,即取地址 0x601018 存储的值 0x400416,并跳转。

    目标地址为 0x400416,则又跳转回去 .plt 节中,且刚好是 400410: ff 25 02 0c 20 00 的下一条指令。

  6. 继续分析 <puts@plt>400416 地址处的代码。

    1
    2
    3
    4
    0000000000400410 <puts@plt>:
    400410: ff 25 02 0c 20 00 jmp QWORD PTR [rip+0x200c02] // 601018 <puts@GLIBC_2.2.5>
    400416: 68 00 00 00 00 push 0x0
    40041b: e9 e0 ff ff ff jmp 400400 <.plt>

    这里 push 的值(参考链接):

    • x86-64,为目标函数在 .rela.plt 节中所在条目的索引值
    • x86,为目标函数在 .rela.plt 节中的偏移值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    [root@centos-7 program]# readelf -r 1022_printf.exe 

    重定位节 '.rela.dyn' 位于偏移量 0x380 含有 1 个条目:
    偏移量 信息 类型 符号值 符号名称 + 加数
    000000600ff8 000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

    重定位节 '.rela.plt' 位于偏移量 0x398 含有 3 个条目:
    偏移量 信息 类型 符号值 符号名称 + 加数
    000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0
    000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main@GLIBC_2.2.5 + 0
    000000601028 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0

    最后跳转到 common 公共表项 jmp 400400 <.plt>

  7. 公共表项分析。

    1
    2
    3
    4
    0000000000400400 <.plt>:
    400400: ff 35 02 0c 20 00 push QWORD PTR [rip+0x200c02] // 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
    400406: ff 25 04 0c 20 00 jmp QWORD PTR [rip+0x200c04] // 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
    40040c: 0f 1f 40 00 nop DWORD PTR [rax+0x0]

    这里入栈的地址为 .got.plt[1],然后间接跳转 .got.plt[2]

    • .got.plt[0]:本 ELF 动态段(.dynamic)的装载地址 。
    • .got.plt[1]:本 ELF 的 link_map 数据结构描述符地址。
    • .got.plt[2]_dl_runtime_resolve 函数的地址。

    参考《PLT&GOT》。

  8. 分析地址 0x601010,即 .got.plt[2]

    1
    2
    3
    4
    5
    6
    [root@centos-7 program]# objdump -s -j .got.plt -M intel 1022_printf.exe 

    Contents of section .got.plt:
    601000 280e6000 00000000 00000000 00000000 (.`.............
    601010 00000000 00000000 16044000 00000000 ..........@.....
    601020 26044000 00000000 36044000 00000000 &.@.....6.@.....

    可以看到,0x601008(.got.plt[1])0x601010(.got.plt[2]) 目前都是 0。经过动态分析,.got.plt[1] = link_map.got.plt[2] = _dl_runtime_resolve,即跳转到 _dl_runtime_resolve 函数。

  9. 后面将进行动态分析 _dl_runtime_resolve 函数。

总结:

  • 每个 PLT 入口项对应一个 GOT 项,执行函数实际上就是跳转到相应 GOT 项存储的地址 ,该 GOT 项初始值为 PLTn项中的 push 指令地址(即 jmp 的下一条指令,所以第 1 次跳转没有任何作用),待符号解析完成后存放符号的真正地址。

第一次调用外部函数:

16.jpeg

之后再次调用:

17.jpeg

动态调试

  1. 搭建远程调试。

    参考IDA远程动态调试(linux & Windows)搭建,参考由一道逆向题而引发,IDA调试ELF文件需要关闭 Linux 防火墙

    动态调试常用命令:

    IDA快捷键 功能
    F7 单步步进,遇到call/指令跟进
    F8 单步步过,遇到call指令不跟进
    F4 运行到光标所在的行
    Ctrl + F7 知道该函数返回时才停止
    F9 运行程序
    Ctrl+F2 终止一个正在运行的进程
    F2 设置断点
  2. 找到 call puts 函数,动态跟踪。

    View—Open subviews—Functions(shift+F3)—main:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    .text:000000000040052D
    .text:000000000040052D // Attributes: bp-based frame
    .text:000000000040052D
    .text:000000000040052D // int __cdecl main(int argc, const char **argv, const char **envp)
    .text:000000000040052D public main
    .text:000000000040052D main proc near // DATA XREF: _start+1D↑o
    .text:000000000040052D
    .text:000000000040052D var_18= qword ptr -18h
    .text:000000000040052D var_10= qword ptr -10h
    .text:000000000040052D var_4= dword ptr -4
    .text:000000000040052D
    .text:000000000040052D // __unwind {
    .text:000000000040052D push rbp
    .text:000000000040052E mov rbp, rsp
    .text:0000000000400531 sub rsp, 20h
    .text:0000000000400535 mov [rbp+var_4], edi
    .text:0000000000400538 mov [rbp+var_10], rsi
    .text:000000000040053C mov [rbp+var_18], rdx
    .text:0000000000400540 lea rdi, aHelloWorld // "Hello world."
    .text:0000000000400547 call sub_400410 // call <puts@plt>
    .text:000000000040054C mov eax, 0
    .text:0000000000400551 leave
    .text:0000000000400552 retn
    .text:0000000000400552 ; } // starts at 40052D
    .text:0000000000400552 main endp

    在地址 0x400547 设置断点(F2),然后运行(F9),程序断在断点处。接着单步步进(F7)如下图,进入到 PLT 的 <puts@plt> 处。在左下角数据区域可以看到地址 0x601018 里面存储的值是 0x400416(即又回到 <puts@plt> 的下一条指令)。

    18.png

    继续 F7 步进到 common 公共表项。

    19.png

    可以看到,.got.plt[1] == link_map == 0x7FDB'6BFD3150.got.plt[2] == _dl_runtime_resolve == 0x7FDB'6BDC6A30

  3. 动态分析 _dl_runtime_resolve 函数。

    在虚拟机中使用 ldd --version 查看使用的 glibc 版本(对应于 /lib64/libc-2.17.so),和 IDA 反汇编结果一样都是 2.17 稳定版。但是反汇编出来的代码和 version >= 2.27 的代码一致。(glibc 源码下载链接

    1
    2
    3
    4
    5
    6
    [root@centos-7 program]# ldd --version
    ldd (GNU libc) 2.17
    Copyright (C) 2012 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    由 Roland McGrath 和 Ulrich Drepper 编写。

    glibc >= 2.27 时,_dl_runtime_resolve 函数的源代码在 glibc/sysdeps/x86_64/dl-trampoline.h 中进行定义,在 glibc/sysdeps/x86_64/dl-trampoline.S 中,如果使用了 USE_XSAVEC 则会使用 _dl_runtime_resolve_xsavec 函数,如下:

    1
    2
    3
    4
    5
    6
    7
    #define USE_XSAVEC
    #define STATE_SAVE_ALIGNMENT 64
    #define _dl_runtime_resolve _dl_runtime_resolve_xsavec // 使用 _dl_runtime_resolve_xsavec 函数
    #include "dl-trampoline.h"
    #undef _dl_runtime_resolve
    #undef USE_XSAVEC
    #undef STATE_SAVE_ALIGNMENT

    所以我们以 glibc >= 2.27 代码进行分析。源代码是基于 AT&T 风格的汇编代码,可读性较差,我习惯 Intel 风格 反汇编,所以直接使用了 IDA 反汇编出来的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    ld_2.17.so:00007FDB6BDC6A30 _dl_runtime_resolve_xsavec proc near
    ld_2.17.so:00007FDB6BDC6A30
    ld_2.17.so:00007FDB6BDC6A30 var_18= qword ptr -18h
    ld_2.17.so:00007FDB6BDC6A30 var_8= qword ptr -8
    ld_2.17.so:00007FDB6BDC6A30 arg_0= qword ptr 8
    ld_2.17.so:00007FDB6BDC6A30 arg_8= qword ptr 10h
    ld_2.17.so:00007FDB6BDC6A30 arg_10= qword ptr 18h
    ld_2.17.so:00007FDB6BDC6A30 arg_18= qword ptr 20h
    ld_2.17.so:00007FDB6BDC6A30 arg_20= qword ptr 28h
    ld_2.17.so:00007FDB6BDC6A30 arg_30= byte ptr 38h
    ld_2.17.so:00007FDB6BDC6A30 arg_240= qword ptr 248h
    ld_2.17.so:00007FDB6BDC6A30 arg_248= qword ptr 250h
    ld_2.17.so:00007FDB6BDC6A30 arg_250= qword ptr 258h
    ld_2.17.so:00007FDB6BDC6A30 arg_258= qword ptr 260h
    ld_2.17.so:00007FDB6BDC6A30 arg_260= qword ptr 268h
    ld_2.17.so:00007FDB6BDC6A30 arg_268= qword ptr 270h
    ld_2.17.so:00007FDB6BDC6A30
    ld_2.17.so:00007FDB6BDC6A30 push rbx // DATA XREF: .got.plt:off_601010↑o
    ld_2.17.so:00007FDB6BDC6A31 mov rbx, rsp
    ld_2.17.so:00007FDB6BDC6A34 and rsp, 0FFFFFFFFFFFFFFC0h
    ld_2.17.so:00007FDB6BDC6A38 sub rsp, cs:qword_7FDB6BFD1CB0
    ld_2.17.so:00007FDB6BDC6A3F mov [rsp+8+var_8], rax
    ld_2.17.so:00007FDB6BDC6A43 mov [rsp+8], rcx
    ld_2.17.so:00007FDB6BDC6A48 mov [rsp+8+arg_0], rdx
    ld_2.17.so:00007FDB6BDC6A4D mov [rsp+8+arg_8], rsi
    ld_2.17.so:00007FDB6BDC6A52 mov [rsp+8+arg_10], rdi
    ld_2.17.so:00007FDB6BDC6A57 mov [rsp+8+arg_18], r8
    ld_2.17.so:00007FDB6BDC6A5C mov [rsp+8+arg_20], r9
    ld_2.17.so:00007FDB6BDC6A61 mov eax, 0EEh
    ld_2.17.so:00007FDB6BDC6A66 xor edx, edx
    ld_2.17.so:00007FDB6BDC6A68 mov [rsp+8+arg_240], rdx
    ld_2.17.so:00007FDB6BDC6A70 mov [rsp+8+arg_248], rdx
    ld_2.17.so:00007FDB6BDC6A78 mov [rsp+8+arg_250], rdx
    ld_2.17.so:00007FDB6BDC6A80 mov [rsp+8+arg_258], rdx
    ld_2.17.so:00007FDB6BDC6A88 mov [rsp+8+arg_260], rdx
    ld_2.17.so:00007FDB6BDC6A90 mov [rsp+8+arg_268], rdx
    ld_2.17.so:00007FDB6BDC6A98 xsavec [rsp+8+arg_30]
    ld_2.17.so:00007FDB6BDC6A9D mov rsi, [rbx+10h] // 参数二,push 的索引值
    ld_2.17.so:00007FDB6BDC6AA1 mov rdi, [rbx+8] // 参数一,.got.plt[1] == link_map
    ld_2.17.so:00007FDB6BDC6AA5 call near ptr _dl_fixup // 调用 _dl_fixup 函数
    ld_2.17.so:00007FDB6BDC6AAA mov r11, rax
    ld_2.17.so:00007FDB6BDC6AAD mov eax, 0EEh
    ld_2.17.so:00007FDB6BDC6AB2 xor edx, edx
    ld_2.17.so:00007FDB6BDC6AB4 xrstor [rsp+8+arg_30]
    ld_2.17.so:00007FDB6BDC6AB9 mov r9, [rsp+8+arg_20]
    ld_2.17.so:00007FDB6BDC6ABE mov r8, [rsp+8+arg_18]
    ld_2.17.so:00007FDB6BDC6AC3 mov rdi, [rsp+8+arg_10]
    ld_2.17.so:00007FDB6BDC6AC8 mov rsi, [rsp+8+arg_8]
    ld_2.17.so:00007FDB6BDC6ACD mov rdx, [rsp+8+arg_0]
    ld_2.17.so:00007FDB6BDC6AD2 mov rcx, [rsp+8]
    ld_2.17.so:00007FDB6BDC6AD7 mov rax, [rsp+8+var_8]
    ld_2.17.so:00007FDB6BDC6ADB mov rsp, rbx
    ld_2.17.so:00007FDB6BDC6ADE mov rbx, [rsp+18h+var_18]
    ld_2.17.so:00007FDB6BDC6AE2 add rsp, 18h
    ld_2.17.so:00007FDB6BDC6AE6 bnd jmp r11
    ld_2.17.so:00007FDB6BDC6AE6 _dl_runtime_resolve_xsavec endp

    可以看到:

    1、主要调用 _dl_fixup 函数,带有两个参数,即参数一为 .got.plt[1] == link_map,参数二为 push 的索引值。

    2、堆栈恢复后,继续执行 bnd jmp r11r11 中的目标地址是函数 _dl_fixup 的返回值。

  4. 关于函数 _dl_fixup 分析,参考Linux pwn入门教程(10)——针对函数重定位流程的几种攻击

参考计算机系统篇之链接(14):.plt、.plt.got、.got 和 .got.plt sections 之间的区别

section 所在 segment section 属性 用途
.plt 代码段 RE(可读,可执行) .plt section 实际就是通常所说的过程链接表(Procedure Linkage Table, PLT)
.plt.got 代码段 RE .plt.got section 用于存放 __cxa_finalize 函数对应的 PLT 条目
.got 数据段 RW(可读,可写) .got section 中可以用于存放全局变量的地址;.got section 中也可以用于存放不需要延迟绑定的函数的地址。
.got.plt 数据段 RW .got.plt section 用于存放需要延迟绑定的函数的地址

参考:

5 模块加载

6 解析代码

13.jpeg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
#include <stdio.h>
#include <elf.h>
#include <stdbool.h>

#define ulong64 unsigned long long
#define uchar unsigned char
#define uint unsigned int

bool open_file(void ** file_buffer)
{
FILE* fp = NULL;
char* file_name = "/home/alvin/program/test.exe";
void* temp_file_buffer = NULL;
ulong64 file_size = 0;

if((fp = fopen(file_name, "rb")) == NULL)
{
printf("[*] Failed open target file.\n");
return false;
}

fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);

if((temp_file_buffer = malloc(file_size)) == NULL)
{
printf("[*] Failed malloc the buffer for file.\n");
fclose(fp);
return false;
}

if(fread(temp_file_buffer, file_size, 1, fp) != 1)
{
printf("[*] Failed to read the file.\n");
free(temp_file_buffer);
fclose(fp);
return false;
}

*file_buffer = temp_file_buffer;
fclose(fp);

return true;
}


void* print_file_header(bool b_print)
{
void* file_buffer = NULL;
Elf64_Ehdr* pfile_header = NULL;
uchar* ptemp = NULL;

if(open_file(&file_buffer) == false || (file_buffer == NULL))
{
printf("[*] Failed open the file.\n");
return NULL;
}


if(*(unsigned int*)file_buffer != 0x464c457f)
{
printf("[*] This is not a ELF file.\n");
free(file_buffer);
return NULL;
}

ptemp = (uchar*)((ulong64)file_buffer + 5);
switch(*ptemp)
{
case ELFCLASS32:
pfile_header = (Elf32_Ehdr*)file_buffer;
break;
case ELFCLASS64:
pfile_header = (Elf64_Ehdr*)file_buffer;
break;
default:
printf("[*] This is not a ELF file.\n");
free(file_buffer);
return NULL;
}

if(!b_print) return (void*)pfile_header;

printf("************************************************************************\n");
printf("------File Header(Elf64_Ehdr)------\n");
printf("[*] pfile_header->e_machine = %lld\n", pfile_header->e_machine);
printf("[*] pfile_header->e_entry = %lld\n", pfile_header->e_entry);
printf("[*] pfile_header->e_phoff = %lld\n", pfile_header->e_phoff);
printf("[*] pfile_header->e_shoff = %lld\n", pfile_header->e_shoff);
printf("[*] pfile_header->e_phnum = %lld\n", pfile_header->e_phnum);
printf("[*] pfile_header->e_shnum = %lld\n", pfile_header->e_shnum);
printf("[*] pfile_header->e_shstrndx = %lld\n", pfile_header->e_shstrndx);
printf("************************************************************************\n");

return (void*)pfile_header;
}


void* print_section_header_table(void* pfile_header_buffer, bool b_print)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Shdr* psection_header_table_entry = NULL;
Elf64_Shdr* psec_header_name_entry = NULL;
uint section_table_entry_num = 0;
char* psec_name_str = NULL;


if(!print_file_header(0))
{
printf("[*] Failed printf the file section header table.\n");
free(pfile_header_buffer);
return NULL;
}

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);

if(!b_print) return (void*)psection_header_table_entry;

psec_header_name_entry = (Elf64_Shdr*)&psection_header_table_entry[pfile_header->e_shstrndx];
section_table_entry_num = pfile_header->e_shnum;
psec_name_str = (char*)((ulong64)pfile_header + psec_header_name_entry->sh_offset);

uint i = 0;
printf("Section Headers:\r\n");
printf(" [Nr] %-18s %-12s %-16s %-16s ", "Name", "Type", "Address", "Offset");
printf("%-16s %-10s %-5s %-5s %-5s %-5s\r\n", "Size", "EntSize", "Flags", "Link", "Info", "Align");
for(; i < section_table_entry_num; i++)
{
printf(" [%2d] %-18s ", i, (char*)(psec_name_str + psection_header_table_entry[i].sh_name));
switch(psection_header_table_entry[i].sh_type)
{
case SHT_NULL:
printf("%-12s ", "NULL");break;
case SHT_PROGBITS:
printf("%-12s ", "PROGBITS");break;
case SHT_SYMTAB:
printf("%-12s ", "SYMTAB");break;
case SHT_STRTAB:
printf("%-12s ", "STRTAB");break;
case SHT_RELA:
printf("%-12s ", "RELA");break;
case SHT_HASH:
printf("%-12s ", "GNU_HASH");break;
case SHT_DYNAMIC:
printf("%-12s ", "DYNAMIC");break;
case SHT_NOTE:
printf("%-12s ", "NOTE");break;
case SHT_NOBITS:
printf("%-12s ", "NOBITS");break;
case SHT_REL:
printf("%-12s ", "REL");break;
case SHT_SHLIB:
printf("%-12s ", "SHLIB");break;
case SHT_DYNSYM:
printf("%-12s ", "DYNSYM");break;
case SHT_INIT_ARRAY:
printf("%-12s ", "INIT_ARRY");break;
case SHT_FINI_ARRAY:
printf("%-12s ", "FINI_ARRY");break;
case SHT_PREINIT_ARRAY:
printf("%-12s ", "PREINIT_ARRAY");break;
case SHT_GNU_HASH:
printf("%-12s ", "GNU_HASH");break;
case SHT_GNU_ATTRIBUTES:
printf("%-12s ", "GNU_ATTRIBUTES");break;
case SHT_GNU_LIBLIST:
printf("%-12s ", "GNU_LIBLIST");break;
case SHT_GNU_verdef:
printf("%-12s ", "GNU_verdef");break;
case SHT_GNU_verneed:
printf("%-12s ", "GNU_verneed");break;
case SHT_GNU_versym:
printf("%-12s ", "GNU_versym");break;
default:
printf("%-12s ", "NONE");break;
}
printf("%016llx %016llx",psection_header_table_entry[i].sh_addr, psection_header_table_entry[i].sh_offset);
printf(" %016llx %016llx ", psection_header_table_entry[i].sh_size, psection_header_table_entry[i].sh_entsize);
switch (psection_header_table_entry[i].sh_flags) {
case 0:
printf("%4s %4u %4u %4lu\r\n", "", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 1:
printf("%4s %4u %4u %4lu\r\n", "W", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 2:
printf("%4s %4u %4u %4lu\r\n",
"A", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 4:
printf("%4s %4u %4u %4lu\r\n",
"X", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 3:
printf("%4s %4u %4u %4lu\r\n", "WA", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 5:
printf("%4s %4u %4u %4lu\r\n", "WX", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 6:
printf("%4s %4u %4u %4lu\r\n",
"AX", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case 7:
printf("%4s %4u %4u %4lu\r\n", "WAX", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
case SHF_MASKPROC:
printf("%4s %4u %4u %4lu\r\n", "MS", psection_header_table_entry[i].sh_link, psection_header_table_entry[i].sh_info, psection_header_table_entry[i].sh_addralign);
break;
default:
printf("NONE\r\n");
break;
}
}
printf("Key to Flags:\r\n");
printf(" W (write), A (alloc), X (execute), M (merge), S (strings), l (large)\r\n");
printf(" I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)\r\n");
printf(" O (extra OS processing required) o (OS specific), p (processor specific)\r\n");

return (void*)psection_header_table_entry;
}

bool print_symbol_table(void* pfile_header_buffer)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Shdr* psection_header_table_entry = NULL;
Elf64_Shdr* psec_symbol_entry = NULL;
Elf64_Shdr* psec_header_name_entry = NULL;
Elf64_Shdr* psec_sym_name_entry = NULL;
Elf64_Sym* psym_entry = NULL;
char* psec_name_str = NULL;
char* psym_name_str = NULL;

if(!print_file_header(0))
{
printf("[*] Failed printf the file section header table.\n");
free(pfile_header_buffer);
return NULL;
}

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);
psec_header_name_entry = (Elf64_Shdr*)&psection_header_table_entry[pfile_header->e_shstrndx];
psec_name_str = (char*)((ulong64)pfile_header + psec_header_name_entry->sh_offset);

int i = 0;
printf("**********************************************************************************\n");
for(; i < pfile_header->e_shnum; i++)
{
if(!strcmp(psection_header_table_entry[i].sh_name + psec_name_str, ".dynsym") \
|| !strcmp(psection_header_table_entry[i].sh_name + psec_name_str, ".symtab"))
{
psym_entry = (Elf64_Sym*)((ulong64)pfile_header + psection_header_table_entry[i].sh_offset);
uint index = psection_header_table_entry[i].sh_link;
uint usym_entry_num = psection_header_table_entry[i].sh_size / psection_header_table_entry[i].sh_entsize;
psec_sym_name_entry = (Elf64_Shdr*)&psection_header_table_entry[index];
psym_name_str = (char*)((ulong64)pfile_header + psec_sym_name_entry->sh_offset);

printf("Symbol table '%s' contains %d entries:\r\n", psection_header_table_entry[i].sh_name + psec_name_str, usym_entry_num);
printf("%7s %-8s %s %s %s %s %s %s\r\n",\
"Num:", "Value", "Size", "Type", "Bind", "Vis", "Ndx", "Name");
int j = 0;
for(; j < usym_entry_num; j++)
{
uchar usym_type = ELF32_ST_TYPE(psym_entry[j].st_info);
uchar usym_bind = ELF32_ST_BIND(psym_entry[j].st_info);
printf(" %4d: %016llx %-6llx", j, psym_entry[j].st_value, psym_entry[j].st_size);
switch(usym_type)
{
case STT_NOTYPE:
printf("%-8s", "NOTYPE"); break;
case STT_OBJECT:
printf("%-8s", "OBJECT"); break;
case STT_FUNC:
printf("%-8s", "FUNC"); break;
case STT_SECTION:
printf("%-8s", "SECTION"); break;
case STT_FILE:
printf("%-8s", "FILE"); break;
default:
break;
}
switch(usym_bind)
{
case STB_LOCAL:
printf("%-8s", "LOCAL"); break;
case STB_GLOBAL:
printf("%-8s", "GLOBAL"); break;
case STB_WEAK:
printf("%-8s", "WEAK"); break;
default:
break;
}
printf("%-8d", psym_entry[j].st_other);
switch(psym_entry[j].st_shndx)
{
case SHN_UNDEF:
printf("%s %s\r\n", "UND", psym_entry[j].st_name + psym_name_str); break;
case SHN_ABS:
printf("%s %s\r\n", "ABS", psym_entry[j].st_name + psym_name_str); break;
case SHN_COMMON:
printf("%s %s\r\n", "COM", psym_entry[j].st_name + psym_name_str); break;
default:
printf("%-3d %s\r\n", psym_entry[j].st_shndx, psym_entry[j].st_name + psym_name_str); break;
}
}
}
}
printf("**********************************************************************************\n");

return NULL;
}

ulong64 get_image_base(void* pfile_header_buffer)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Shdr* psection_header_table_entry;
char* psec_name_str = NULL;
ulong64 uimage_base = 0;

if(!print_file_header(0))
{
printf("[*] Failed get image base.\n");
free(pfile_header_buffer);
return -1;
}

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);
psec_name_str = (char*)((ulong64)pfile_header + psection_header_table_entry[pfile_header->e_shstrndx].sh_offset);

int i = 0;
for(; i < pfile_header->e_shnum; i++)
{
if(!strcmp(psection_header_table_entry[i].sh_name + psec_name_str, ".interp"))
{
uimage_base = psection_header_table_entry[i].sh_addr - psection_header_table_entry[i].sh_offset;
}
}
//printf("[*] the image base = 0x%llx\n", uimage_base);
return uimage_base;
}

bool print_SHT_DYNAMIC_section(void* pfile_header_buffer)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Shdr* psection_header_table_entry = NULL;
Elf64_Shdr* psec_header_name_entry = NULL;
Elf64_Dyn* pdynamic_entry = NULL;
char* pdyn_name_str = NULL;
char* psec_name_str = NULL;
ulong64 uimage_base = 0;

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);
psec_header_name_entry = (Elf64_Shdr*)&psection_header_table_entry[pfile_header->e_shstrndx];
psec_name_str = (char*)((ulong64)pfile_header + psec_header_name_entry->sh_offset);

ulong64 delta = 0;
int i = 0;
uimage_base = get_image_base(pfile_header_buffer);

for(; i < pfile_header->e_shnum; i++)
{
if(psection_header_table_entry[i].sh_type == SHT_DYNAMIC)
{
pdynamic_entry = (Elf64_Dyn*)((ulong64)pfile_header + psection_header_table_entry[i].sh_offset);
uint uentry_num = psection_header_table_entry[i].sh_size / psection_header_table_entry[i].sh_entsize;
uint udyn_num = 0;

while(pdynamic_entry[udyn_num].d_tag)
{
udyn_num++;
}
udyn_num += 1;

printf("\nDynamic section at offset 0x%llx contains %d entries:\r\n", psection_header_table_entry[i].sh_offset, udyn_num);
printf("%-16s %-16s %-16s\r\n", "Tag", "Type", "Name/Value");

int j = 0;
for(j = 0; j < udyn_num; j++)
{
if(!strcmp(psection_header_table_entry[j].sh_name + psec_name_str, ".dynstr"))
printf(".dynstr = 0x%llx\n", psection_header_table_entry[j].sh_offset);

if(pdynamic_entry[j].d_tag == DT_STRTAB)
pdyn_name_str = (char*)((ulong64)pfile_header + pdynamic_entry[j].d_un.d_ptr);
}

for(j = 0; j < udyn_num; j++)
{
switch(pdynamic_entry[j].d_tag)
{
case DT_NULL:
printf("%016llx %-16s %-16s\r\n", pdynamic_entry[j].d_tag, "DT_NULL", "0x0"); break;
case DT_NEEDED:
if(pfile_header->e_type == ET_EXEC) delta = pdynamic_entry[j].d_un.d_val - uimage_base;
else delta = pdynamic_entry[j].d_un.d_val;
printf("%016llx %-16s %-16s\r\n", pdynamic_entry[j].d_tag, "DT_NEEDED", pdyn_name_str + delta); break;
case DT_PLTRELSZ:
printf("%016llx %-16s %-lld(bytes)\r\n", pdynamic_entry[j].d_tag, "DT_PLTRELSZ", pdynamic_entry[j].d_un.d_val); break;
case DT_PLTGOT:
printf("%016llx %-16s 0x%-llx(GOT)\r\n", pdynamic_entry[j].d_tag, "DT_PLTGOT", pdynamic_entry[j].d_un.d_ptr); break;
case DT_HASH:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_HASH", pdynamic_entry[j].d_un.d_ptr); break;
case DT_STRTAB:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_STRTAB", pdynamic_entry[j].d_un.d_ptr); break;
case DT_SYMTAB:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_SYMTAB", pdynamic_entry[j].d_un.d_ptr); break;
case DT_RELA:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RELA", pdynamic_entry[j].d_un.d_ptr); break;
case DT_RELASZ:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RELASZ", pdynamic_entry[j].d_un.d_val); break;
case DT_RELAENT:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RELAENT", pdynamic_entry[j].d_un.d_val); break;
case DT_SYMENT:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_SYMENT", pdynamic_entry[j].d_un.d_val); break;
case DT_INIT:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_INIT", pdynamic_entry[j].d_un.d_ptr); break;
case DT_FINI:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_FINI", pdynamic_entry[j].d_un.d_ptr); break;
case DT_SONAME:
if(pfile_header->e_type == ET_EXEC) delta = pdynamic_entry[j].d_un.d_val - uimage_base;
else delta = pdynamic_entry[j].d_un.d_val;
printf("%016llx %-16s %-16s\r\n", pdynamic_entry[j].d_tag, "DT_SONAME", pdyn_name_str + delta); break;
case DT_RPATH:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RPATH", pdynamic_entry[j].d_un.d_val); break;
case DT_REL:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_REL", pdynamic_entry[j].d_un.d_ptr); break;
case DT_RELSZ:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RELSZ", pdynamic_entry[j].d_un.d_val); break;
case DT_RELENT:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_RELENT", pdynamic_entry[j].d_un.d_val); break;
case DT_PLTREL:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_PLTREL", pdynamic_entry[j].d_un.d_val); break;
case DT_DEBUG:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_DEBUG", pdynamic_entry[j].d_un.d_ptr); break;
case DT_JMPREL:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_JMPREL", pdynamic_entry[j].d_un.d_ptr); break;
case DT_INIT_ARRAY:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_INIT_ARRAY", pdynamic_entry[j].d_un.d_ptr); break;
case DT_FINI_ARRAY:
printf("%016llx %-16s 0x%-llx\r\n", pdynamic_entry[j].d_tag, "DT_FINI_ARRAY", pdynamic_entry[j].d_un.d_ptr); break;
default:
printf("\r\n");
}/* switch */
}/* for(j)*/
}/* if */
}/* for() */


return true;
}

bool print_relocation_table(void* pfile_header_buffer)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Shdr* psection_header_table_entry = NULL;
Elf64_Shdr* psec_rela_usesym_entry = NULL;
Elf64_Shdr* psec_sym_usename_entry = NULL;
Elf64_Rela* prela_entry = NULL;
Elf64_Rel* prel_entry = NULL;
Elf64_Sym* prela_usesym_entry = NULL;
char* psec_name_str = NULL;
char* psym_name_str = NULL;

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);
psec_name_str = (char*)((ulong64)pfile_header + psection_header_table_entry[pfile_header->e_shstrndx].sh_offset);

int i = 0;
printf("**********************************************************************************\n");
for(; i < pfile_header->e_shnum; i++)
{
if(!strncmp(psection_header_table_entry[i].sh_name + psec_name_str, ".rel", 4))
{
uint urela_num = psection_header_table_entry[i].sh_size / psection_header_table_entry[i].sh_entsize;

uint urela_usesym_index = psection_header_table_entry[i].sh_link;
psec_rela_usesym_entry = (Elf64_Shdr*)&psection_header_table_entry[urela_usesym_index];
prela_usesym_entry = (Elf64_Sym*)((ulong64)pfile_header + psec_rela_usesym_entry->sh_offset);

psec_sym_usename_entry = (Elf64_Shdr*)&psection_header_table_entry[psec_rela_usesym_entry->sh_link];
psym_name_str = (char*)((ulong64)pfile_header + psec_sym_usename_entry->sh_offset);

uchar uchoise = 0;

printf("\nRelocation section '%s' at offset 0x%llx contains %lld entries:\r\n", \
psection_header_table_entry[i].sh_name + psec_name_str, psection_header_table_entry[i].sh_offset, urela_num);
printf("%-16s %-16s %-16s %-16s %-16s\r\n", "Offset", "Info", "Type", "Sym.Value", "Sym.Name + Addend");

if(psection_header_table_entry[i].sh_type == SHT_REL)
{
uchoise = 1;
prel_entry = (Elf64_Rel*)((ulong64)pfile_header + psection_header_table_entry[i].sh_offset);
}
if(psection_header_table_entry[i].sh_type == SHT_RELA)
{
uchoise = 2;
prela_entry = (Elf64_Rela*)((ulong64)pfile_header + psection_header_table_entry[i].sh_offset);
int j = 0;
for(; j < urela_num; j++)
{
uint uindex = ELF64_R_SYM(prela_entry[j].r_info);
uint utype = ELF64_R_TYPE(prela_entry[j].r_info);

printf("%016llx %016llx ", prela_entry[j].r_offset, prela_entry[j].r_info);
switch(utype)
{
case R_X86_64_NONE:
printf("%-16s", "R_386_NONE"); break;
case R_X86_64_64:
printf("%-16s", "R_X86_64_64"); break;
case R_X86_64_PC32:
printf("%-16s", "R_X86_64_PC32"); break;
case R_X86_64_GOT32:
printf("%-16s", "R_X86_64_GOT32"); break;
case R_X86_64_PLT32:
printf("%-16s", "R_X86_64_PLT32"); break;
case R_X86_64_COPY:
printf("%-16s", "R_X86_64_COPY"); break;
case R_X86_64_GLOB_DAT:
printf("%-16s", "R_X86_64_GLOB_DAT"); break;
case R_X86_64_JUMP_SLOT:
printf("%-16s", "R_X86_64_JUMP_SLOT"); break;
case R_X86_64_RELATIVE:
printf("%-16s", "R_X86_64_RELATIVE"); break;
case R_X86_64_GOTPCREL:
printf("%-16s", "R_X86_64_GOTPCREL"); break;
}
printf(" %016llx", prela_usesym_entry[uindex].st_value);
printf(" %-16s + %lx\r\n", prela_usesym_entry[uindex].st_name + psym_name_str, prela_entry[j].r_addend);
}
}
}
}



return true;
}

void* print_program_header_table(void* pfile_header_buffer, bool b_print)
{
Elf64_Ehdr* pfile_header = NULL;
Elf64_Phdr* program_header_table_entry = NULL;
Elf64_Shdr* psection_header_table_entry = NULL;
Elf64_Shdr* psec_header_name_entry = NULL;
char* psec_name_str = NULL;

pfile_header = (Elf64_Ehdr*)pfile_header_buffer;
program_header_table_entry = (Elf64_Phdr*)((ulong64)pfile_header + pfile_header->e_phoff);
psection_header_table_entry = (Elf64_Shdr*)((ulong64)pfile_header + pfile_header->e_shoff);
psec_header_name_entry = (Elf64_Shdr*)&psection_header_table_entry[pfile_header->e_shstrndx];
psec_name_str = (char*)((ulong64)pfile_header + psec_header_name_entry->sh_offset);

if(!b_print) return (void*)program_header_table_entry;

printf("**********************************************************************************\n");
switch(pfile_header->e_type)
{
case 0:
printf(" No file type.\r\n"); break;
case 1:
printf("Elf file type is ET_REL (Relocatable file)\r\n"); break;
case 2:
printf("Elf file type is ET_EXEC (Executable file)\r\n"); break;
case 3:
printf("Elf file type is ET_DYN (Shared object file)\r\n"); break;
case 4:
printf("Elf file type is ET_CORE (Core file)\r\n"); break;
default:
printf(" ERROR\r\n"); break;
}

printf("Entry point 0x%llx\r\n", pfile_header->e_entry);
printf("There are %d program headers, starting at offset %lld.\r\n", pfile_header->e_phnum, pfile_header->e_phoff);

printf("Program Headers:\r\n");
printf("[Index] %-16s %-16s %-16s %-16s", "Type", "Offset", "VirtAddr", "PhysAddr");
printf(" %-16s %-16s %-6s %-6s\r\n", "FileSiz", "MemSiz", "Flags", "Align");

int i = 0;
for(; i < pfile_header->e_phnum; i++)
{
switch(program_header_table_entry[i].p_type)
{
case PT_NULL:
printf("%6d %-16s ", i, ""); break;
case PT_LOAD:
printf("%6d %-16s ", i, "LOAD"); break;
case PT_DYNAMIC:
printf("%6d %-16s ", i, "DYNAMIC"); break;
case PT_INTERP:
printf("%6d %-16s ", i, "INTERP"); break;
case PT_NOTE:
printf("%6d %-16s ", i, "NOTE"); break;
case PT_SHLIB:
printf("%6d %-16s ", i, "SHLIB"); break;
case PT_PHDR:
printf("%6d %-16s ", i, "PHDR"); break;
case PT_TLS:
printf("%6d %-16s ", i, "TLS"); break;
case PT_NUM:
printf("%6d %-16s ", i, "NUM"); break;
case PT_LOOS:
printf("%6d %-16s ", i, "LOOS"); break;
case PT_GNU_EH_FRAME:
printf("%6d %-16s ", i, "GNU_EH_FRAME"); break;
case PT_GNU_STACK:
printf("%6d %-16s ", i, "GNU_STACK"); break;
case PT_GNU_RELRO:
printf("%6d %-16s ", i, "GNU_RELRO"); break;
case PT_LOSUNW:
printf("%6d %-16s ", i, "LOSUNW"); break;
case PT_SUNWSTACK:
printf("%6d %-16s ", i, "SUNWSTACK"); break;
case PT_HIOS:
printf("%6d %-16s ", i, "HIOS"); break;
case PT_LOPROC:
printf("%6d %-16s ", i, "LOPROC"); break;
case PT_HIPROC:
printf("%6d %-16s ", i, "HIPROC"); break;
default: break;
}
printf("%016llx %016llx %016llx %016llx %016llx ", program_header_table_entry[i].p_offset, program_header_table_entry[i].p_vaddr,\
program_header_table_entry[i].p_paddr, program_header_table_entry[i].p_filesz, program_header_table_entry[i].p_memsz);

switch(program_header_table_entry[i].p_flags)
{
case PF_X:
printf("%-6s %-llx\r\n", "--E", program_header_table_entry[i].p_align); break;
case PF_W:
printf("%-6s %-llx\r\n", "-W-", program_header_table_entry[i].p_align); break;
case PF_R:
printf("%-6s %-llx\r\n", "R--", program_header_table_entry[i].p_align); break;
case PF_X|PF_W:
printf("%-6s %-llx\r\n", "-WE", program_header_table_entry[i].p_align); break;
case PF_X|PF_R:
printf("%-6s %-llx\r\n", "R-E", program_header_table_entry[i].p_align); break;
case PF_W|PF_R:
printf("%-6s %-llx\r\n", "RW-", program_header_table_entry[i].p_align); break;
case PF_X|PF_R|PF_W:
printf("%-6s %-llx\r\n", "RWE", program_header_table_entry[i].p_align); break;
default: printf("\r\n"); break;
}
if(PT_INTERP == program_header_table_entry[i].p_type)
printf("%8s %16s [Requesting program interpreter: %s]\r\n", "", "", (char*)((ulong64)pfile_header + program_header_table_entry[i].p_offset));
}

printf("\r\n Section to Segment mapping:\r\n");
printf("\r\n Segment Sections...\r\n");

i = 0;
for(; i < pfile_header->e_phnum; i++)
{
ulong64 sec_begin_va = 0;
ulong64 sec_end_va = 0;
ulong64 seg_begin_va = 0;
ulong64 seg_end_va = 0;

printf(" %-8d", i);
int j = 0;
for(; j < pfile_header->e_shnum; j++)
{
sec_begin_va = psection_header_table_entry[j].sh_addr;
sec_end_va = sec_begin_va + psection_header_table_entry[j].sh_size;
seg_begin_va = program_header_table_entry[i].p_vaddr;
seg_end_va = seg_begin_va + program_header_table_entry[i].p_memsz;

if((seg_begin_va < sec_begin_va && sec_end_va <= seg_end_va) || \
(seg_begin_va < sec_end_va && sec_end_va <= seg_end_va))
printf("%s ", (char*)(psection_header_table_entry[j].sh_name + psec_name_str));
}
printf("\r\n");
}

return (void*)program_header_table_entry;
}

int main(int argc, char* argv[], char* enpv[])
{
void* pfile_header = NULL;
void* psection_header_table = NULL;

pfile_header = print_file_header(true);
print_section_header_table(pfile_header, true);
print_symbol_table(pfile_header);
print_relocation_table(pfile_header);
print_program_header_table(pfile_header, true);
print_SHT_DYNAMIC_section(pfile_header);

getchar();
return 0;
}

输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
************************************************************************
------File Header(Elf64_Ehdr)------
[*] pfile_header->e_machine = 62
[*] pfile_header->e_entry = 4195392
[*] pfile_header->e_phoff = 64
[*] pfile_header->e_shoff = 6440
[*] pfile_header->e_phnum = 9
[*] pfile_header->e_shnum = 30
[*] pfile_header->e_shstrndx = 29
************************************************************************
Section Headers:
[Nr] Name Type Address Offset Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 0000000000400238 0000000000000238 000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.ABI-tag NOTE 0000000000400254 0000000000000254 0000000000000020 0000000000000000 A 0 0 4
[ 3] .note.gnu.build-id NOTE 0000000000400274 0000000000000274 0000000000000024 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400298 0000000000000298 000000000000001c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 00000000004002b8 00000000000002b8 0000000000000060 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400318 0000000000000318 000000000000003d 0000000000000000 A 0 0 1
[ 7] .gnu.version GNU_versym 0000000000400356 0000000000000356 0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r GNU_verneed 0000000000400360 0000000000000360 0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 0000000000000380 0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 0000000000000398 0000000000000048 0000000000000018 NONE
[11] .init PROGBITS 00000000004003e0 00000000000003e0 000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000400400 0000000000000400 0000000000000040 0000000000000010 AX 0 0 16
[13] .text PROGBITS 0000000000400440 0000000000000440 0000000000000192 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 00000000004005d4 00000000000005d4 0000000000000009 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 00000000004005e0 00000000000005e0 000000000000001c 0000000000000000 A 0 0 8
[16] .eh_frame_hdr PROGBITS 00000000004005fc 00000000000005fc 0000000000000034 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 0000000000400630 0000000000000630 00000000000000f4 0000000000000000 A 0 0 8
[18] .init_array INIT_ARRY 0000000000600e10 0000000000000e10 0000000000000008 0000000000000008 WA 0 0 8
[19] .fini_array FINI_ARRY 0000000000600e18 0000000000000e18 0000000000000008 0000000000000008 WA 0 0 8
[20] .jcr PROGBITS 0000000000600e20 0000000000000e20 0000000000000008 0000000000000000 WA 0 0 8
[21] .dynamic DYNAMIC 0000000000600e28 0000000000000e28 00000000000001d0 0000000000000010 WA 6 0 8
[22] .got PROGBITS 0000000000600ff8 0000000000000ff8 0000000000000008 0000000000000008 WA 0 0 8
[23] .got.plt PROGBITS 0000000000601000 0000000000001000 0000000000000030 0000000000000008 WA 0 0 8
[24] .data PROGBITS 0000000000601030 0000000000001030 0000000000000004 0000000000000000 WA 0 0 1
[25] .bss NOBITS 0000000000601034 0000000000001034 0000000000000004 0000000000000000 WA 0 0 1
[26] .comment PROGBITS 0000000000000000 0000000000001034 000000000000002d 0000000000000001 NONE
[27] .symtab SYMTAB 0000000000000000 0000000000001068 00000000000005e8 0000000000000018 28 46 8
[28] .strtab STRTAB 0000000000000000 0000000000001650 00000000000001c9 0000000000000000 0 0 1
[29] .shstrtab STRTAB 0000000000000000 0000000000001819 0000000000000108 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
**********************************************************************************
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL 0 UND
1: 0000000000000000 0 FUNC GLOBAL 0 UND puts
2: 0000000000000000 0 FUNC GLOBAL 0 UND __libc_start_main
3: 0000000000000000 0 NOTYPE WEAK 0 UND __gmon_start__
Symbol table '.symtab' contains 63 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL 0 UND
1: 0000000000400238 0 SECTION LOCAL 0 1
2: 0000000000400254 0 SECTION LOCAL 0 2
3: 0000000000400274 0 SECTION LOCAL 0 3
4: 0000000000400298 0 SECTION LOCAL 0 4
5: 00000000004002b8 0 SECTION LOCAL 0 5
6: 0000000000400318 0 SECTION LOCAL 0 6
7: 0000000000400356 0 SECTION LOCAL 0 7
8: 0000000000400360 0 SECTION LOCAL 0 8
9: 0000000000400380 0 SECTION LOCAL 0 9
10: 0000000000400398 0 SECTION LOCAL 0 10
11: 00000000004003e0 0 SECTION LOCAL 0 11
12: 0000000000400400 0 SECTION LOCAL 0 12
13: 0000000000400440 0 SECTION LOCAL 0 13
14: 00000000004005d4 0 SECTION LOCAL 0 14
15: 00000000004005e0 0 SECTION LOCAL 0 15
16: 00000000004005fc 0 SECTION LOCAL 0 16
17: 0000000000400630 0 SECTION LOCAL 0 17
18: 0000000000600e10 0 SECTION LOCAL 0 18
19: 0000000000600e18 0 SECTION LOCAL 0 19
20: 0000000000600e20 0 SECTION LOCAL 0 20
21: 0000000000600e28 0 SECTION LOCAL 0 21
22: 0000000000600ff8 0 SECTION LOCAL 0 22
23: 0000000000601000 0 SECTION LOCAL 0 23
24: 0000000000601030 0 SECTION LOCAL 0 24
25: 0000000000601034 0 SECTION LOCAL 0 25
26: 0000000000000000 0 SECTION LOCAL 0 26
27: 0000000000000000 0 FILE LOCAL 0 ABS crtstuff.c
28: 0000000000600e20 0 OBJECT LOCAL 0 20 __JCR_LIST__
29: 0000000000400470 0 FUNC LOCAL 0 13 deregister_tm_clones
30: 00000000004004a0 0 FUNC LOCAL 0 13 register_tm_clones
31: 00000000004004e0 0 FUNC LOCAL 0 13 __do_global_dtors_aux
32: 0000000000601034 1 OBJECT LOCAL 0 25 completed.6355
33: 0000000000600e18 0 OBJECT LOCAL 0 19 __do_global_dtors_aux_fini_array_entry
34: 0000000000400500 0 FUNC LOCAL 0 13 frame_dummy
35: 0000000000600e10 0 OBJECT LOCAL 0 18 __frame_dummy_init_array_entry
36: 0000000000000000 0 FILE LOCAL 0 ABS test.c
37: 0000000000000000 0 FILE LOCAL 0 ABS crtstuff.c
38: 0000000000400720 0 OBJECT LOCAL 0 17 __FRAME_END__
39: 0000000000600e20 0 OBJECT LOCAL 0 20 __JCR_END__
40: 0000000000000000 0 FILE LOCAL 0 ABS
41: 0000000000600e18 0 NOTYPE LOCAL 0 18 __init_array_end
42: 0000000000600e28 0 OBJECT LOCAL 0 21 _DYNAMIC
43: 0000000000600e10 0 NOTYPE LOCAL 0 18 __init_array_start
44: 00000000004005fc 0 NOTYPE LOCAL 0 16 __GNU_EH_FRAME_HDR
45: 0000000000601000 0 OBJECT LOCAL 0 23 _GLOBAL_OFFSET_TABLE_
46: 00000000004005d0 2 FUNC GLOBAL 0 13 __libc_csu_fini
47: 0000000000601030 0 NOTYPE WEAK 0 24 data_start
48: 0000000000000000 0 FUNC GLOBAL 0 UND puts@@GLIBC_2.2.5
49: 0000000000601034 0 NOTYPE GLOBAL 0 24 _edata
50: 00000000004005d4 0 FUNC GLOBAL 0 14 _fini
51: 0000000000000000 0 FUNC GLOBAL 0 UND __libc_start_main@@GLIBC_2.2.5
52: 0000000000601030 0 NOTYPE GLOBAL 0 24 __data_start
53: 0000000000000000 0 NOTYPE WEAK 0 UND __gmon_start__
54: 00000000004005e8 0 OBJECT GLOBAL 2 15 __dso_handle
55: 00000000004005e0 4 OBJECT GLOBAL 0 15 _IO_stdin_used
56: 0000000000400560 65 FUNC GLOBAL 0 13 __libc_csu_init
57: 0000000000601038 0 NOTYPE GLOBAL 0 25 _end
58: 0000000000400440 0 FUNC GLOBAL 0 13 _start
59: 0000000000601034 0 NOTYPE GLOBAL 0 25 __bss_start
60: 000000000040052d 32 FUNC GLOBAL 0 13 main
61: 0000000000601038 0 OBJECT GLOBAL 2 24 __TMC_END__
62: 00000000004003e0 0 FUNC GLOBAL 0 11 _init
**********************************************************************************
**********************************************************************************

Relocation section '.rela.dyn' at offset 0x380 contains 1 entries:
Offset Info Type Sym.Value Sym.Name + Addend
0000000000600ff8 0000000300000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0

Relocation section '.rela.plt' at offset 0x398 contains 3 entries:
Offset Info Type Sym.Value Sym.Name + Addend
0000000000601018 0000000100000007 R_X86_64_JUMP_SLOT 0000000000000000 puts + 0
0000000000601020 0000000200000007 R_X86_64_JUMP_SLOT 0000000000000000 __libc_start_main + 0
0000000000601028 0000000300000007 R_X86_64_JUMP_SLOT 0000000000000000 __gmon_start__ + 0
**********************************************************************************
Elf file type is ET_EXEC (Executable file)
Entry point 0x400440
There are 9 program headers, starting at offset 64.
Program Headers:
[Index] Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align
0 PHDR 0000000000000040 0000000000400040 0000000000400040 00000000000001f8 00000000000001f8 R-E 8
1 INTERP 0000000000000238 0000000000400238 0000000000400238 000000000000001c 000000000000001c R-- 1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
2 LOAD 0000000000000000 0000000000400000 0000000000400000 0000000000000724 0000000000000724 R-E 200000
3 LOAD 0000000000000e10 0000000000600e10 0000000000600e10 0000000000000224 0000000000000228 RW- 200000
4 DYNAMIC 0000000000000e28 0000000000600e28 0000000000600e28 00000000000001d0 00000000000001d0 RW- 8
5 NOTE 0000000000000254 0000000000400254 0000000000400254 0000000000000044 0000000000000044 R-- 4
6 GNU_EH_FRAME 00000000000005fc 00000000004005fc 00000000004005fc 0000000000000034 0000000000000034 R-- 4
7 GNU_STACK 0000000000000000 0000000000000000 0000000000000000 0000000000000000 0000000000000000 RW- 10
8 GNU_RELRO 0000000000000e10 0000000000600e10 0000000000600e10 00000000000001f0 00000000000001f0 R-- 1

Section to Segment mapping:

Segment Sections...
0
1 .interp
2 .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
3 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
4 .dynamic
5 .note.ABI-tag .note.gnu.build-id
6 .eh_frame_hdr
7
8 .init_array .fini_array .jcr .dynamic .got