节表,又叫区段表,区段表是我们直接探索的最后位置了,区段是不需要直接解析的地方。
区段表存储着PE文件的一些属性,区段表是一个由若干个结构体依次排列组成的,每一个结构体代表着PE文件主体中的一段数据的属性,也就是每一个区段头都对应着PE文件主体的一段数据,这段数据叫做节或者叫区段,区段表规定了区段(节)的属性。
下面是节的定义
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//区段的名字
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc; //这个区段的大小
DWORD VirtualAddress;//这个区段起始的相对虚拟地址(RVA)
DWORD SizeOfRawData; //区段在文件中的大小
DWORD PointerToRawData;//区段的偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;//这一区段的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
区段表是由多个这样的结构体组成的,以一个全是0的结构体结尾
关于区段(节)名字的规矩如下 :
.text :一般是代码段
.data :一般是数据段
.bss : 表示未初始化的数据
.rdata : 表示只读的数据
.textbss :和代码相关
.idata 和edata :储存导入表和导出表的信息
.rsrc :储存资源的区段(节)
.relcoc : 储存重定位信息的区段(节)
那么这个区段(节)表到底有什么用呢?
其实这个区段表就是解析数据目录表的基础,用于 相对虚拟地址 (RVA)与文件偏移(Offectset)的转换。
在此之前的结构体中出现了很多的RVA,其实这些地址都落在了PE文件主体的某一个区段中,当我们想在文件中找到这个位置的时候,却不能直接用这个相对虚拟地址,而是需要一定的转换。
要转换的相对虚拟地址一定会落在一个区段中,我们想要知道落在哪一个区段中,就需要分别与各个区段起始的相对虚拟地址做比较(也就是与结构体中的VirtualAddress做比较)。
如果落在一个区段中,就用要转换的相对虚拟地址减去区段起始的相对虚拟基址,得到的是这个地址相对于这个区段的偏移,再用 这个偏移加上区段在文件中的起始位置,就是加上PointerToRawData,就得到了文件偏移。
Offect(转)=RVA(转)-RVA(区段)+Offect(区段)
我们可以用代码来实现,那么怎么找到区段头呢,系统提供了一个宏来方便的找到它的位置
#define IMAGE_FIRST_SECTION( ntheader ) ((PIMAGE_SECTION_HEADER) \
((ULONG_PTR)(ntheader) + \
FIELD_OFFSET( IMAGE_NT_HEADERS, OptionalHeader ) + \
((ntheader))->FileHeader.SizeOfOptionalHeader \
))
我们只需要调用 IMAGE_FIRST_SECTION(pNT)就可以了,参数是NT头的指针。
DWORD RvaToOffect(DWORD Rva, PBYTE buf)
{
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)buf;//获取DOS头
PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + buf);//获取NT头
PIMAGE_SECTION_HEADER pHerder = IMAGE_FIRST_SECTION(pNt);//获取区段头
DWORD dwNumber = pNt->FileHeader.NumberOfSections;
if (Rva < pHerder[0].PointerToRawData)
{
return Rva;
}
for (int i = 0; i < dwNumber; i++)
{
if (
(Rva >= pHerder[i].VirtualAddress) &&
(Rva <= pHerder[i].VirtualAddress + pHerder[i].Misc.VirtualSize)
)
{
return Rva - pHerder[i].VirtualAddress + pHerder[i].PointerToRawData;
}
}//循环比较在哪一个区段
}
上面的代码就是RVA和Offect的转换函数,这样我们就能去 解析前面没有 详细解析的 数据目录表。
到这里PE文件的头部信息就结束了,之后 就是PE文件 的主体部分,主体 部分在加载的时候就是根据区段表进行加载的一块一块的数据,一般不用去直接理会区段,因为所有PE的信息都已经包含在头中,我们可以通过头中的引导去解析主体中的数据。