什么是PE文件?
PE文件是在windows平台可执行的文件。
包括:.exe(可执行程序),dll(动态链接库).sys(驱动程序)
这是PE文件的基本结构:
Dos头是PE文件的起始位置,它
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; //PE文件的标识,PE文件的标识一定是5A4D
WORD e_cblp;
WORD e_cp;
WORD e_crlc;
WORD e_cparhdr;
WORD e_minalloc;
WORD e_maxalloc;
WORD e_ss;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; //Dos头距离Nt头的距离,是一个偏移
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
DOS头部分,有用的就两个参数:e_magic 和 e_lfanew
使用010editor来解析PE文件: 这是一个标准DOS头的组成部分。
e_magic: 5A4D 标识它是一个PE文件(exe dll)等
e_lfanew: 0110,这是一个偏移地址,表示从Dos头开始位置到标准NT部分的偏移,这里为110。
使用代码解析DOS头部分
BOOL PE::Load(const char* DllName)
{
HANDLE MainHandle = CreateFileA(
DllName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
DWORD FileSize = GetFileSize(MainHandle, NULL);
char* FileBuff = new char[FileSize]{NULL};
DWORD RealSize = 0;
BOOL IsSuccess = ReadFile(MainHandle, FileBuff, FileSize, &RealSize, NULL);
if (!IsSuccess)
{
printf("读取文件失败!\n");
return FALSE;
}
pDosHeader = (PIMAGE_DOS_HEADER)FileBuff;
return TRUE;
}
NT头结构体:
IMAGE_NT_HEADERS STRUCT
{
+0h DWORD Signature
+4h IMAGE_FILE_HEADER FileHeader
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader
} IMAGE_NT_HEADERS ENDS
Signature: 一个5A4D的标识,标识是一个PE文件。
这里表示的就是NT标识的部分:
他的全四个字节是一个DWORD的标识,Signature字段被设置为4550h,ASCII码为”PE00“。
可以看到它的偏移地址就是110h ,别忘了我们上面的DOS头部分的e_lfanew的值就是110h ,它表示的就是Dos头到NT头的偏移地址。
注意:我们使用010editor来查看的是偏移地址,不是真正的地址,需要加上FileBuff的基址,才是真正的地址。
标准NT头也叫做文件头。
structIMAGE_FILE_HEADER
{
WORD Machine;//运行平台
WORD NumberOfSections;//区块表的个数
DWORD TimeDataStamp;//文件创建时间,是从1970年至今的秒数
DWORD PointerToSymbolicTable;//指向符号表的指针
DWORD NumberOfSymbols;//符号表的数目
WORD SizeOfOptionalHeader;//IMAGE_NT_HEADERS结构中OptionHeader成员的大小,对于win32平台这个值通常是0x00e0
WORD Characteristics;//文件的属性值
}
这里面比较重要的字段是:
这里是标准NT头的部分:
代码实现标准PE头:
PIMAGE_NT_HEADERS pNtHeader; //NT头
PIMAGE_FILE_HEADER pFileHeader; //标准NT头
//首先要找到NT头的位置:基址+偏移地址
NtHeader = (PIMAGE_NT_HEADERS)(FileBuff + pDosHeader->e_lfanew);
pFileHeader =&pNtHeader->FileHeader;
pNtHeader包含了标准NT头与可选NT头,取地址即可以到达。
typedefstruct_IMAGE_OPTIONAL_HEADER
{
+18h WORD Magic;// 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion;// 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion;// 链接程序的次版本号
+1Ch DWORD SizeOfCode;// 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData;// 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData;// 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint;// 程序执行入口RVA
+2Ch DWORD BaseOfCode;// 代码的区块的起始RVA
+30h DWORD BaseOfData;// 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
+34h DWORD ImageBase;// 程序的首选装载地址
+38h DWORD SectionAlignment;// 内存中的区块的对齐大小
+3Ch DWORD FileAlignment;// 文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion;// 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion;// 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion;// 可运行于操作系统的主版本号
+46h WORD MinorImageVersion;// 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion;// 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion;// 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue;// 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage;// 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders;// 所有头 + 区块表的尺寸大小
+58h DWORD CheckSum;// 映像的校检和
+5Ch WORD Subsystem;// 可执行文件期望的子系统
+5Eh WORD DllCharacteristics;// DllMain()函数何时被调用,默认为 0
+60h DWORD SizeOfStackReserve;// 初始化时的栈大小
+64h DWORD SizeOfStackCommit;// 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve;// 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit;// 初始化时实际提交的堆大小
+70h DWORD LoaderFlags;// 与调试有关,默认为 0
+74h DWORD NumberOfRvaAndSizes;// 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
+78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_
重要的有:
这里是可选PE头的部分
代码解析:
PIMAGE_OPTIONAL_HEADER pOptionalHeader//可选NT头
//得到可选NT头的
pOptionalHeader = &pNtHeader->OptionalHeader;