ELF文件格式解析器 原理 + 代码
参考: https://bbs.kanxue.com/thread-259901.htm
写在前面:
读《Linux二进制》,发现作者对 ELF文件格式部分并没有做详细介绍,为了加深对elf文件格式理解,我自己着手写了个解析器, 会和readelf 工具协同对比。
原理:
ELF文件(目标文件)格式主要三种:
1. 可重定向文件(Relocatable file):文件保存着代码和适当的数据,用来和其他的目标文件一起来创建一个可执行文件或者是一个共享目标文件。(目标文件或者静态库文件,即linux通常后缀为.a和.o的文件)这是由汇编器汇编生成的 .o 文件。后面的链接器(link editor)拿一个或一些 Relocatable object files 作为输入,经链接处理后,生成一个可执行的对象文件 (Executable file) 或者一个可被共享的对象文件(Shared object file),内核可加载模块 .ko 文件也是 Relocatable object file
2. 可执行文件(Executable file):文件保存着一个用来执行的程序。(例如bash,gcc等)
3. 共享目标文件:
即 .so 文件。如果拿前面的静态库来生成可执行程序,那每个生成的可执行程序中都会有一份库代码的拷贝。如果在磁盘中存储这些可执行程序,那就会占用额外的磁盘空 间;另外如果拿它们放到Linux系统上一起运行,也会浪费掉宝贵的物理内存。如果将静态库换成动态库,那么这些问题都不会出现
一般的ELF文件有三个重要的索引表
1. ELF header:在文件的开始,描述整个文件的组织。。
2. Program header table:告诉系统如何创建进程映像。用来构造进程映像的目标文件必须具有程序头部表,可重定位文件不需要这个表。
3. Section header table :包含了描述文件节区的信息,每个节区在表中都有一项,每一项给出诸如节区名称、节区大小这类信息。用于链接的目标文件必须包含节区头部表,其他目标文件可以有,也可以没有这个表。
4. sections 或者 segments:segments是从运行的角度来描述elf文件,sections是从链接的角度来描述elf文件,也就是说,在链接阶段,我们可以忽略program header table来处理此文件,在运行阶段可以忽略section header table来处理此程序(所以很多加固手段删除了section header table)。注意:segments与sections是包含的关系,一个segment包含若干个section。
(图片来自网络)
上面提到的包含关系,我们可以用表格来对比一下结构:
了解整体结构之后,我们就可以看一下具体的结构体和指针了。
代码先写了一个help函数,包含基本信息和指令结构,效果如下:
1
2
3
4
5
6
7
8
9
|
void help() { printf ( "这是jentle的解析器demo\n" ); printf ( "-h :头部信息\n" ); printf ( "-S :节区表信息\n" ); printf ( "-s :符号表信息\n" ); printf ( "-l :程序头信息\n" ); printf ( "-r :重定位表信息\n" ); } |
1.-h指令,查看和打印 程序header函数。
我们查看一下elf headr结构体:
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
|
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; |
这里面包括后面的code都会涉及到elf的数据格式,在这给出:
所以打印elf的头信息可以设计为:
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
|
//读取文件头函数 void fileheader( const char *pbuff) { printf ( "ELF Header:\r\n" ); //Magic printf ( " Magic: " ); for ( int i = 0;i<EI_NIDENT;++i) //e_ident[EI_NIDENT] { printf ( "%02X" , pbuff[i]); putchar ( ' ' ); } printf ( "\r\n" ); //Class printf ( " %-33s:" , "Class" ); switch (pbuff[4]) { case 0: printf ( " Invalid class\r\n" ); break ; case 1: printf ( " ELF32\r\n" ); break ; case 2: printf ( " ELF64\r\n" ); break ; default : printf ( " ERROR\r\n" ); break ; } //Data printf ( " %-33s:" , "Data" ); switch (pbuff[5]) { case 0: printf ( " Invalid data encoding\r\n" ); break ; case 1: printf ( " 2's complement, little endian\r\n" ); break ; case 2: printf ( " 2's complement, big endian\r\n" ); break ; default : printf ( " ERROR\r\n" ); break ; } //Version printf ( " %-33s: %s\r\n" , "Version" , "1(current)" ); //OS/ABI printf ( " %-33s: %s\r\n" , "OS/ABI" , "UNIX - System V" ); //ABI Version printf ( " %-33s: %s\r\n" , "ABI Version" , "0" ); pbuff += EI_NIDENT; //Type printf ( " %-33s:" , "Type" ); switch (*(uint16_t*)pbuff) { case 0: printf ( " No file type\r\n" ); break ; case 1: printf ( " Relocatable file\r\n" ); break ; case 2: printf ( " Executable file\r\n" ); break ; case 3: printf ( " Shared object file\r\n" ); break ; case 4: printf ( " Core file\r\n" ); break ; default : printf ( " ERROR\r\n" ); break ; } pbuff += sizeof (uint16_t); //Machine printf ( " %-33s:" , "Machine" ); switch (*(uint16_t*)pbuff) { case EM_386: printf ( " Intel 80386\r\n" ); break ; case EM_ARM: printf ( " ARM\r\n" ); break ; case EM_X86_64: printf ( " AMD X86-64 arrchitecture\r\n" ); break ; default : printf ( " ERROR\r\n" ); break ; } pbuff += sizeof (uint16_t); //Version printf ( " %-33s: %s\r\n" , "version" , "0X1" ); pbuff += sizeof (uint32_t); //入口点位置 printf ( " %-33s: 0X%lx\r\n" , "Entry point address" , *(uint64_t*)pbuff); pbuff += sizeof (uint64_t); //程序头大小 printf ( " %-33s: %lu (bytes into file)\r\n" , "Start of program headers" , *(uint64_t*)pbuff); pbuff += sizeof (uint64_t); //区段大小 printf ( " %-33s: %lu (bytes into file)\r\n" , "Start of section headers" , *(uint64_t*)pbuff); pbuff += sizeof (uint64_t); //Flags printf ( " %-33s: 0X0\r\n" , "Flags" ); pbuff += sizeof (Elf32_Word); //本节大小 printf ( " %-33s: %d (bytes)\r\n" , "Size of this header" , *(Elf32_Half*)pbuff); pbuff += sizeof (Elf32_Half); //程序头大小 printf ( " %-33s: %d (bytes)\r\n" , "Size of program headers" , *(Elf32_Half*)pbuff); pbuff += sizeof (Elf32_Half); //程序头大小 printf ( " %-33s: %d\r\n" , "Number of program headers" , *(Elf32_Half*)pbuff); pbuff += sizeof (Elf32_Half); //section大小 printf ( " %-33s: %d (bytes)\r\n" , "Size of section headers" , *(Elf32_Half*)pbuff); pbuff += sizeof (Elf32_Half); //section大小 printf ( " %-33s: %d\r\n" , "Number of section headers" , *(Elf32_Half*)pbuff); pbuff += sizeof (Elf32_Half); //下标值 printf ( " %-33s: %d\r\n" , "Section header string table index" , *(Elf32_Half*)pbuff); } |
效果展示:
readelf对比:
参考https://blog.csdn.net/qq_37431937前辈图解:
2. -S 指令 打印section信息
段表结构体:
1
2
3
4
5
6
7
8
9
10
|
typedef struct { Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字 //符串表索引(offset),否则符号表项没有名称。 Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。 Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。 unsigned char st_info; //符号的类型和绑定属性。 unsigned char st_other; //未定义。 Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。 //此成员给出相关的节区头部表索引。 } Elf32_sym; |
一些成员参数解释:
代码设计:
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
|
void sectionheader( const char *pbuff) { //60 偏移位置得到节区数量 int nNumSec = *(Elf64_Half*)(pbuff + 60); //get shstrndex Elf64_Ehdr* pfilehead = (Elf64_Ehdr*)pbuff; Elf64_Half eshstrndx = pfilehead->e_shstrndx; //得到偏移地址 Elf64_Shdr* psecheader = (Elf64_Shdr*)(pbuff + pfilehead->e_shoff); Elf64_Shdr* pshstr = (Elf64_Shdr*)(psecheader + eshstrndx); char * pshstrbuff = ( char *)(pbuff + pshstr->sh_offset); //output info printf ( "共有 %d 节区表, 偏移位置开始于 0x%lx:\r\n\r\n" , nNumSec, *(Elf64_Off*)(pbuff + 40)); printf ( "节头:\r\n" ); //打印标志位信息 printf ( " [Nr] %-16s %-16s %-16s %-16s\r\n" , "Name" , "Type" , "Address" , "Offset" ); printf ( " %-16s %-16s %-5s %-5s %-5s %-5s\r\n" , "Size" , "EntSize" , "Flags" , "Link" , "Info" , "Align" ); //遍历每一个节表数量 for ( int i = 0;i<nNumSec;++i) { printf ( " [%2d] %-16s " , i, ( char *)(psecheader[i].sh_name + pshstrbuff)); //Type switch (psecheader[i].sh_type) { case SHT_NULL: printf ( "%-16s " , "NULL" ); break ; case SHT_PROGBITS: printf ( "%-16s " , "PROGBITS" ); break ; case SHT_SYMTAB: printf ( "%-16s " , "SYMTAB" ); break ; case SHT_STRTAB: printf ( "%-16s " , "STRTAB" ); break ; case SHT_RELA: printf ( "%-16s " , "RELA" ); break ; case SHT_HASH: printf ( "%-16s " , "GNU_HASH" ); break ; case SHT_DYNAMIC: printf ( "%-16s " , "DYNAMIC" ); break ; case SHT_NOTE: printf ( "%-16s " , "NOTE" ); break ; case SHT_NOBITS: printf ( "%-16s " , "NOBITS" ); break ; case SHT_REL: printf ( "%-16s " , "REL" ); break ; case SHT_SHLIB: printf ( "%-16s " , "SHLIB" ); break ; case SHT_DYNSYM: printf ( "%-16s " , "DYNSYM" ); break ; case SHT_INIT_ARRAY: printf ( "%-16s " , "INIT_ARRY" ); break ; case SHT_FINI_ARRAY: printf ( "%-16s " , "FINI_ARRY" ); break ; case SHT_PREINIT_ARRAY: printf ( "%-16s " , "PREINIT_ARRAY" ); break ; case SHT_GNU_HASH: printf ( "%-16s " , "GNU_HASH" ); break ; case SHT_GNU_ATTRIBUTES: printf ( "%-16s " , "GNU_ATTRIBUTES" ); break ; case SHT_GNU_LIBLIST: printf ( "%-16s " , "GNU_LIBLIST" ); break ; case SHT_GNU_verdef: printf ( "%-16s " , "GNU_verdef" ); break ; case SHT_GNU_verneed: printf ( "%-16s " , "GNU_verneed" ); break ; case SHT_GNU_versym: printf ( "%-16s " , "GNU_versym" ); break ; default : printf ( "%-16s " , "NONE" ); break ; } printf ( "%016lX %08lX\r\n" , psecheader[i].sh_addr, psecheader[i].sh_offset); printf ( " %016lX %016lx " , psecheader[i].sh_size, psecheader[i].sh_entsize); switch (psecheader[i].sh_flags) { case 0: printf ( "%3s %4u %4u %4lu\r\n" , "" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 1: printf ( "%3s %4u %4u %4lu\r\n" , "W" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 2: printf ( "%3s %4u %4u %4lu\r\n" , "A" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 4: printf ( "%3s %4u %4u %4lu\r\n" , "X" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 3: printf ( "%3s %4u %4u %4lu\r\n" , "WA" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 5: //WX printf ( "%3s %4u %4u %4lu\r\n" , "WX" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 6: //AX printf ( "%3s %4u %4u %4lu\r\n" , "AX" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case 7: //WAX printf ( "%3s %4u %4u %4lu\r\n" , "WAX" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[i].sh_addralign); break ; case SHF_MASKPROC: //MS printf ( "%3s %4u %4u %4lu\r\n" , "MS" , psecheader[i].sh_link, psecheader[i].sh_info, psecheader[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" ); } |
效果展示:
readelf 对比:
3. -s 打印符号表信息
目标文件的符号表中包含用来定位、重定位程序中符号定义和引用的信息。符号表 索引是对此数组的索引。索引 0 表示表中的第一表项,同时也作为 定义符号的索引。
符号是对某些类型的数据或者代码(如全局变量或函数)的符号引用。 例如,printf()函数会在动态符号表.dynsym 中存有一个指向该函数的 符号条目。在大多数共享库和动态链接可执行文件中,存在两个符号表。 如前面使用readelf –S命令输出的内容中,可以看到有两个节:.dynsym 和.symtab。
.dynsym保存了引用来自外部文件符号的全局符号,如printf这样的 库函数,.dynsym保存的符号是.symtab所保存符号的子集,.symtab中 还保存了可执行文件的本地符号,如全局变量,或者代码中定义的本地函数 等。因此,.symtab保存了所有的符号,而.dynsym只保存动态/全局符号。
因此,就存在这样一个问题:既然.symtab 中保存了.dynsym 中所有的 符号,那么为什么还需要两个符号表呢?使用readelf –S命令查看可执行文 件的输出,可以看到一部分节被标记为了 A(ALLOC) 、WA(WRITE/ALLOC) 或者 AX(ALLOC/EXEC) 。.dynsym 是被标记了 ALLOC 的,而.symtab 则没有标记。 ALLOC 表示有该标记的节会在运行时分配并装载进入内存,而.symtab 不是在运行时必需的,因此不会被装载到内存中。.dynsym保存的符号只能在运行时被解析,因此是运行时动态链接器所需要的唯一符号。.dynsym符号表 对于动态链接可执行文件的执行来说是必需的,而.symtab符号表只是用来进 行调试和链接的,有时候为了节省空间,会将.symtab符号表从生产二进制文 件中删掉。
符号表:.dynsym
符号表包含用来定位、重定位程序中符号定义和引用的信息,简单的理解就是符号表记录了该文件中的所有符号,所谓的符号就是经过修饰了的函数名或者变量名,不同的编译器有不同的修饰规则。例如符号_ZL15global_static_a,就是由global_static_a变量名经过修饰而来。
符号表格式如下:
1
2
3
4
5
6
7
8
9
10
|
typedef struct { Elf32_Word st_name; //符号表项名称。如果该值非0,则表示符号名的字 //符串表索引(offset),否则符号表项没有名称。 Elf32_Addr st_value; //符号的取值。依赖于具体的上下文,可能是一个绝对值、一个地址等等。 Elf32_Word st_size; //符号的尺寸大小。例如一个数据对象的大小是对象中包含的字节数。 unsigned char st_info; //符号的类型和绑定属性。 unsigned char st_other; //未定义。 Elf32_Half st_shndx; //每个符号表项都以和其他节区的关系的方式给出定义。 //此成员给出相关的节区头部表索引。 } Elf32_sym; |
字符串表.dynstr 略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
void tableheader( const char *pbuff) { //从节区里面定位到偏移 Elf64_Ehdr* pfilehead = (Elf64_Ehdr*)pbuff; Elf64_Half eshstrndx = pfilehead->e_shstrndx; Elf64_Shdr* psecheader = (Elf64_Shdr*)(pbuff + pfilehead->e_shoff); Elf64_Shdr* pshstr = (Elf64_Shdr*)(psecheader + eshstrndx); char * pshstrbuff = ( char *)(pbuff + pshstr->sh_offset); for ( int i = 0;i<pfilehead->e_shnum;++i) { if (! strcmp (psecheader[i].sh_name + pshstrbuff, ".dynsym" ) || ! strcmp (psecheader[i].sh_name + pshstrbuff, ".symtab" )) { Elf64_Sym* psym = (Elf64_Sym*)(pbuff + psecheader[i].sh_offset); int ncount = psecheader[i].sh_size / psecheader[i].sh_entsize; char * pbuffstr = ( char *)((psecheader + psecheader[i].sh_link)->sh_offset + pbuff); printf ( "Symbol table '%s' contains %d entries:\r\n" , psecheader[i].sh_name + pshstrbuff, ncount); outputsyminfo(psym, pbuffstr, ncount); continue ; } } } |
效果展示:
readelf 对比:
参数解释:
STT_NOTYPE:符号类型未定义。
STT_FUNC:表示该符号与函数或者其他可执行代码关联。
STT_OBJECT:表示该符号与数据目标文件关联。
符号绑定
STB_LOCAL:本地符号在目标文件之外是不可见的,目标文件包含 了符号的定义,如一个声明为 static 的函数。
STB_GLOBAL:全局符号对于所有要合并的目标文件来说都是可见 的。一个全局符号在一个文件中进行定义后,另外一个文件可以对这 个符号进行引用。
STB_WEAK:与全局绑定类似,不过比 STB_GLOBAL 的优先级低。 被标记为 STB_WEAK 的符号有可能会被同名的未被标记为 STB_WEAK的符号覆盖。
下面是对绑定和类型字段进行打包和解包的宏指令。
ELF32_ST_BIND(info)或者 ELF64_ST_BIND(info):从 st_info 值中提取出一个绑定。 ELF32_ST_TYPE(info)或者 ELF64_ST_TYPE(info):从 st_info 值中提取类型。 ELF32_ST_TYPE(bind,type)或者ELF64_ST_INFO(bind,type): 将一个绑定和类型转换成st_info值。
4. -l 指令 program头信息。
程序头表与段表互相独立,有ELF文件头同一管理。
结构信息:
1
2
3
4
5
6
7
8
9
10
|
typedef struct { Elf32_Word p_type; //此数组元素描述的段的类型,或者如何解释此数组元素的信息。 Elf32_Off p_offset; //此成员给出从文件头到该段第一个字节的偏移 Elf32_Addr p_vaddr; //此成员给出段的第一个字节将被放到内存中的虚拟地址 Elf32_Addr p_paddr; //此成员仅用于与物理地址相关的系统中。System V忽略所有应用程序的物理地址信息。 Elf32_Word p_filesz; //此成员给出段在文件映像中所占的字节数。可以为0。 Elf32_Word p_memsz; //此成员给出段在内存映像中占用的字节数。可以为0。 Elf32_Word p_flags; //此成员给出与段相关的标志。 Elf32_Word p_align; //此成员给出段在文件中和内存中如何对齐。 } Elf32_phdr; |
参数解释:
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
|
(1) p_type表示当前描述的段的种类。常见有以下常数。 #define PT_NULL 0 //空段 #define PT_LOAD 1 //可装载段 #define PT_DYNAMIC 2 //表示该段包含了用于动态连接器的信息 #define PT_INTERP 3 //表示当前段指定了用于动态连接的程序解释器,通常是ld-linux.so #define PT_NOTE 4 //该段包含有专有的编译器信息 #define PT_SHLIB 5 //该段包含有共享库 (2) p_offset给出了该段在二进制文件中的偏移量,单位为字节。 (3) p_vaddr给出了该段需要映射到进程虚拟地址空间中的位置。 (4) p_paddr在只支持物理寻址,不支持虚拟寻址的系统当中才使用。 (5) p_filesz给出了该段在二进制文件当中的长度,单位为字节。 (6) p_memsz给出了段在虚拟地址空间当中的长度,单位为字节。与p_filesz不等时会通过截断数据或者以0填充的方式处理。 (7) p_flags保存了标志信息,定义了该段的访问权限。有如下值 #define PF_R 0x4 //该段可读 #define PF_W 0x2 //该段可写 #define PF_X 0x1 //该段可执行 (8) p_align指定了段在内存和二进制文件当中的对齐方式,即p_offset和p_vaddr必须是p_align的整数倍。 |
设计代码:
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
|
void programheader( const char *pbuff) { Elf64_Ehdr* pfilehead = (Elf64_Ehdr*)pbuff; Elf64_Phdr* pproheader = (Elf64_Phdr*)(pbuff + pfilehead->e_phoff); Elf64_Shdr* psecheader = (Elf64_Shdr*)(pbuff + pfilehead->e_shoff); Elf64_Shdr* pshstr = (Elf64_Shdr*)(psecheader + pfilehead->e_shstrndx); char * pstrbuff = ( char *)(pbuff + pshstr->sh_offset); printf ( "Elf 文件类型是" ); switch (pfilehead->e_type) { case 0: printf ( " No file type\r\n" ); break ; case 1: printf ( " Relocatable file\r\n" ); break ; case 2: printf ( " Executable file\r\n" ); break ; case 3: printf ( " Shared object file\r\n" ); break ; case 4: printf ( " Core file\r\n" ); break ; default : printf ( " ERROR\r\n" ); break ; } printf ( "入口点位置 0X%0lX\r\n" , pfilehead->e_entry); printf ( "共有 %d 程序头, 偏移位置 %lu\r\n\r\n" , pfilehead->e_phnum, pfilehead->e_phoff); printf ( "Program Headers:\r\n" ); printf ( " %-14s %-16s %-16s %-16s\r\n" , "Type" , "Offset" , "VirtAddr" , "PhysAddr" ); printf ( " %-14s %-16s %-16s %-6s %-6s\r\n" , "" , "FileSiz" , "MemSiz" , "Flags" , "Align" ); for ( int i=0;i<pfilehead->e_phnum;++i) { //type switch (pproheader[i].p_type) { case PT_NULL: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_LOAD: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "LOAD" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_DYNAMIC: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "DYNAMIC" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_INTERP: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "INTERP" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_NOTE: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "NOTE" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_SHLIB: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "SHLIB" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_PHDR: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "PHDR" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_TLS: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "TLS" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_NUM: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "NUM" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_GNU_EH_FRAME: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "GNU_EH_FRAME" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_GNU_RELRO: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "GNU_RELRO" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; case PT_GNU_STACK: printf ( " %-14s %016lX %016lX %016lX\r\n %-14s %016lX %016lX " , "GNU_STACK" , pproheader[i].p_offset, pproheader[i].p_vaddr, pproheader[i].p_paddr, "" , pproheader[i].p_filesz, pproheader[i].p_memsz); break ; default : break ; } // switch (pproheader[i].p_flags) { case PF_X: printf ( "%-6s %-lX\r\n" , " E" , pproheader[i].p_align); break ; case PF_W: printf ( "%-6s %-lX\r\n" , " W " , pproheader[i].p_align); break ; case PF_R: printf ( "%-6s %-lX\r\n" , "R " , pproheader[i].p_align); break ; case PF_X|PF_W: printf ( "%-6s %-lX\r\n" , " WE" , pproheader[i].p_align); break ; case PF_X|PF_R: printf ( "%-6s %-lX\r\n" , "R E" , pproheader[i].p_align); break ; case PF_W|PF_R: printf ( "%-6s %-lX\r\n" , "RW " , pproheader[i].p_align); break ; case PF_X|PF_R|PF_W: printf ( "%-6s %-lX\r\n" , "RWE" , pproheader[i].p_align); break ; default : printf ( "\r\n" ); break ; } if (PT_INTERP == pproheader[i].p_type) printf ( " [Requesting program interpreter: %s]\r\n" , ( char *)(pbuff + pproheader[i].p_offset)); } printf ( "\r\n Section to Segment mapping:\r\n" ); printf ( " 段节...\r\n" ); for ( int i=0;i<pfilehead->e_phnum;++i) { printf ( " %-7d" , i); for ( int n = 0;n<pfilehead->e_shnum;++n) { Elf64_Off temp = psecheader[n].sh_addr + psecheader[n].sh_size; if ((psecheader[n].sh_addr>pproheader[i].p_vaddr && psecheader[n].sh_addr<pproheader[i].p_vaddr + pproheader[i].p_memsz) || (temp > pproheader[i].p_vaddr && temp<=pproheader[i].p_vaddr + pproheader[i].p_memsz)) { printf ( "%s " , ( char *)(psecheader[n].sh_name + pstrbuff)); } } printf ( "\r\n" ); } } |
效果展示:
readelf对比:
最后还有一个重定位表的打印函数,暂时不介绍了。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。