使用readelf-S<executable>命令可以获取可执行文件的节信息。注意,是大写的S。可执行文件的主要节包括:
(1).text:可装载的节,用于存放编译过的程序机器码。
(2).data:可装载的节,包含全局变量的初始值。
(3).rodata:可装载的节,用于存放只读数据(包含常数),如printf()函数中的格式化字符串、switch语句的跳转表等。
(4).bss:一个空的可分配的节,用于存放未初始化的全局变量。C语言规定所有未初始化的全局变量初始值均为0,这样就可避免可执行空间的浪费。
(5).interp:包含动态链接器的路径名,即动态链接器的位置存储在.interp节。动态链接器本身也是一个共享对象文件。当一个依赖外部共享库的可执行程序被运行时,需要进行动态链接,以便在运行时定位程序中未被定义的外部符号,此时需要知道动态链接器的位置。
(6).symtab:符号表,其中存放的是在程序中定义和引用的与函数及全局变量相关的信息。
(7).rel.text:存放.text节需要修改的重定位位置列表。一般来说,在调用外部函数和使用外部全局变量的命令时,需要对命令的某些部分进行重定位。
(8).rel.data:存放模块定义和引用的全局变量重定位信息。一般来说,对于任何初始化的全局变量,若其初始化值是全局变量的地址或外部定义的函数,则需要进行重定位。
(9).debug:该节存放的是与调试相关的信息,在生成可执行文件时使用-g选项才有该节。该节是不被装载、不被分配内存空间的,软件的发行版本一般不包含该节。
(10).hash:动态库为了导出函数,通常需要使用该节。该节保存了一个用于查找符号的散列表,用于支持符号表的访问,能够提高搜索符号的速度。gcc可通过选项-hash-style=style设置链接器散列表类型。主要有三种类型:
①-hash-style=sysv:通过该设置,ELF文件会具有.hash节。该设置告知gcc使用DT_HASH而不是DT_GNU_HASH类型,使用的是比较老的Hash函数。
②-hash-style=gnu:通过该设置,ELF文件会具有.gnu.hash节。该设置告知gcc使用DT_GNU_HASH,使用的是比较新的Hash函数,跟老的Hash函数不兼容。使用该设置会使动态链接的速度提高大约50%。
③-hash-style=both:通过该设置,ELF文件会同时具有.hash节和.gnu.hash节。
默认的设置是-hash-style=sysv。使用不同的配置,ELF文件会具有不同的.hash节名字。可以使用命令readelf-S libxxx.so|grep“hash”来显示ELF文件支持的散列表类型,使用命令$gcc-dumpspecs|grep“hash”来显示编译器的内置规范:
具体的示例如下:
(11).init:该节包含有助于进程初始化代码的可执行命令。如果函数放在.init节中,当程序开始运行时,系统会安排在主程序入口点(在C程序中称为main()函数)之前执行.init节中的代码。
(12).fini:该节包含有助于进程终止代码的可执行命令。也就是说,在main()函数返回后,当程序正常退出时,系统将执行放置在.fini节中的代码。
此外,编译器可以使用.fini节来实现C++中的全局构造函数和析构函数。例如:
Listing 2.8 .init节和.fini节的测试程序initFinTest.c
Listing 2.9 .preinit节、.init节和.fini节的测试程序preinitTest.c
运行结果如下:
(13).init_array:.init_array节的测试程序如Listing 2.10所示。
Listing 2.10 .init-array节的测试程序init-arrayTest.c
输入命令:
测试结果为:
说明:前三行的输出对应.init_array节中的函数执行,最后一行的输出对应main()函数的执行。
可以通过如下命令查看存放在.init_array节中的函数名:
由于.init_array节、.fini_array节分别存放程序执行前以及执行后的代码,因此攻击者可以将恶意代码的地址放置在.init_array节、.fini_array节,当程序运行时或程序运行结束前触发恶意代码的执行。
(14).plt(Procedure Linkage Table,过程链接表):用于在调用外部函数时确定其地址的表,可以与.got.plt节一起用于在程序运行时确定被调用外部函数的真实地址。
(15).got(Global Offsets Table,全局偏移表):用于在程序运行时确定外部全局变量的地址,由动态链接器在程序运行时确定外部全局变量的真实地址。
(16).got.plt:用于在链接时确定外部函数的地址,可由动态链接器在程序运行时确定被调用外部函数的真实地址。例如: