一、节表
1.节表引入
-
我们知道一个PE文件的结构是由DOS头 + PE标记 + 标准PE头 + 可选PE头 + 节表 + 多个节构成
-
那么一个可执行文件分的多个节在硬盘上和内存中应该从哪个地址位置开始存储,到哪里结束等都需要一个东西来管理、来记录。这个东西就是节表
-
节表相当于各个节(.text、.idata等)的一个目录;而DOS和NT头相当于对整个文件的一个描述
2.定位节表位置与计算个数
-
节表是表,肯定不止一个数。一个PE文件有多少个节,就有多少个节表!每一个节表的大小是确定的,40字节
-
如何确定有多少个节:可以通过标准PE头中的NumberOfsections字段的值来确定;确定了有多少个节,就确定了有多少一个节表,一个节表记录管理一个节的信息
-
一个PE文件从哪里开始是节表(硬盘上的地址):DOS头大小 + 垃圾空位 + PE签名大小 + 标准PE头大小 + 可选PE头大小(需要查);我们知道DOS头大小固定为64字节;PE签名大小为4字节;标准PE头大小固定为20字节;可选PE头大小可以通过标准PE头中的SizeOfOptionalHeader字段的值来确定
所以:e_lfanew + 4 + 20 + SizeOfOptionalHeader = 节表开始地址
-
如果文件运行装载到内存中节表在4GB内存中的地址要加上imagebase的值,才是节表真正在内存中的起始地址
3.节表结构
-
每一个节表的结构是一个结构体类型的,长度固定为0x28,即40字节。如下:
#define IMAGE_SIZEOF_SHORT_NAME 8 //宏定义 typedef struct _IMAGE_SECTION_HEADER{ BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; * //每一个节都可以取一个名字,最大长度为8字节 union{ DWORD PhysicalAddress; DWORD VirtualSize; }Misc; * //Misc就是此联合体类型的变量 DWORD VirtualAddress; * DWORD SizeOfRawData; * DWORD PointerToRawData; * DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; * } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; -
一个节对应一个节表,即一个节表由一个结构体类型的节表记录信息。节表数据紧接着可选PE头数据后面,节表中会循环上述的结构,因为一个节就对应一个结构,且这些数据都是挨着顺序存放的

-
即一个节表中记录了此.exe所有的节的信息,每一个节都用一个结构体来存储信息,所以所有的节对应的节表组合在一起就构成了PE文件的节表结构
二、节表每个字段的含义
1.Name[IMAGE_SIZEOF_SHORT_NAME]
- 8个字节,一般情况下是以"\0"结尾的ASCII码来表示节的名称,名字可以自定义(一般是编译器加的)。我们知道数组元素是从最后一个元素倒着入栈的,所以从低地址往高地址分别是从数组的第一个元素到最后一个元素
- 容易出现的安全问题:
- 因为Name数组的长度最大为8字节,如果定义节的名称为
.text:.的ASCII码为0x2E;t的ASCII码为0x74;e的ASCII码为0x65;x的ASCII码为0x78。所以Name数组中的数据为2E 74 65 78 74 00 00 00,一共8字节,如果此时用一个指针指向Name数组首地址,即char* np = Name所在的地址,接着使用printf("%s",np);来打印此节表的名字,那么由于使用%s和char*的方式来访问一个数组会从数组首地址一直打印直到遇到\0,即0x00就结束打印,所以此时就会打印出来.text,没有任何问题 - 但是如果我们自定义名字或者编译器帮我们定义的名字长度等于8,把这8位全占了,没有给
\0留位置存储。比如.abcdefg,每个字符转换成1字节的ASCII码,最后Name数组中的数据为2E 61 62 63 64 65 66 67,一共8字节。那么如果我们还是使用char* p = Name的起始地址,使用printf("%s",p);来打印名字,就会有越界问题,因为此时数组由于没有\0结尾了,那么就会接着把Name后面的内存中的数据打印出来,直到遇到一个0x00为止才停止打印。那么打印的名字就可能是.abcdefg癑5J@.??.等乱码 - 解决方法:自己定义一个==
char arr[9] = "";==然后使用库函数strncpy(arr,name,8);,或者自己写一个循环一个一个赋值进我们自定义的数组中,最后一位补\0即可,然后使用%s的方法打印我们自定义的数组中的值即可
- 因为Name数组的长度最大为8字节,如果定义节的名称为
- 操作系统也是通过这种方法来处理8字节长度的名字:即8字节名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理
2.Misc
-
表示该节装入内存时在对齐前的真实尺寸:联合体类型变量,大小为4字节。该值改了对程序的运行没有影响,如果一个程序没被修改过,那这个值就是准确的,如果被其他的软件加工过,就可能变的不准确,但不影响程序运行
-
Misc.VirtualSize的值准确的是:节在内存中没有对齐时的大小(为什么一定要是在内存中的,详见day30.1)
-
为什么定义成联合体,因为有些编译器或者软件喜欢用
PhysicalAddress这个变量名表示,有些又喜欢用VirtualSize这个变量名表示,那么为了两个都可以使用,而且共用一个内存不占用多余的内存,就使用联合体,想使用Phys

本文详细介绍了PE文件结构中的节表,包括节表的位置计算、节表结构、字段含义以及如何手动解析节表。通过示例分析了节表中的Name、Misc、VirtualAddress、SizeOfRawData等字段,并解释了Characteristics字段如何反映节的属性。此外,还提供了手动解析节表的代码示例。
最低0.47元/天 解锁文章
593

被折叠的 条评论
为什么被折叠?



