PE文件学习笔记(二):Section Table解析

来源:互联网 发布:qq三国js点智力 编辑:程序博客网 时间:2024/05/21 18:02

1、Section Table结构解析:

Section Table(节表)是记录PE文件中各个节的详细信息的集合,其每个成员是struct _IMAGE_SECTION_HEADER结构体,即节表是一个结构体数组来维护,属于线性结构。而节表的相对起始位置为:紧接着可选PE表。即:DOS头 + 中间空闲及垃圾数据 + NT头(三部分:4字节签名+标准PE头20字节+可选PE头)

#define IMAGE_SIZEOF_SIGNATURE 4
#define IMAGE_SIZEOF_FILE_HEADER 20

用变量描述节标配偏移地址为(这是相对于文件首/ImageBase的偏移量):

SectionTableStart =
_IMAGE_DOS_HEADER.e_lfanew +
IMAGE_SIZEOF_SIGNATURE +
IMAGE_SIZEOF_FILE_HEADER +
_IMAGE_FILE_HEADER.SizeOfOptionalHeader

一个结构体成员如下:

#define IMAGE_SIZEOF_SHORT_NAME              8typedef struct _IMAGE_SECTION_HEADER{    0X00 BYTE    Name[IMAGE_SIZEOF_SHORT_NAME]; //节(段)的名字.text/.data/.rdata/.cmd等。                                                //由于长度固定8字节,所以可以没有\0结束符,因此不能用char *直接打印    0X08 union{            DWORD   PhysicalAddress;        //物理地址            DWORD   VirtualSize;            //虚拟大小    }Misc;//存储的是该节在没有对齐前的真实尺寸,可改,不一定准确(可干掉)    0X0C DWORD   VirtualAddress;            //块的RVA,相对虚拟地址    0X10 DWORD   SizeOfRawData;             //该节在文件对齐后的尺寸大小(FileAlignment的整数倍)    0X14 DWORD   PointerToRawData;          //节区在文件中的偏移量    //0X18 DWORD   PointerToRelocations;    //重定位偏移(obj中使用)    //0X1C DWORD   PointerToLinenumbers;    //行号表偏移(调试用)    //0X20 WORD    NumberOfRelocations;     //重定位项目数(obj中使用)    //0X22 WORD    NumberOfLinenumbers;     //行号表中行号的数目    0X24 DWORD   Characteristics;           //节属性(按bit位设置属性)} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;#define IMAGE_SIZEOF_SECTION_HEADER          40

一个结构体大小为40字节(IMAGE_SIZEOF_SECTION_HEADER),因此节表的大小为节表元素个数乘以结构体大小为:_IMAGE_FILE_HEADER.NumberOfSections * IMAGE_SIZEOF_SECTION_HEADER。而整个头大小(包含节表)为:

SizeOfHeaders = SectionTableStart + SizeOfSectionTable =
_IMAGE_DOS_HEADER.e_lfanew +
IMAGE_SIZEOF_SIGNATURE +
IMAGE_SIZEOF_FILE_HEADER +
_IMAGE_FILE_HEADER.SizeOfOptionalHeader +
(_IMAGE_FILE_HEADER.NumberOfSections * IMAGE_SIZEOF_SECTION_HEADER)

综合来说: PE文件的DOS Header、PE Header、Section Table、Sections的具体分布情况以及Section Table的一个struct _IMAGE_SECTION_HEADER 成员的细节描述如下图所示:
这里写图片描述
下来我们就具体的成员进行测试与分析(采用和DOS头与PE头解析 相同的exe文件进行测试):

我们知道了NT的偏移地址为:e_lfanew = 00000100H,而可选PE头的大小为E0H。所以SectionTableStart = 100H + 4H + 14H + E0H = 0000 01F8H。即节表起始偏移地址为:0000 01F8H,如下所示:
这里写图片描述
我们在标准PE头_IMAGE_FILE_HEADER中获取的NumberOfSections 大小为08H,所以总共有八个节(Sections),节表中也有8个结构体元素,每个占用40字节。 SizeOfHeaders = SectionTableStart + SizeOfSectionTable = 0000 01F8H + (40*8)(D) = 0000 01F8H + 140H = 338H。由于SizeOfHeaders是对齐后的大小,而FileAlignment = 200H,所以SizeOfHeaders补齐后为400H。如下图所示:

