PE头字段说明
一、NT头
1、PE标识
2、标准PE头
3、扩展PE头
二、标准PE头
ypedef struct _IMAGE_FILE_HEADER {
WORD Machine; * //可以运行在什么样的CPU上 任意:0 Intel 386以及后续:14C x64:8664
WORD NumberOfSections; * //表示节的数量
DWORD TimeDateStamp; * //编译器填写的时间戳 与文件属性里面(创建时间、修改时间)无关
DWORD PointerToSymbolTable; //调试相关
DWORD NumberOfSymbols; //调试相关
WORD SizeOfOptionalHeader;* //扩展PE头的大小(32位PE文件:0xE0 64位PE文件:0xF0)
WORD Characteristics; * //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
三、扩展PE头
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; * //PE32:10B PE32+:20B
BYTE MajorLinkerVersion; //链接器版本号
BYTE MinorLinkerVersion; //链接器版本号
DWORD SizeOfCode; * //所有代码节的总和 文件对齐后的大小 编译器填的 没用
DWORD SizeOfInitializedData; * //包含所有已经初始化数据的节的总大小 文件对齐后的大小 编译器填的 没用
DWORD SizeOfUninitializedData; * //包含未初始化数据的节的总大小 文件对齐后的大小 编译器填的 没用
DWORD AddressOfEntryPoint; * //程序入口
DWORD BaseOfCode; * //代码开始的基址,编译器填的 没用
DWORD BaseOfData; * //数据开始的基址,编译器填的 没用
DWORD ImageBase; * //内存镜像基址
DWORD SectionAlignment; * //内存对齐
DWORD FileAlignment; * //文件对齐
WORD MajorOperatingSystemVersion; //标识操作系统版本号 主版本号
WORD MinorOperatingSystemVersion; //标识操作系统版本号 次版本号
WORD MajorImageVersion; //PE文件自身的版本号
WORD MinorImageVersion; //PE文件自身的版本号
WORD MajorSubsystemVersion; //运行所需子系统版本号
WORD MinorSubsystemVersion; //运行所需子系统版本号
DWORD Win32VersionValue; //子系统版本的值,必须为0
DWORD SizeOfImage; * //内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍
DWORD SizeOfHeaders; * //所有头+节表按照文件对齐后的大小,否则加载会出错
DWORD CheckSum; * //校验和,一些系统文件有要求.用来判断文件是否被修改.
WORD Subsystem; //子系统 驱动程序(1) 图形界面(2) 控制台、DLL(3)
WORD DllCharacteristics; //文件特性 不是针对DLL文件的
DWORD SizeOfStackReserve; * //初始化时保留的栈大小
DWORD SizeOfStackCommit; * //初始化时实际提交的大小
DWORD SizeOfHeapReserve; * //初始化时保留的堆大小
DWORD SizeOfHeapCommit; * //初始化时实践提交的大小
DWORD LoaderFlags; //调试相关
DWORD NumberOfRvaAndSizes; * //目录项数目
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
四、扩展知识
1、虚拟内存
每个exe程序都有自己独立的4G内存空间,一个exe文件由一堆PE文件组成
2、ImageBase
ImageBase是exe在内存中的起始位置
3、程序入口
AddressOfEntryPoint + ImageBase 才是程序真正的入口
五、PE头打印
1、打开文件,并判断是否打开成功
FILE *pFile = NULL;
DWORD fileSize = 0;
LPVOID pFileBuffer = NULL;
pFile = fopen(lpszFile,"rb"); //打开文件
if(!pFile)
{
printf("打开文件失败");
return NULL;
}
2、读取文件大小
//读取文件大小
fseek(pFile,0,SEEK_END); //将指针从开始的位置移动到末尾
fileSize = ftell(pFile); //获取数据大小
3、申请内存,并判断是否申请成功
#include "stdlib.h"
//分配缓冲区(申请内存)
pFileBuffer = malloc(fileSize);
if(!pFileBuffer)
{
printf("分配空间失败");
fclose(pFile);
return NULL;
}
4、将文件读取到内存中
fseek(pFile,0,SEEK_SET); //将指针指向开始
size_t n = fread(pFileBuffer,fileSize,1,pFile); //将数据读取到缓冲区中
if(!n)
{
printf("读取数据失败");
free(pFileBuffer); //释放内存
fclose(pFile); //关闭文件
return NULL;
}
5、关闭文件
fclose(pFile);
6、判断是否有效MZ标识
//定义几个变量
PIMAGE_DOS_HEADER pDosHeader = NULL; //接收DOS头
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ头");
free(pFileBuffer);
return;
}
7、打印DOS头
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; //类型转换
printf("MZ标志: %x\n",pDosHeader->e_magic);
printf("PE偏移: %x\n",pDosHeader->e_lfanew);
8、判断是否为PE标识
if(*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return;
}
9、打印PE文件标识
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew); //将指针移动到PE头开始位置,并且类型转换
printf("*************************NT头*************************\n");
printf("PE标识: %x\n",pNTHeader->Signature);
10、打印标准PE头
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("*************************标准PE头*************************\n");
printf("节的数量: %x\n",pPEHeader->NumberOfSections);
printf("可选PE头的大小: %x\n",pPEHeader->SizeOfOptionalHeader);
11、打印可选PE头
printf("*************************可选PE头*************************\n");
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
printf("程序入口地址: %x\n",pOptionHeader->AddressOfEntryPoint);
printf("文件在内存中的入口地址: %x\n",pOptionHeader->ImageBase);
printf("文件对齐: %x\n",pOptionHeader->FileAlignment);
printf("内存对齐: %x\n",pOptionHeader->SectionAlignment);
printf("PE头在文件对齐后大小: %x\n",pOptionHeader->SizeOfHeaders);
printf("PE文件在内存对齐后大小: %x\n",pOptionHeader->SizeOfImage);
12、释放内存
free(pFileBuffer);
13、完整代码
#include "stdafx.h"
#include <windows.h>
#include "stdlib.h"
#include "stdio.h"
//读取文件
LPVOID ReadPEFile(LPSTR lpszFile)
{
FILE *pFile = NULL;
DWORD fileSize = 0;
LPVOID pFileBuffer = NULL;
pFile = fopen(lpszFile,"rb"); //打开文件
if(!pFile)
{
printf("打开文件失败");
return NULL;
}
//读取文件大小
fseek(pFile,0,SEEK_END); //将指针从开始的位置移动到末尾
fileSize = ftell(pFile); //获取数据大小
//分配缓冲区(申请内存)
pFileBuffer = malloc(fileSize);
if(!pFileBuffer)
{
printf("分配空间失败");
fclose(pFile);
return NULL;
}
//将文件数据读取到缓冲区
fseek(pFile,0,SEEK_SET); //将指针指向开始
size_t n = fread(pFileBuffer,fileSize,1,pFile); //将数据读取到缓冲区中
if(!n)
{
printf("读取数据失败");
free(pFileBuffer); //释放内存
fclose(pFile); //关闭文件
return NULL;
}
//关闭文件
fclose(pFile); //关闭文件
return pFileBuffer; //返回缓冲区的首地址
}
//*****************************************************************************************************************************
VOID PrintNTHeaders()
{
LPVOID pFileBuffer = NULL;
PIMAGE_DOS_HEADER pDosHeader = NULL;
PIMAGE_NT_HEADERS pNTHeader = NULL;
PIMAGE_FILE_HEADER pPEHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
//读取文件
LPSTR FILEPATH = "c:/notepad.exe";
pFileBuffer = ReadPEFile(FILEPATH);
if(!pFileBuffer)
{
printf("文件读取失败");
return;
}
//判断是否是有效的MZ标志
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
{
printf("不是有效的MZ头");
free(pFileBuffer);
return;
}
pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
//打印DOS头
printf("MZ标志: %x\n",pDosHeader->e_magic);
printf("PE偏移: %x\n",pDosHeader->e_lfanew);
//判断 是否是PE标志
if(*((PDWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
{
printf("不是有效的PE标志\n");
free(pFileBuffer);
return;
}
//***************************************************************************************************
printf("*************************PE头*************************\n");
pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew);
printf("PE标识: %x\n",pNTHeader->Signature);
//***************************************************************************************************
printf("*************************标准PE头*************************\n");
pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
printf("节的数量: %x\n",pPEHeader->NumberOfSections);
printf("可选PE头的大小: %x\n",pPEHeader->SizeOfOptionalHeader);
//***************************************************************************************************
printf("*************************可选PE头*************************\n");
pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
printf("程序入口地址: %x\n",pOptionHeader->AddressOfEntryPoint);
printf("文件在内存中的入口地址: %x\n",pOptionHeader->ImageBase);
printf("文件对齐: %x\n",pOptionHeader->FileAlignment);
printf("内存对齐: %x\n",pOptionHeader->SectionAlignment);
printf("PE头在文件对齐后大小: %x\n",pOptionHeader->SizeOfHeaders);
printf("PE文件在内存对齐后大小: %x\n",pOptionHeader->SizeOfImage);
}
int main(int argc, char* argv[])
{
PrintNTHeaders();
getchar();
return 0;
}