PE文件
1.0 PE简介
1.0.0什么是PE文件
PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。
1.0.1 PE文件的两种状态
PE文件在运行的时候,在内存中的状态和在硬盘当中是不一样的,要在内存中进行一次类似拉伸的操作才能够成功运行。如下图所示:
1.1PE头
PE头一般包含如下两个部分
- DOS头
- NT头
其中NT头包含如下三个部分:
IMAGE_NT_HEADERS STRUCT +0h DWORD Signature //PE文件标识 +4h IMAGE_FILE_HEADER FileHeader //标准PE头 +18h IMAGE_OPTIONAL_HEADER32 OptionalHeader //可选PE头 IMAGE_NT_HEADERS ENDS
注:标准PE头和可选PE头都在NT头里,PE头的结构和内容如下两张图所示:
1.1.1 DOS头:
WORD e_magic //"MZ标记" 用于判断是否为可执行文件. DWORD e_lfanew; //标准PE头相对于文件的偏移,用于定位标准PE文件头的开始位置
1.1.2、标准PE头:
WORD Machine; * //程序运行的CPU型号:0x0 任何处理器/0x14C 386及后续处理器 WORD NumberOfSections; * //文件中存在的节的总数,如果要新增节或者合并节 就要修改这个值. DWORD TimeDateStamp; * //时间戳:文件的创建时间(和操作系统的创建时间无关),编译器填写的. DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; * //可选PE头的大小,32位PE文件默认E0h 64位PE文件默认为F0h 大小可以自定义. WORD Characteristics; * //每个位有不同的含义,可执行文件值为10F 即0 1 2 3 8位置1
1.1.3 可选PE头
WORD Magic; * //说明文件类型:10B 32位下的PE文件 20B 64位下的PE文件 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode;* //所有代码节的和,必须是FileAlignment的整数倍 编译器填的 没用 DWORD SizeOfInitializedData;* //已初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用 DWORD SizeOfUninitializedData;* //未初始化数据大小的和,必须是FileAlignment的整数倍 编译器填的 没用 DWORD AddressOfEntryPoint;* //程序入口 DWORD BaseOfCode;* //代码开始的基址,编译器填的 没用 DWORD BaseOfData;* //数据开始的基址,编译器填的 没用 DWORD ImageBase;* //内存镜像基址 DWORD SectionAlignment;* //内存对齐 DWORD FileAlignment;* //文件对齐 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage;* //内存中整个PE文件的映射的尺寸,可以比实际的值大,但必须是SectionAlignment的整数倍 DWORD SizeOfHeaders;* //所有头+节表按照文件对齐后的大小,否则加载会出错 DWORD CheckSum;* //校验和,一些系统文件有要求.用来判断文件是否被修改. WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve;* //初始化时保留的堆栈大小 DWORD SizeOfStackCommit;* //初始化时实际提交的大小 DWORD SizeOfHeapReserve;* //初始化时保留的堆大小 DWORD SizeOfHeapCommit;* //初始化时实践提交的大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes;* //目录项数目
1.1.4 PE头解析
PE头使用代码进行解析需要明白PE文件大概又如下图所示的结构: 使用c语言设计PE头解析程序,具体代码如下:
// shellcode_inject.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include<Windows.h> #include<stdlib.h> void * readFile(char * filepath){ //printf(filepath); int filesize=0; FILE *filep=NULL; char *buffer=NULL; filep = fopen(filepath,"rb"); if (filep == NULL){ printf("打开文件失败!"); return NULL; } fseek(filep,0,SEEK_END); filesize = ftell(filep); fseek(filep,0,SEEK_SET); //printf("filesize is %d\n",filesize); buffer = (char *)malloc(sizeof(char)*filesize); if(buffer == NULL){ printf("内存分配失败!"); fclose(filep); return NULL; } memset(buffer,0,filesize); size_t n = fread(buffer,1,filesize,filep); if(!n){ printf("读取数据失败!"); fclose(filep); free(buffer); return NULL; } fclose(filep); return buffer; } void readPEHeader(char *filebuffer){ //声明所要用到的变量 PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; IMAGE_FILE_HEADER PEHeader; PIMAGE_OPTIONAL_HEADER64 pOptionHeader; PIMAGE_SECTION_HEADER pSectionHeader = NULL; //获取DOS头 printf("*********DOS头输出:**********\n"); pDosHeader = (PIMAGE_DOS_HEADER)filebuffer; printf("MZ标志:%x\n",(*pDosHeader).e_magic); printf("NT头相对于文件初始位置的偏移:%x\n",(*pDosHeader).e_lfanew); printf("*********DOS头输出:**********\n"); //获取NP头 printf("*********NT头输出:**********\n"); pNTHeader = PIMAGE_NT_HEADERS(filebuffer + (*pDosHeader).e_lfanew); printf("PE标记:%x\n",(*pNTHeader).Signature); printf("*********NT头输出:**********\n"); //获取标准PE头 printf("*********标准PE头输出:**********\n"); PEHeader = (*pNTHeader).FileHeader; printf("Machine:%x\n",PEHeader.Machine); printf("节的数量为:0x%x\n",PEHeader.NumberOfSections); printf("可选PE头的大小:0x%x\n",PEHeader.SizeOfOptionalHeader); printf("*********标准PE头输出:**********\n"); //获取可选PE头,我这里用的是64位的notepad.exe所以使用这个PIMAGE_OPTIONAL_HEADER64 pOptionHeader = PIMAGE_OPTIONAL_HEADER64(filebuffer + (*pDosHeader).e_lfanew + sizeof((*pNTHeader).Signature)+sizeof((*pNTHeader).FileHeader)); printf("*********可选头输出:**********\n"); printf("Magic:0x%x\n",(*pOptionHeader).Magic); printf("*********可选PE头输出:**********\n"); free(filebuffer);//释放内存 } int main(int argc, char* argv[]) { char *filepath = "C:\\Users\\crls\\Desktop\\VC6.0green\\MyProjects\\PEReader\\Debug\\notepad.exe"; char *filebuffer = (char *)readFile(filepath); readPEHeader(filebuffer); return 0; }