这里写图片描述

0000 0400H前的0均是用来补齐的。我们第一个节的名字为8字节:2E 74 65 78 74 00 00 00,即“.text…”。VirtualSize大小为4个字节:000FDF2CH,不是200H的倍数,所以补齐需要到000FE000H。而其节首地址为:PointerToRawData(第5个元素)= 00000400H,即紧接着节表对齐处开始。则第一节结束地址为:0000 0400H + 000F E000H = 000FE400H(即第四个元素SizeOfRawData:文件对齐后的尺寸)。如下所示:

这里写图片描述

FE400H前面的0是用来对齐的内存填充的。而查看节表第二个元素的第5个成员,得知第二节开始地址为000FE400H,是紧接着对齐后的第一节的,依次类推。由于查看时看的是硬盘上的文件,所以VirtualAddress成员没有意义,但是在内存中的分析方式和硬盘上PE文件是一样的,需要注意的是ImageBase(硬盘上基址是0)和SectionAlignment(硬盘上是FileAlignment)两个关键点。

除了exe中不太会用到几个成员外还有最后一个属性成员Characteristics,其常见的属性情况如下所示:
这里写图片描述
而第一节的属性为60 00 00 20 = 40 00 00 20H | 20 00 00 00H | 00 00 00 20H = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE,即.text包含可执行代码,可读可执行。第二节与第一节相同,第三节为C0000040即40000000H | 80000000H | 00000040H,“.data”为可读可写已初始化;第四节“.bss”为C0000000,即可读可写。之后的节均是相同的分析方式。

注意:SizeOfRawData的大小在硬盘上一定不小于Misc,但在加载进内存中运行时则不一定大于Misc。
比如:在该节有一个未初始化的数组char a[1000],在编译连接完成生成.exe文件后并没有分配内存(因为是未初始化的),对应在硬盘上就是没有预留的一块地址空间初始化为0。SizeOfRawData是文件对齐的倍数,文件对齐时参考的大小是不包含未初始化的a[1000],可能大小是170则对齐是200,SizeOfRawData(200)在硬盘上大于Misc(170)。
而在真正加载进内存运行时,a[1000]就要分配1000字节,Misc就变成了(1170)超过了SizeOfRawData的大小(200)。产生这一特点的原因就是:SizeOfRawData是在编译连接完成后就确定的,是不可变的。而Misc是在硬盘上内存里随时可能发生变化的。更本质一点的原因是:未初始化的变量在装载时才分配空间,如果不存在这种动态变化的且无人为修改Misc,则SizeOfRawData定不小于Misc。

2、代码解析节表:

void PETool::print_SECTIONS_TABEL(){    fprintf(fp_peMess, "节表(Section Table):\n");    IMAGE_SECTION_HEADER * section = section_header;    char name[IMAGE_SIZEOF_SHORT_NAME + 1] = {0};    for(int i = 0; i < sectionNum; i++){        memset(name, 0, IMAGE_SIZEOF_SHORT_NAME + 1);        memcpy(name, section+i, IMAGE_SIZEOF_SHORT_NAME);        fprintf(fp_peMess,"\tsection[%d]:\n",i);        fprintf(fp_peMess, "\t\tName                :[%s]\n",name);        fprintf(fp_peMess, "\t\tVirtualSize:        :[%08X]\n",section[i].Misc.VirtualSize);        fprintf(fp_peMess, "\t\tVirtualAddress      :[%08X]\n",section[i].VirtualAddress);        fprintf(fp_peMess, "\t\tSizeOfRawData       :[%08X]\n",section[i].SizeOfRawData);        fprintf(fp_peMess, "\t\tPointerToRawData    :[%08X]\n",section[i].PointerToRawData);        fprintf(fp_peMess, "\t\tPointerToRelocations:[%08X]\n",section[i].PointerToRelocations);        fprintf(fp_peMess, "\t\tPointerToLinenumbers:[%08X]\n",section[i].PointerToLinenumbers);        fprintf(fp_peMess, "\t\tNumberOfRelocations :[%04X]\n",section[i].NumberOfRelocations);        fprintf(fp_peMess, "\t\tNumberOfLinenumbers :[%04X]\n",section[i].NumberOfLinenumbers);        fprintf(fp_peMess, "\t\tCharacteristics     :[%08X]\n",section[i].Characteristics);    }}