还记得么?我们在很前面讲File header时,里面一个成员就是NumberOfSections,就是区段的个数,是7个,那么对应的区段表中的表项也应当有7项。
每一个区段表项的结构都如下所示:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 名字,没的说吧?
union {
DWORD PhysicalAddress; // 物理地址,保护模式下根本用不到
DWORD VirtualSize; // 区段的大小(内存中)
} Misc;
DWORD VirtualAddress; // 区段的虚拟内存地址
DWORD SizeOfRawData; // 在硬盘上该区段的大小,是FileAlignment的整数倍
DWORD PointerToRawData; // 文件中该区段的偏移
DWORD PointerToRelocations; // 属于该区段的重定位表项的偏移,没有就是0
DWORD PointerToLinenumbers; // 文件中的行号表项,没有就是0
WORD NumberOfRelocations; // 重定位的个数,EXE是0个
WORD NumberOfLinenumbers; // 行号表项的个数,没有是0
DWORD Characteristics; // 下详
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
其中IMAGE_SIZEOF_SHORT_NAME在winnt.h中定义等于8,即区段名Name最多8个字符。
第二个成员Misc是一个共用体。我们只需要关注VirtualSize成员即可,也就是加载器分配给该Section的内存大小,是SectionAlignment的倍数。
VirtualAddress和PointerToRawData分别表示内存和磁盘中的该段的起始地址,所以非常重要!
最后一个Characteristics是该段的一个重要属性,其值的含义及相关常数定义也非常复杂,具体的可以到MSDN上搜索IMAGE_SECTION_HEADER,然后点Characteristics成员,就会有详解,这里就不列出了,在分析下面的实例时,会提到一些其常见的含义。
好,下面我打算结合真实的区段表,逐步把每个常用区段都过一遍,其实内容很少,别紧张哈~
下图是我截取的testPE.exe的区段表
PICTURE MISSING
计算下IMAGE_SECTION_HEADER的长度=40字节,也就是图中的两行半长度,比较整齐好认,下文就不再标出它们的位置了。
.bss是指存放未初始化数据的区段,包括所有的static变量,尽管没有出现在截图中,但比较重要,不得不提。首先请一定注意这里的未初始化与C语言中的未初始化概念!C语言的未初始化指不为堆栈变量提供初值,而变量又会在内存中随机位置出现,产生随机的无意义的初值。这里的未初始化,即指全部填0。已初始化的概念一致。
.textbss是MS的Linker往PE中写入的区段。在正常的PE程序里,可以看到,它在文件中长度(RawData,原始数据)总是0,地址也是0,但在进程的虚拟地址空间中却占了1000h个字节。Characteristics为E0 00 00 A0 = IMAGE_SCN_MEM_EXECUTE|IMAGE_SCN_MEM_WRITE|IMAGE_SCN_CNT_CODE|IMAGE_SCN_CNT_UNINITIALIZED_DATA,意为可写可执行的代码段,不进行初始化。其实这个区段的出现是因为重定位映像基址比较困难,所以想通过这个空段来起到等同于重定位的作用。我分析下来,.textbss应该属于一种特殊的.text区段,代码段基地址BaseOfCode得到的是10000h,而.text起始地址是11000h,其差值1000h正好是.textbss的virtualsize。
.text。不用说的重要,放程序的代码。图中内存占用大小为00 00 34 A4,内存中地址为00 01 10 00,区段的文件大小为00 00 36 00,区段在文件的起始偏移为00 00 04 00,当中几个不管了,特征值Characteristics为60 00 00 20,查阅MSDN,得到IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE,意为该段是代码段,可以被执行,可以被读取。
.rdata,存放只读数据,比如字符串字面值,常量以及调试目录信息等。图中该区段在内存中,大小为00 00 1C 39,地址为00 01 50 00;在文件中,大小为00 00 1E 00,地址为00 00 3A 00;Characteristics是40 00 00 40,对应IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,已初始化且可读,但因没有IMAGE_SCN_MEM_WRITE故不可写。
.data,数据区段,除了上面的static变量,还有存储在堆栈段的自动变量,其他所有变量都在.data里,典型的有各类全局变量。图中该区段在内存中,大小为00 00 05 AC,地址为00 01 70 00;在文件中,大小为00 00 02 00,地址为00 00 58 00。Characteristics是C0 00 00 40,由MSDN,IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA,可读可写且被初始化过,C语言默认全局变量初始化为0。
.idata,所有输入的DLL函数信息。图中内存中,大小 00 00 08 4D,起始地址00 01 80 00,文件中,文件中,大小 00 00 0A 00,偏移00 00 5A 00。Characteristics同.data。我们会在稍后详细解析import table。
.edata这里没有,是输出函数用的。
.rsrc。PE的资源区段,包括程序所有资源信息,像什么图标、图片、AVI、字符串表、热键表、对话框、版本信息等等,你想放什么就有什么。图中内存中大小是00 00 0C 09,地址为00 01 90 00,文件中,大小是00 00 0E 00,地址为00 00 64 00。Characteristics同.rdata,意味着内存中资源区段是只读的。顺带提一下,编辑PE的资源,我个人比较喜欢eXescope,尽管原文中作者推荐resource hacker,各有所好吧!
.reloc。重定位区段,又称基址重定位表,每一个表项仅仅描述了一个需要被加上基址差(期待的基址与实际的基址,EXE是0)的地址(一个4kb页面的地址)。就拿原文的例子好了,EXE假设它的映像会被载入到0x10000地址处,然后它有个字符串起始偏移是0x14002。但由于某种原因,EXE被PE加载器赶到0x60000处去了,那么显然如果再以0x14002访问字符串就是大大的错误。解决办法是,在重定位区段里告诉系统,0x14002这个地址需要被加上一个基址的差值,这里明显等于0x60000-0x10000=0x50000,而字符串相对偏移是
0x14002-0x10000=0x4002,所以新的字符串地址是0x64002,这样就天下太平了。
图中.reloc区段在内存中大小是00 00 04 51,地址为00 01 A0 00,文件中,大小是00 00 06 00,地址为00 00 72 00。Characteristics为42 00 00 40,IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_DISCARDABLE,最后一个参数的意思是,如果需要的话,重定位区段可以被PE加载器无视而不被加载。通常EXE的这个区段是被无视的,但DLL由于无法确定会被系统映射到地址空间的何处,所以这个区段是很关键的。
区段这个部分就讲到这里,下一篇将讲述下输出区段(Export Section)。