ELF的节(section)
节(section)不是段(segment)。又说了一遍,主要是看到很多文章把节称为段,他自己可能懂,但是会误导新手,会让新手混淆概念,以致于学习的时候顺着错误的方向去研究了。
另外节头或者说节头表,这个与节(section)也不要混淆了,节头表里面是一条一条的描述信息,这些信息记录了我们应该在哪个地方可以找到节(section),说白了就是节(section)的信息描述。
上一篇文章我们研究了ELF的节头。现在我们来看看节头里的节描述信息条目所对应的每个节(section)的具体位置和保存的内容。也就是来看看具体的节。
已知我们的hello,world的示例文件是30个节,那么我们挨个看看
第1个节(section)。
从节头表的偏移地址14688开始取64个字节就是第1个节的描述信息
即无效的节,节描述信息里的其它字段都没有定义,都是0。
第2个节(section)。
14688+64=14752,从14752开始取64个字节就是第2个节的描述信息
sh_name。占4个字节,这里是0x1b,十进制是27,说明在节头字符串表27个字节开始是该节的名称值,那么我们看看名称是啥,说在节头字符串表的27个字节开始是该节的名称,那么节头字符串表在哪儿呢?所以前提是得知道节头字符串表的位置,还记得在ELF文件头的文章里我们分析是发现文件头的62个字节开始取2个字节的内容保存的是节头表中的节头字符串表描述条目的索引值,那么我们看看保存的索引值是多少
可以看到是0x1d,十进制是29,一共30个条目,索引范围就是0-29,索引是29,即第30个节开始是节头字符串表的描述信息,根据描述信息就可以找到节头表的位置,节头表中第30个条目,我们看看节头表中的第30条
已知示例文件的节头表的偏移地址是14688,节头表每个条目大小是64,总共有30个条目,第30个条目是节头字符串表的描述条目。那么我们计算一下第30个条目的偏移量:(14688+30*64)-64=16544,即16544个字节开始是第30个条目,大小是64个字节
从图中可以看到节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,我们用xxd从14423处取263个字节就是节头字符串表
可以看到的确都是一堆字符串内容,那么我们现在要取的是第2个节(section)的节名称,已知第二个节的节名称在该表中的索引是27,那么我继续用xxd指令取一下
14423+27=14450
以空字符为终止,所以是2e696e74657270,ascii码就是.interp,说明第2个节的名称是.interp
既然已知第2个节是.interp节,那么节数据内容是什么呢?
已知第2个节如节描述信息里所示的,偏移地址是0x2a8,十进制是680,大小是0x1c,十进制是28,我们用xxd指令取出来看看内容是什么
原来该节保存的是动态链接器的系统路径
第3个节(section)。
14752+64=14816,从14816开始取64个字节就是第3个节的描述信息
sh_name是0x23,十进制是35,说明该节的名称在节头字符串表中的偏移量是35个字节
我们在节头字符串表中看看35给字节开始,直到遇到空字符的内容是什么
上面分析第2个节时已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的35个字节开始的地方取数据直到空字符串结束
14423+35=14458,263-35=228
内容是.note.gnu.build-id,说明第3个节(section)的名称是.note.gnu.build-id
该节的数据内容是什么呢?我们继续用xxd指令看看
已知该节的文件偏移地址是0x2c4,十进制是708,大小是0x24,十进制是36
存储的内容是编译器相关的信息
第4个节(section)。
14816+64=14880,从14880开始取64个字节就是第4个节的描述信息
sh_name是0x36,十进制是54,说明该节的名称在节头字符串表中的偏移量54
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的54个字节开始的地方取数据直到空字符串结束
14423+54=14477,263-54=209
内容是.note.ABI-tag,说明第4个节的名称是.note.ABI-tag节
.note.ABI-tag节的存储的内容是什么呢?我们继续用xxd指令看看
该节的描述信息里给出了偏移地址和大小,偏移地址是0x2e8,十进制是744,大小是0x20,十进制是32
该节存放的内容是二进制文件ABI信息
第5个节(section)。
14880+64=14944,从14944开始取64个字节就是第5个节的描述信息
sh_name是0x44,十进制是68,说明该节的名称在节头字符串表中的偏移量是68
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的68个字节开始的地方取数据直到空字符串结束
14423+68=14491,263-68=195
内容是.gnu.hash,说明第5个节是.gnu.hash节
继续看看.gnu.hash节的内容是什么
该节的描述信息里给出了偏移地址和大小,偏移地址是0x308,十进制是776,大小是0x24,十进制是36
内容是一个哈希散列表,哈希散列表由4个部分组成:
header、boolm filter、hash buckets和hash values。该节的设计是对原生的.hash节的增强和改进,目的是用于快速查找符号的。后续专门写一篇来仔细拆分.gnu.hash节
第6个节(section)。
14944+64=15008,从15008开始取64个字节就是第6个节的描述信息
sh_name。sh_name是0x4e,十进制是78,说明该节的名称在节头字符串表中的偏移量是78
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的78个字节开始的地方取数据直到空字符串结束
14423+78=14501,263-68=185
该节名称是.dynsym,说明第6个节是.dynsym节,该节是动态符号表节
接下来继续看看.dynsym节的内容
已知.dynsym节的偏移量是0x330,十进制是816,大小是0xa8,十进制是168
这样看看不出是什么内容,后续专门来拆分看看动态符号表节的构成。
.dynsym节的数据内容是一个动态符号表,数据内容其实是从共享库导入的动态符号信息,表中有多个条目,每个条目有固定大小。
注:.dynsym节保存的符号其实是.symtab中的符号的子集。即.symtab中的符号包含.dynsym节的符号。区别是.dynsym节的符号一般都是共享库中的符号信息,而.symtab节中的符号保存了所有的符号。
第7个节(section)。
15008+64=15072,从15072开始取64个字节就是第7个节的描述信息
sh_name。sh_name是0x56,十进制是86,说明该节的名称在节头字符串表中的偏移量是86
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的86个字节开始的地方取数据直到空字符串结束
14423+86=14509,263-86=177
可以看到该节是.dynstr节,.dynstr节是动态库号字符串表节,下面我们看看里面的数据
已知.dynstr节的偏移量是0x3d8,十进制是984,大小是0x82,十进制是130
可以看到里面都是字符串,且是动态库中的符号字符串,每个符号以空字符结束
第8个节(section)。
15072+64=15136,从15136开始取64个字节就是第8个节的描述信息
sh_name。sh_name是0x5e,十进制是94,说明该节的名称在节头字符串表中的偏移量是94
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的94个字节开始的地方取数据直到空字符串结束
14423+94=14517,263-94=169
该节的名称是.gnu.version,说明是.gnu.version节,符号版本表。下面我们看看.gnu.version节中的数据
已知.gnu.version节的偏移量是0x45a,十进制是1114,大小是0xe,十进制是14
该节中包含一个版本信息索引表,每个表项的长度为2个字节,表项的数量与动态符号表.dynsym中所有符号的数目相同,并且一一对应。
具体数据的意思后续专门来拆分符号版本表,即.gnu.version节
第9个节(section)。
15136+64=15200,从15200开始取64个字节就是第9个节的描述信息
sh_name。sh_name是0x6b,十进制是107,说明该节的名称在节头字符串表中的偏移量是107
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的107个字节开始的地方取数据直到空字符串结束
14423+107=14530,263-107=156
该节名称是.gnu.version_r,说明是.gnu.version_r节。下面我们一起来看看该节的数据
已知.gnu.version_r节的偏移量是0x468,十进制是1128,大小是0x20,十进制是32
该节的数据是定义了符号版本所要求的元素
第10个节(section)。
15200+64=15264,从15264开始取64个字节就是第10个节的描述信息
sh_name。sh_name是0x7a,十进制是122,说明该节的名称在节头字符串表中的偏移量是122
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的122个字节开始的地方取数据直到空字符串结束
14423+122=14545,263-122=141
该节名称是.rela.dyn,说明该节是.rela.dyn节。下面看看该节的内容
已知.rela.dyn节的偏移量是0x488,十进制是1160,大小是0xc0,十进制是192
数据内容是包含带有显式加数的重定位条目,每个条目固定大小(24个字节)。后续专门来拆分解析重定位节的构成。
第11个节(section)。
15264+64=15328,从15328开始取64个字节就是第11个节的描述信息
sh_name。sh_name是0x84,十进制是132,说明该节的名称在节头字符串表中的偏移量是132
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的132个字节开始的地方取数据直到空字符串结束
14423+132=14555,263-132=131
该节名称是.rela.plt,说明第11个节是.rela.plt节。
已知.rela.plt节的偏移量是0x548,十进制是1352,大小是0x18,十进制是24
重定位是连接符号引用与符号定义的过程。例如,程序调用函数时,关联的调用指令必须在执行时将控制权转移到正确的目标地址。可重定位文件必须包含说明如何修改其节内容的信息。通过此信息,可执行文件和共享目标文件可包含进程的程序映像的正确信息。重定位项即是这些数据。
第12个节(section)。
15328+64=15392,从15392开始取64个字节就是第12个节的描述信息
sh_name。sh_name是0x8e,十进制是142,说明该节的名称在节头字符串表中的偏移量是142
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的142个字节开始的地方取数据直到空字符串结束
14423+142=14565,263-142=121
可以看到第12个节的名称是.init,这是.init节
已知.init节的偏移量是0x1000,十进制是4096,大小是0x17,十进制是23
这23个字节内容是什么呢?ELF手册的解释是:本节包含有助于处理初始化代码的可执行指令。 当程序开始运行时,系统会在调用主程序入口点之前执行本节中的代码。 此部分的类型为SHT_PROGBITS。 使用的属性是SHF_ALLOC和SHF_EXECINSTR。
说明是保存的初始化代码,程序入口代码执行前这里保存的代码会先执行。
第13个节(section)。
15392+64=15456,从15456开始取64个字节就是第13个节的描述信息
sh_name。sh_name是0x89,十进制是137,说明该节的名称在节头字符串表中的偏移量是137
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的137个字节开始的地方取数据直到空字符串结束
14423+137=14560,263-137=126
是.plt,说明第13个节是.plt节
已知.plt节的偏移量是0x1020,十进制是4128,大小是32
该节的数据是一个过程链接表,后续专门来拆分过程链接表。
第14个节(section)。
15456+64=15520,从15520开始取64个字节就是第14个节的描述信息
sh_name。sh_name是0x94,十进制是148,说明该节的名称在节头字符串表中的偏移量是148
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的148个字节开始的地方取数据直到空字符串结束
14423+148=14571,263-148=115
可以看到该节是.plt.got节,下面看看该节的内容
已知该节的描述信息里记录着该节的偏移量是0x1040,十进制4160,大小是0x8,十进制是8
暂时不知道.plt.got节的数据是什么,随着时间推移.plt.got节我会有个交代的
第15个节(section)。
15520+64=15584,从15584开始取64个字节就是第15个节的描述信息
sh_name。sh_name是0x9d,十进制是157,说明该节的名称在节头字符串表中的偏移量是157
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的157个字节开始的地方取数据直到空字符串结束
14423+157=14580,263-157=106
该节是.text节。.text节是保存程序代码指令的节
.text节的偏移量是0x1050,十进制是4176,大小是0x161,十进制是353
该节保存的都是程序代码指令,很明显偏移量就是程序的入口代码。31ed,对应的是xor ebp,ebp
第16个节(section)。
15584+64=15648,从15648开始取64个字节就是第16个节的描述信息
sh_name。sh_name是0xa3,十进制是163,说明该节的名称在节头字符串表中的偏移量是163
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的163个字节开始的地方取数据直到空字符串结束
14423+163=14586,263-163=100
.fini,第16个节是.fini节。
.fini节的偏移量是0x11b4,十进制是4532,大小是0x9,十进制是9
本节保存的是有助于进程终止代码的可执行指令。当程序正常退出时,系统将安排执行本节中的代码。此节属于SHT U PROGBITS类型。使用的属性是SHF_ALLOC和SHF_EXECINSTR。
第17个节(section)。
15648+64=15712,从15712开始取64个字节就是第17个节的描述信息
sh_name。sh_name是0xa9,十进制是169,说明该节的名称在节头字符串表中的偏移量是169
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的169个字节开始的地方取数据直到空字符串结束
14423+169=14592,263-169=94
可以看到是.rodata,第17个节是.rodata节
.rodata节的偏移量是0x2000,十进制是8192,大小是0x11,十进制是17
.rodata节保存的是只读数据,例如代码中的字符串等等。
第18个节(section)。
15712+64=15776,从15776开始取64个字节就是第18个节的描述信息
sh_name。sh_name是0xb1,十进制是177,说明该节的名称在节头字符串表中的偏移量是177
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的177个字节开始的地方取数据直到空字符串结束
14423+177=14600,263-177=86
是.eh_frame_hdr,说明第18个节是.eh_frame_hdr节。
该节的偏移量是0x2014,十进制是8212,大小是0x3c,十进制是60
该节对应PT_GNU_EH_FRAME段(segment),该节保存的内容是异常处理信息的位置和大小等数据。
第19个节(section)。
15776+64=15840,从15840开始取64个字节就是第19个节的描述信息
sh_name。sh_name是0xbf,十进制是191,说明该节的名称在节头字符串表中的偏移量是191
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的191个字节开始的地方取数据直到空字符串结束
14423+191=14614,263-191=72
.eh_frame,说明第19个节是.eh_frame节
.eh_frame节的偏移量是0x2050,十进制是8272,大小是0x108,十进制是264
保存的内容也是跟异常处理信息,具体.eh_frame节和.eh_frame_hdr节的区别暂时未知
第20个节(section)。
15840+64=15904,从15904开始取64个字节就是第20个节的描述信息
sh_name。sh_name是0xc9,十进制是201,说明该节的名称在节头字符串表中的偏移量是201
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的201个字节开始的地方取数据直到空字符串结束
14423+201=14624,263-201=62
是.init_array,说明第20个节是.init_array节。
该节的偏移量是0x2de8,十进制是11752,大小是0x8,十进制是8
.init_array节保存的是指向一些初始化代码(例如静态构造函数)的指针
第21个节(section)。
15904+64=15968,从15968开始取64个字节就是第21个节的描述信息
sh_name。sh_name是0xd5,十进制是213,说明该节的名称在节头字符串表中的偏移量是213
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的213个字节开始的地方取数据直到空字符串结束
14423+213=14636,263-213=50
是.fini_array,说明第21个节是.fini_array节
偏移量是0x2df0,十进制是11760,大小是8
.fini_array节保存的是数组,数组中的函数会在程序运行完毕后执行
第22个节(section)。
15968+64=16032,从16032开始取64个字节就是第22个节的描述信息
sh_name。sh_name是0xe1,十进制是225,说明该节的名称在节头字符串表中的偏移量是225
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的225个字节开始的地方取数据直到空字符串结束
14423+225=14648,263-225=38
.dynamic,说明第22个节是.dynamic节
偏移量是0x2df8,十进制是11768,大小是0x1e0,大小是480
该节对应PT_DYNAMIC段(segment),数据是动态链接文件所必须的一些信息。暂不做讨论,后面会利用本示例文件专门进行拆解。
第23个节(section)。
16032+64=16096,从16096开始取64个字节就是第23个节的描述信息
sh_name。sh_name是0x98,十进制是152,说明该节的名称在节头字符串表中的偏移量是152
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的152个字节开始的地方取数据直到空字符串结束
14423+152=14575,263-152=111
.got,第23个节是.got节
偏移量是0x2fd8,十进制是12248,大小是0x28,十进制是40
该节的数据全部是0
第24个节(section)。
16096+64=16160,从16160开始取64个字节就是第24个节的描述信息
sh_name。sh_name是0xea,十进制是234,说明该节的名称在节头字符串表中的偏移量是234
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的234个字节开始的地方取数据直到空字符串结束
14423+234=14657,263-234=29
.got.plt,说明第24个节是.got.plt节
偏移量是0x3000,十进制是12288,大小是0x20,十进制是32
该节保存的数据是一个全局偏移表,什么是全局偏移表暂不做讨论,后续专门拆解
第25个节(section)。
16160+64=16224,从16224开始取64个字节就是第25个节的描述信息
sh_name是0xf3,十进制是243,说明该节的名称在节头字符串表中的偏移量是243
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的243个字节开始的地方取数据直到空字符串结束
14423+243=14666,263-243=20
可以看到第25个节是.data节,偏移量是0x3020,十进制是12320,大小是0x10,十进制是16
该节保存的是初始化的全局变量等等数据,该节被包含在data段中,.data节不是data段,但是被包含在data段(segment)中。
第26个节(section)。
16224+64=16288,从16288开始取64个字节就是第26个节的描述信息
sh_name是0xf9,十进制是249,说明该节的名称在节头字符串表中的偏移量是249
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的249个字节开始的地方取数据直到空字符串结束
14423+249=14672,263-249=14
第26个节是.bss节,偏移量是0x3030,十进制是12336,大小是8
.bss节保存的是未进行初始化的全局数据,也是data段的一部分,程序加载是被初始化为0,不保存实际数据,但可以在程序执行期间进行赋值。
第27个节(section)。
16288+64=16352,从16352开始取64个字节就是第27个节的描述信息
sh_name是0xfe,十进制是254,说明该节的名称在节头字符串表中的偏移量是
254
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的254个字节开始的地方取数据直到空字符串结束
14423+254=14677,263-254=9
第27个节是.comment节,偏移量是0x3030,十进制是12336,大小是0x26,十进制是38
保存的是版本控制信息
第28个节(section)。
16352+64=16416,从16416开始取64个字节就是第28个节的描述信息
sh_name是1,说明该节的名称在节头字符串表中的偏移量是1
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的1个字节开始的地方取数据直到空字符串结束
14423+1=14424,263-1=262
第28个节是.symtab节
偏移量是0x3058,十进制是12376,大小是0x600,十进制是1536
该节保存的是一个符号表,表中每个条目的大小是固定的24个字节,后面会专门拆解。
第29个节(section)。
16416+64=16480,从16480开始取64个字节就是第29个节的描述信息
sh_name是9,说明该节的名称在节头字符串表中的偏移量是9
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的9个字节开始的地方取数据直到空字符串结束
14423+9=14432,263-9=254
第29个节是.strtab节,偏移量是0x3658,十进制是13912,大小是0x1ff,十进制是511
该节保存的是一个符号字符串表,与.symtab节相关联,.symtab节中的st_name的值就是该节的索引值,通过该值即可找到符号名称。
第30个节(section)。
16480+64=16544,从16544开始取64个字节就是第30个节的描述信息
sh_name是0x11,十进制是17,说明该节的名称在节头字符串表中的偏移量是17
已知节头字符串表的偏移量是0x3857,十进制是14423,大小是0x107,十进制是263,现在我们要从这个表里的17个字节开始的地方取数据直到空字符串结束
14423+17=14440,263-17=246
第30个节是.shstrtab节,即节头字符串表,偏移量是0x3857,十进制是14423,大小是0x107,十进制是263
到这儿30个节都已经拆分完毕了。
可以看到第30个节的描述信息条目的偏移量是16544,条目大小是64,16544+64=16608,16608正好是该示例文件的大小
说明一路从文件头的拆分走到这儿整个示例文件的结构已经解剖完毕了。