具体的学习总结:
PE结构详解:
1、DOS头:共40H(64字节)
0X00 WORD e_magic; //※Magic DOS signature MZ(4Dh 5Ah):MZ标记:用于标记是否是可执行文件
0x3C DWORD e_lfanew; //※Offset to start of PE header:定位PE文件,PE头相对于文件的偏移量
2、PE头:
//NT头
//pNTHeader = dosHeader + dosHeader->e_lfanew;
struct _IMAGE_NT_HEADERS{
0x00 DWORD Signature; //PE文件标识:ASCII的"PE\0\0"
0x04 _IMAGE_FILE_HEADER FileHeader;//0x04是偏移量。标准PE头
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;//0x18是偏移量,可变PE头
};
NT头的第一个成员是”PE\0\0”
//标准PE头:最基础的文件信息,共20字节
3、Section Table结构解析:
Section Table(节表)是记录PE文件中各个节的详细信息的集合,其每个成员是struct _IMAGE_SECTION_HEADER结构体,即节表是一个结构体数组来维护,属于线性结构。而节表的相对起始位置为:紧接着可选PE表。即:DOS头 + 中间空闲及垃圾数据 + NT头(三部分:4字节签名+标准PE头20字节+可选PE头)。
4、导出表、导入表
pDH获取DOS部分,首先判断是否为MZ,接下来判断PE,此时找到PE的位置pNtH,判断是否为PE。接下来是标准PE头pFH和扩展PE头pOH,再后面是节表,首先需要算节表的位置,
DWORD SectionHeaderOffset = (DWORD)pNtH + 24 + (DWORD)pFH->SizeOfOptionalHeader;
//节表位置的计算,4字节的PE头标志,20位的标准PE头,加上扩展PE头
找到每一个节表的位置,输出名字、虚拟地址、大小和偏移。
然后是导出表、导入表
首先编写了一个找到导入表导出表相对于内存的偏移的方法RVAOffset,获取的参数是PE头的位置和导出表的虚拟地址,找到节表的位置,然后用虚拟地址减去节表的地址,可以算出导出表对于节表的偏移,再加上节表在文件中的偏移,就可以算出所求导出表对与基地址的偏移。
#include"stdio.h"
#include"stdlib.h"
#include"windows.h"
//计算导入表导出表相对于内存的偏移
int File_IAT(PIMAGE_NT_HEADERS pNtH,void* pFileAddress);
int File_INT(PIMAGE_NT_HEADERS pNtH,void* pFileAddress);
unsigned int RVAOffset(PIMAGE_NT_HEADERS pNtHeader, unsigned int Rva)
{
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((unsigned int)pNtHeader + sizeof(IMAGE_NT_HEADERS));
//算出节表的位置
for (int i = 0; i < pNtHeader->FileHeader.NumberOfSections; i++)
{
unsigned int SectionBeginRva = pSectionHeader[i].VirtualAddress;//内存中的偏移地址
unsigned int SectionEndRva = pSectionHeader[i].VirtualAddress + pSectionHeader[i].SizeOfRawData;//内存中的偏移地址+节在文件中对齐后的大小
if (Rva >= SectionBeginRva && Rva <= SectionEndRva)
{
unsigned int Temp = Rva - SectionBeginRva;
unsigned int Rwa = Temp + pSectionHeader[i].PointerToRawData; //节区在文件中的偏移
return Rwa;
}
}
}
int main()
{
void* pFileAddress=NULL;
char sFilePath[MAX_PATH];
PIMAGE_DOS_HEADER pDH = NULL;//指向IMAGE_DOS结构的指针
PIMAGE_NT_HEADERS pNtH = NULL;//指向IMAGE_NT结构的指针
PIMAGE_FILE_HEADER pFH = NULL;//指向IMAGE_FILE结构的指针
PIMAGE_OPTIONAL_HEADER pOH = NULL;//指向IMAGE_OPTIONALE结构的指针
long Length = 0;
//获取文件路径
printf("Please input the pe file path:\n");
scanf("%s",sFilePath);
//打开文件
FILE* pf = fopen(sFilePath,"rb");
if(pf == NULL)
{
printf("func ReadFile() Error!\n");
}
//获取文件长度
fseek(pf, 0, SEEK_END);
Length = ftell(pf);
fseek(pf, 0, SEEK_SET);
if(Length == -1)
printf("func GetFileLength() Error!\n");
//分配空间
pFileAddress = (void*)malloc(Length);
if (pFileAddress == NULL)
printf("func malloc() Error!\n");
memset(pFileAddress, 0, Length);
//读取文件进入内存
fread(pFileAddress, Length, 1, pf);
fclose(pf);
/************************************************************************/
/* PE头的判断 */
/************************************************************************/
if (!pFileAddress) //判断映像地址
{
printf("Not a valid PE file 1!\n");
return 0;
}
printf("--------------------PEheader------------------------\n");
pDH = (PIMAGE_DOS_HEADER)pFileAddress;
if (pDH->e_magic!=IMAGE_DOS_SIGNATURE) //判断是否为MZ
{
printf("Not a valid PE file 2!\n");
return 0;
}
pNtH = (PIMAGE_NT_HEADERS)((unsigned int)pDH + pDH->e_lfanew); //判断是否为PE格式
if (pNtH->Signature!=IMAGE_NT_SIGNATURE)
{
printf("Not a valid PE file 3!\n");
return 0;
}
printf("PE e_lfanew is: 0x%x\n", pNtH);
/************************************************************************/
/* FileHeader */
/************************************************************************/
pFH = &pNtH->FileHeader;//标准PE头
printf("-----------------FileHeader------------------------\n");
printf("NumberOfSections: %d\n", pFH->NumberOfSections);
printf("SizeOfOptionalHeader: %d\n", pFH->SizeOfOptionalHeader);
/************************************************************************/
/* OptionalHeader */
/************************************************************************/
pOH = &pNtH->OptionalHeader;//扩展PE头
printf("-----------------OptionalHeader---------------------\n");
printf("SizeOfCode:0x%08x\n", pOH->SizeOfCode);//所有含有代码的区块的大小 编译器填入 没用(可改)
printf("AddressOfEntryPoint: 0x%08X\n", pOH->AddressOfEntryPoint);//程序入口RVA
printf("pFileAddress is 0x%x\n", pFileAddress);//内存镜像基址(程序默认载入基地址)
printf("SectionAlignment: 0x%08x\n", pOH->SectionAlignment);//内存中对齐大小
printf("FileAlignment: 0x%08x\n", pOH->FileAlignment);//文件中对齐大小(提高程序运行效率)
printf("SizeOfImage: 0x%08x\n", pOH->SizeOfImage);//内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍
printf("SizeOfHeaders: 0x%08x\n", pOH->SizeOfHeaders);//所有的头加上节表文件对齐之后的值
printf("NumberOfRvaAndSizes: 0x%08x\n", pOH->NumberOfRvaAndSizes);
/************************************************************************/
/* SectionTable */
/************************************************************************/
int SectionNumber = 0;
unsigned int SectionHeaderOffset = (unsigned int)pNtH + 24 + (unsigned int)pFH->SizeOfOptionalHeader; //节表位置的计算
//4字节的PE头标志,20位的标准PE头,加上扩展PE头
printf("--------------------SectionTable---------------------\n");
for (SectionNumber; SectionNumber < pFH->NumberOfSections;SectionNumber++)
{
PIMAGE_SECTION_HEADER pSh = (PIMAGE_SECTION_HEADER)(SectionHeaderOffset + 40 * SectionNumber);
//40字节*节数
printf("%d 's Name is %s\n", SectionNumber + 1, pSh->Name);//名字
printf("VirtualAddress: 0x%08X\n", (unsigned int)pSh->VirtualAddress);//虚拟地址
printf("SizeOfRawData: 0x%08X\n", (unsigned int)pSh->SizeOfRawData); //节在文件中对齐的尺寸
printf("PointerToRawData: 0x%08X\n", (unsigned int)pSh->PointerToRawData);//节区在文件中的偏移
}
/************************************************************************/
/* ImportTable */
/************************************************************************/
printf("--------------------ImportTable----------------------\n");
printf("--------------------IAT----------------------\n");
File_IAT(pNtH,pFileAddress);
printf("--------------------INT----------------------\n");
int i = File_INT(pNtH,pFileAddress);
if(i ==0)
File_IAT(pNtH,pFileAddress);
system("pause");
}
int File_IAT(PIMAGE_NT_HEADERS pNtH,void* pFileAddress)
{
unsigned int dwImportOffset = RVAOffset(pNtH, pNtH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned int)pFileAddress + dwImportOffset);
while(pImport->FirstThunk )
{
unsigned int* OriginalFirstThunk_INT = (unsigned int*)((unsigned int)pFileAddress + RVAOffset(pNtH, pImport->OriginalFirstThunk)); // RVA 指向 INT (PIMAGE_THUNK_DATA结构数组)
unsigned int* FirstThunk_IAT = (unsigned int*)((unsigned int)pFileAddress + RVAOffset(pNtH, pImport->FirstThunk)); // RVA 指向 IAT (PIMAGE_THUNK_DATA结构数组)
unsigned int dwName = (unsigned int)pFileAddress + RVAOffset(pNtH, pImport->Name);
printf("---------Import File Name: %s\n", dwName);
//循环输出IAT表
while (*FirstThunk_IAT)
{
// (5)进行判断,如果最高位为1则是按序号导入信息,去掉最高位就是函数序号,否则是名字导入
if ((*FirstThunk_IAT) >> 31) //最高位是1,序号导入
{
unsigned int Original = *FirstThunk_IAT << 1 >> 1; //去除最高标志位。
printf("按序号导入: %08Xh -- %08dd\n", Original, Original); //16进制 -- 10 进制
}
else //名字导入
{
// (7)获取函数名
unsigned int ImportNameAdd_RAV = *FirstThunk_IAT;
unsigned int ImportNameAdd_FOA = RVAOffset(pNtH, ImportNameAdd_RAV);
PIMAGE_IMPORT_BY_NAME ImportName = (PIMAGE_IMPORT_BY_NAME)((unsigned int)pFileAddress + ImportNameAdd_FOA);
printf("按名字导入[HINT/NAME]: %02X--%s\n", ImportName->Hint, ImportName->Name);
}
FirstThunk_IAT++;
}
pImport++;
}
return 1;
}
int File_INT(PIMAGE_NT_HEADERS pNtH,void* pFileAddress)
{
unsigned int dwImportOffset = RVAOffset(pNtH, pNtH->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((unsigned int)pFileAddress + dwImportOffset);
while(pImport->OriginalFirstThunk )
{
unsigned int* OriginalFirstThunk_INT = (unsigned int*)((unsigned int)pFileAddress + RVAOffset(pNtH, pImport->OriginalFirstThunk)); // RVA 指向 INT (PIMAGE_THUNK_DATA结构数组)
unsigned int dwName = (unsigned int)pFileAddress + RVAOffset(pNtH, pImport->Name);
printf("---------Import File Name: %s\n", dwName);
//循环输出IAT表
while (*OriginalFirstThunk_INT)
{
// (5)进行判断,如果最高位为1则是按序号导入信息,去掉最高位就是函数序号,否则是名字导入
if ((*OriginalFirstThunk_INT) >> 31) //最高位是1,序号导入
{
unsigned int Original = *OriginalFirstThunk_INT << 1 >> 1; //去除最高标志位。
printf("按序号导入: %08Xh -- %08dd\n", Original, Original); //16进制 -- 10 进制
}
else //名字导入
{
// (7)获取函数名
unsigned int ImportNameAdd_RAV = *OriginalFirstThunk_INT;
unsigned int ImportNameAdd_FOA = RVAOffset(pNtH, ImportNameAdd_RAV);
PIMAGE_IMPORT_BY_NAME ImportName = (PIMAGE_IMPORT_BY_NAME)((unsigned int)pFileAddress + ImportNameAdd_FOA);
printf("按名字导入[HINT/NAME]: %02X--%s\n", ImportName->Hint, ImportName->Name);
}
OriginalFirstThunk_INT++;
}
pImport++;
}
if ((unsigned int)pImport->OriginalFirstThunk == 0)
{
return 0;
}
else
return 1;
}