段是程序执行的必要组成部分,段可以被分割成若干个节,而节头表是对这些节的位置和大小的描述,主要是链接和调试使用的,而对程序的执行却不是必需的。因为对程序内存布局的描述已经由程序头表描述了,而节头表则是对其的补充。即使节头不存在,节依然存在,只是无法通过节头去引用。
查看程序的节头:
readelf -S hello.out
输出:
共有 31 个节头,从偏移量 0x19e0 开始:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 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
000000000000003f 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000400358 00000358
0000000000000008 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000400360 00000360
0000000000000020 0000000000000000 A 6 1 8
[ 9] .rela.dyn RELA 0000000000400380 00000380
0000000000000018 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000400398 00000398
0000000000000030 0000000000000018 AI 5 24 8
[11] .init PROGBITS 00000000004003c8 000003c8
000000000000001a 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 00000000004003f0 000003f0
0000000000000030 0000000000000010 AX 0 0 16
[13] .plt.got PROGBITS 0000000000400420 00000420
0000000000000008 0000000000000000 AX 0 0 8
[14] .text PROGBITS 0000000000400430 00000430
0000000000000182 0000000000000000 AX 0 0 16
[15] .fini PROGBITS 00000000004005b4 000005b4
0000000000000009 0000000000000000 AX 0 0 4
[16] .rodata PROGBITS 00000000004005c0 000005c0
0000000000000010 0000000000000000 A 0 0 4
[17] .eh_frame_hdr PROGBITS 00000000004005d0 000005d0
0000000000000034 0000000000000000 A 0 0 4
[18] .eh_frame PROGBITS 0000000000400608 00000608
00000000000000f4 0000000000000000 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000600e10 00000e10
0000000000000008 0000000000000000 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000600e18 00000e18
0000000000000008 0000000000000000 WA 0 0 8
[21] .jcr PROGBITS 0000000000600e20 00000e20
0000000000000008 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 0000000000600e28 00000e28
00000000000001d0 0000000000000010 WA 6 0 8
[23] .got PROGBITS 0000000000600ff8 00000ff8
0000000000000008 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 0000000000601000 00001000
0000000000000028 0000000000000008 WA 0 0 8
[25] .data PROGBITS 0000000000601028 00001028
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 0000000000601038 00001038
0000000000000008 0000000000000000 WA 0 0 1
[27] .comment PROGBITS 0000000000000000 00001038
0000000000000035 0000000000000001 MS 0 0 1
[28] .shstrtab STRTAB 0000000000000000 000018ce
000000000000010c 0000000000000000 0 0 1
[29] .symtab SYMTAB 0000000000000000 00001070
0000000000000648 0000000000000018 30 47 8
[30] .strtab STRTAB 0000000000000000 000016b8
0000000000000216 0000000000000000 0 0 1
下面结合输出来分析十六进制的程序代码。
实例中这一段的偏移量为0x238~0x254,可以用hexedit工具来查看这一节的内容:
2F 6C 69 62 36 34 2F 6C ......../lib64/l
00000240 64 2D 6C 69 6E 75 78 2D 78 38 36 2D 36 34 2E 73 d-linux-x86-64.s
00000250 6F 2E 32 00 o.2.
可以看出,这一节保存的是程序解释器的位置。
实例中这一段的偏移量为0x254~0x274,内容中有GNU三个可见字符,应该是指明运行环境的相关信息。
实例中这一段偏移量为0x274~0x298,内容中仍然只有GNU三个可见字符。
实例中这一段偏移量为0x298~0x2b4,内容中的的仍然只有GNU三个字符,这里保存了一个用来查找符号的散列表。
实例中这一段偏移量为0x2b8~0x318,这里保存了从共享库导入的动态符号信息。
实例中这一段偏移量为0x318~0x357,这里保存了动态符号字符表,可以查看一下这一段的内容:
00 6C 69 62 63 2E 73 6F .........libc.so
00000320 2E 36 00 70 72 69 6E 74 66 00 5F 5F 6C 69 62 63 .6.printf.__libc
00000330 5F 73 74 61 72 74 5F 6D 61 69 6E 00 5F 5F 67 6D _start_main.__gm
00000340 6F 6E 5F 73 74 61 72 74 5F 5F 00 47 4C 49 42 43 on_start__.GLIBC
00000350 5F 32 2E 32 2E 35 00 00 00 00 02 00 02 00 00 00 _2.2.5..........
实例中这一段偏移量为0x358~0x360 和 0x360~0x380,含义不是很懂,不过似乎参考书中也没有提及,以后用到了再说吧。
实例中这一段偏移量为0x380~0x398 和 0x398~0x3c8。这里保存了重定位相关的信息,这些信息描述了如何在链接或者运行时,对ELF目标文件的某部分内容或者进程镜像进行补充或者修改。
实例中这一段偏移量为0x3c8~0x3e2。这一节是可执行的进程初始化代码,进程进入主方法之前会运行这一段代码。
这一段通过objdump -d hello.out可以查看到相关的代码段:
00000000004003c8 <_init>:
4003c8: 48 83 ec 08 sub $0x8,%rsp
4003cc: 48 8b 05 25 0c 20 00 mov 0x200c25(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0>
4003d3: 48 85 c0 test %rax,%rax
4003d6: 74 05 je 4003dd <_init+0x15>
4003d8: e8 43 00 00 00 callq 400420 <__libc_start_main@plt+0x10>
4003dd: 48 83 c4 08 add $0x8,%rsp
4003e1: c3 retq
实例中这一段偏移量为0x3f0~0x420。这一节是过程链接表,包含了动态链接器调用从共享库导入的函数所必需的相关代码。
也可以用objdump来查看相关代码:
00000000004003f0 :
4003f0: ff 35 12 0c 20 00 pushq 0x200c12(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
4003f6: ff 25 14 0c 20 00 jmpq *0x200c14(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
4003fc: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400400 :
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28>
0000000000400410 <__libc_start_main@plt>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28>
实例中这一段偏移量为0x420~0x428。这一节提供了对共享库函数的访问入口,由动态链接器在运行时进行修改。
也可以用objdump来查看相关代码:
0000000000400420 <.plt.got>:
400420: ff 25 d2 0b 20 00 jmpq *0x200bd2(%rip) # 600ff8 <_DYNAMIC+0x1d0>
400426: 66 90 xchg %ax,%ax
保存了程序代码,在实例中偏移量为0x430~0x5b2
可以用objdump来查看:
0000000000400430 <_start>:
400430: 31 ed xor %ebp,%ebp
400432: 49 89 d1 mov %rdx,%r9
400435: 5e pop %rsi
400436: 48 89 e2 mov %rsp,%rdx
400439: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp
40043d: 50 push %rax
40043e: 54 push %rsp
40043f: 49 c7 c0 b0 05 40 00 mov $0x4005b0,%r8
.........
40059b: 5d pop %rbp
40059c: 41 5c pop %r12
40059e: 41 5d pop %r13
4005a0: 41 5e pop %r14
4005a2: 41 5f pop %r15
4005a4: c3 retq
4005a5: 90 nop
4005a6: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
4005ad: 00 00 00
00000000004005b0 <__libc_csu_fini>:
4005b0: f3 c3 repz retq
实例中偏移量为0x5b4~0x5bd,当程序终止时会运行这一段代码。
00000000004005b4 <_fini>:
4005b4: 48 83 ec 08 sub $0x8,%rsp
4005b8: 48 83 c4 08 add $0x8,%rsp
4005bc: c3 retq
实例中偏移量为0x5c0~0x5d0,保存了只读数据,对于这个程序的只读数据就是"hello world",可以用hexedit来查看:
000005C0 01 00 02 00 68 65 6C 6C 6F 20 77 6F 72 6C 64 00 ....hello world.
实例中偏移量为0x5d0~0x604和0x608 ~0x6fc。通过对相关资料的查找,这部分似乎是和异常处理有关,记录函数调用堆栈的,以后要是用到了再了解吧。
到这里程序段的部分也结束了,从这里到数据段的起点都是用0填充的了。
实例中偏移量为0xe10~0xe18与0xe18 ~0xe20。不过并不清楚这两个节是做什么用的,但是从名字上看可能与程序.init节和.fini节有某种联系。
实例中偏移量为0xe20~0xe28。又是一个不明物体emmm。
实例中偏移量为0xe28~0xff0。保存着动态链接信息。
实例中偏移量为0xff8~0xfff。保存着全局偏移表。
实例中偏移量为0x1000~0x1028。它和plt节一起提供了对导入的共享库函数的访问入口。可以看一下它的内容:
00001000 28 0E 60 00 00 00 00 00 00 00 00 00 00 00 00 00 (.`.............
00001010 00 00 00 00 00 00 00 00 06 04 40 00 00 00 00 00 ..........@.....
00001020 16 04 40 00 00 00 00 00
这个字符不可见,但是从数值上有三个有意义的数 0x600e28 0x400406 0x400416,分别表示一个数据段和两个代码:
0000000000400400 :
400400: ff 25 12 0c 20 00 jmpq *0x200c12(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400406: 68 00 00 00 00 pushq $0x0
40040b: e9 e0 ff ff ff jmpq 4003f0 <_init+0x28>
0000000000400410 <__libc_start_main@plt>:
400410: ff 25 0a 0c 20 00 jmpq *0x200c0a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400416: 68 01 00 00 00 pushq $0x1
40041b: e9 d0 ff ff ff jmpq 4003f0 <_init+0x28>
0x600e28则指向的是.dynamic节的内容。
实例中偏移量为0x1028~0x1038。它保存着初始化的全局变量等数据。
实例中偏移量为0x1038~0x1038…,它保存的是未进行初始化的全局数据,程序被加载时数据被初始化为0。
到这里数据段的部分也都结束了,剩下的应该是和节头自身相关的东西了。
实例中偏移量为0x1038~0x106d。这一节保存了版本控制信息,并且也是可见的:
00001030 00 00 00 00 00 00 00 00 47 43 43 3A 20 28 55 62 ........GCC: (Ub
00001040 75 6E 74 75 20 35 2E 34 2E 30 2D 36 75 62 75 6E untu 5.4.0-6ubun
00001050 74 75 31 7E 31 36 2E 30 34 2E 31 30 29 20 35 2E tu1~16.04.10) 5.
00001060 34 2E 30 20 32 30 31 36 30 36 30 39 00 00 00 00 4.0 20160609....
实例中偏移量为0x1070~0x16b8。这节保存了符号表,与ELF符号和重定位有关。
实例中偏移量为0x16b8~0x18ce。这部分保存了符号字符串表,表中的内容会被.symtab的ElfN_Sym结构中的st_name条目引用。
00 63 72 74 73 74 75 66 .........crtstuf
000016C0 66 2E 63 00 5F 5F 4A 43 52 5F 4C 49 53 54 5F 5F f.c.__JCR_LIST__
000016D0 00 64 65 72 65 67 69 73 74 65 72 5F 74 6D 5F 63 .deregister_tm_c
000016E0 6C 6F 6E 65 73 00 5F 5F 64 6F 5F 67 6C 6F 62 61 lones.__do_globa
000016F0 6C 5F 64 74 6F 72 73 5F 61 75 78 00 63 6F 6D 70 l_dtors_aux.comp
......
00001860 64 6C 65 00 5F 49 4F 5F 73 74 64 69 6E 5F 75 73 dle._IO_stdin_us
00001870 65 64 00 5F 5F 6C 69 62 63 5F 63 73 75 5F 69 6E ed.__libc_csu_in
00001880 69 74 00 5F 5F 62 73 73 5F 73 74 61 72 74 00 6D it.__bss_start.m
00001890 61 69 6E 00 5F 4A 76 5F 52 65 67 69 73 74 65 72 ain._Jv_Register
000018A0 43 6C 61 73 73 65 73 00 5F 5F 54 4D 43 5F 45 4E Classes.__TMC_EN
000018B0 44 5F 5F 00 5F 49 54 4D 5F 72 65 67 69 73 74 65 D__._ITM_registe
000018C0 72 54 4D 43 6C 6F 6E 65 54 61 62 6C 65 00 00 2E rTMCloneTable...
实例中偏移量为0x18ce~0x19da。这里保存的是节头字符串表,该字符串保存了每个节的节名。e_shstrndx中保存了.shstrtab的偏移量,前者在readelf -h hello.out的时候能够找到。
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 31
字符串表索引节头: 28
剩余还有地址段为0x19e0(19da向下取整)~0x21a0的部分
为了理解这部分,首先需要知道节头的结构体,这里我展示64位下的结构体:
typedef struct {
uint32_t sh_name;
uint32_t sh_type;
uint64_t sh_flags;
Elf64_Addr sh_addr;
Elf64_Off sh_offset;
uint64_t sh_size;
uint32_t sh_link;
uint32_t sh_info;
uint64_t sh_addralign;
uint64_t sh_entsize;
} Elf64_Shdr;
可以进行一个简单的计算:
(0x21a0-0x19e0)/64(节头项目大小)=31,也就是说,剩下的部分应该就是节头的内容。
可以看一下最后64个字节的内容:
00002160 09 00 00 00 03 00 00 00 00 00 00 00 00 00 00 00 ................
00002170 00 00 00 00 00 00 00 00 B8 16 00 00 00 00 00 00 ................
00002180 16 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00002190 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
在这里出现了熟悉的0x16b8,也就是.strtab的偏移量。这也说明最后的部分就是节头表的内容。
目标文件指的是.o文件,而可执行文件则指的是.out文件,通过对他们所拥有的节的对比也可以知道哪些节是一个可执行程序所必须的:
gcc -c hello.c
执行上面的编译命令就可以看到目录中出现了hello.o。
### 2.目标文件与可执行文件节头对比
可执行文件的文件头如下:
节头:
[号] 名称 类型 地址 偏移量
大小 全体大小 旗标 链接 信息 对齐
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000000000 00000040
000000000000001a 0000000000000000 AX 0 0 1
[ 2] .rela.text RELA 0000000000000000 000001f8
0000000000000030 0000000000000018 I 11 1 8
[ 3] .data PROGBITS 0000000000000000 0000005a
0000000000000000 0000000000000000 WA 0 0 1
[ 4] .bss NOBITS 0000000000000000 0000005a
0000000000000000 0000000000000000 WA 0 0 1
[ 5] .rodata PROGBITS 0000000000000000 0000005a
000000000000000c 0000000000000000 A 0 0 1
[ 6] .comment PROGBITS 0000000000000000 00000066
0000000000000036 0000000000000001 MS 0 0 1
[ 7] .note.GNU-stack PROGBITS 0000000000000000 0000009c
0000000000000000 0000000000000000 0 0 1
[ 8] .eh_frame PROGBITS 0000000000000000 000000a0
0000000000000038 0000000000000000 A 0 0 8
[ 9] .rela.eh_frame RELA 0000000000000000 00000228
0000000000000018 0000000000000018 I 11 8 8
[10] .shstrtab STRTAB 0000000000000000 00000240
0000000000000061 0000000000000000 0 0 1
[11] .symtab SYMTAB 0000000000000000 000000d8
0000000000000108 0000000000000018 12 9 8
[12] .strtab STRTAB 0000000000000000 000001e0
0000000000000015 0000000000000000 0 0 1
通过对比可得到如下结果:
.text 代码节
.rodata 只读数据节
.data 初始化的全局数据节
.bss 未初始化的全局数据节
.comment 版本控制信息
.eh_frame
.shstrtab 节头字符串表
.symtab 符号节
.strtab 符号字符串表节