PE文件结构2(实现PE文件载入)
现在我们已经学完了PE文件格式,但是尚还停留在纸上谈兵的阶段,作为Windows系统上的可执行文件格式,PE文件结构总是和结构体,指针等紧密联系在一起的。理解它的最好方法就是通过写一个类似LordPE的程序来帮助我们理解PE文件结构的底层实现逻辑。计算机到底是如何实现对于PE文件结构的读取和分析的,到底是怎么找到每一个数据的地址的,这是为我们今天研究的关键。
问题关键
1.如何读取导入文件
2.如何找到每个结构体的地址
3.熟悉PE文件结构对应的结构体(参考PE文件结构1)
一. 如何读取文件
首先通过CreatFileA函数打开文件;然后通过HeapAlloc函数分配空间;最后经过ReadFile函数读取文件内容至指定空间。这几个与文件有关的WindowsAPI函数是我们处理PE文件的关键。
二. 找到每个关键结构体的指针
在PE文件结构1当中已经介绍了,PE文件结构的每个组成部分都有其对应的结构体,那么只要我们熟悉了结构体的组成然后找到每个结构体对应的指针,那么问题也就解决了。
现在依次介绍每个部分对应指针应该如何寻址:
DOS首部:dosHeader = (PIMAGE_DOS_HEADER)lpBuffer;
这里的lpBuffer其实就是文件的起始句柄了,PE文件是从DOS首部开始的,所以只要将起始句柄类型转化一下为对应的指针即可。
PE文件首部:ntHeader = (PIMAGE_NT_HEADERS)(lpBuffer + dosHeader->e_lfanew);
这里就是一个RVA和VA的概念了,相对于文件起始地址为RVA也就是dosHeader->e_lfanew,加上文件的起始地址后才是我们可以用来寻址的指针。
ntHeader->FileHeader.Machine;
ntHeader->OptionalHeader.Magic;
由于PE文件首部内部还包含了两个结构体,这里举两个例子。
区块首部:imageSectionHeaderStruct = (PIMAGE_SECTION_HEADER)((DWORD)lpBuffer + dosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
要找到区块表的首部地址就需要先找到PE文件首部地址再加上PE文件首部大小,即为对应的区块首部地址。
由于节块的数目是不一定的,需要通过循环将其依次打印。
for (int i = 0; i < numSections; i++)
{
printf("SECTION HEADER #%d\n", i);
printf("\t%s\n", imageSectionHeaderStruct->Name);
printf("\t\tVirtual Size: 0x%x\n", imageSectionHeaderStruct->Misc.VirtualSize);
printf("\t\tVirtual Address: 0x%x\n", imageSectionHeaderStruct->VirtualAddress);
printf("\t\tRaw Size: 0x%x\n", imageSectionHeaderStruct->SizeOfRawData);
printf("\t\tRaw Address: 0x%x\n", imageSectionHeaderStruct->PointerToRawData);
printf("\t\tReloc Address: 0x%x\n", imageSectionHeaderStruct->PointerToRelocations);
printf("\t\tRelocations Number: 0x%x\n", imageSectionHeaderStruct->NumberOfRelocations);
printf("\t\tLine Numbers: 0x%x\n", imageSectionHeaderStruct->NumberOfLinenumbers);
printf("\t\tCharacteristics: 0x%x\n", imageSectionHeaderStruct->Characteristics);
imageSectionHeaderStruct++;
}
三. 导入表等的打印
导入表,导出表和资源表相对来说就比较麻烦一点,需要先在数据目录表当中寻找对应地址,然后再去数据段找到相应数据打印。
这里以导入表为例:
1.
int* rvaaddr = (int*)(NT_Header->OptionalHeader.DataDirectory + 1);
IMAGE_IMPORT_DESCRIPTOR* ImportTables = (IMAGE_IMPORT_DESCRIPTOR*)(RTF(*rvaaddr) + pBegin);
这里我们先找到导入表的数据目录表对应的地址。
2. 然后找到对应的IAT地址,即
int firstthunk = (int)ImportTables2->FirstThunkl;
这就是我们需要找的地址,之后依次打印即可。
感兴趣的可以查看https://github.com/FULLSHADE/pe-Parser。