PE文件结构(一) 基本结构


 

参考

书:《加密与解密》

视频:小甲鱼 解密系列 视频

 

        exe,dll都是PE(Portable Execute)文件结构。PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。先看2张图,来大概了解一下PE文件结构。

 


PE文件的框架结构


 

通过这张图(开始在下面),我们可以知道PE文件的大概结构,PE文件是由 DOS头,PE文件头,块表,块,调试信息 这些部分组成的。这些结构的定义在 winnt.h 中的  “Image Format”  这一节中。


PE文件磁盘与内存映像结构图

 

通过这张图我们可以知道PE文件映射到内存中的结构,PE文件在磁盘中的结构与映射到内存中的结构的区别。PE文件在磁盘中的结构与映射到内存中的结构基本相同,基本布局也相同。DOS头,PE头,块表相对开头的地址在磁盘跟内存中相同。但是因为磁盘对齐跟内存对齐不同,还有window可能不会一次性加载完程序,会引起 后面块项与开头的偏移地址不同。

PE文件被window加载到内存中后,内存中的版本就模块(Module)。映射文件的起始地址叫做模块句柄(hModule),就是图中基地址(ImageBase)。通过它,可以访问到模块中的其他结构。

 

下面就来具体分析PE文件结构:

 

MS-DOS头部

PE文件第一个字节起始于一个传统的MS-DOS头部,被叫做 IMAGE_DOS_HEADER。

(下面的代码,可以在 winnt.h 中找到)

 

typedef struct _IMAGE_DOS_HEADER {
	WORD e_magic;   //DOS 可执行文件标记 "MZ"  +0h
	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;     //指向PE文件头,"PE",0,0       +3ch
} IMAGE_DOS_HEADER,*PIMAGE_DOS_HEADER;

 

 

其中e_magic 和 e_lfanew 比较重要, e_magic  为 "MZ" ,e_lfanew 字段是真正PE文件的相对偏移(RVA)。

 

图片3

 

PE文件头

接着DOS stub 后是PE文件头(PE Header),被叫做 IMAGE_NT_HEADERS

 

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;                         //+0h  PE文件头  "PE"
    IMAGE_FILE_HEADER FileHeader;            //+4h
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;  //+18h
} IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;   

 

IMAGE_FILE_HEADER

 

IMAGE_FILE_HEADER  映像文件头, 包含了PE文件的一些基本信息。其中SizeOfOptionalHeader指出了IMAGE_OPTIONAL_HEADE 大小。

 

 

typedef struct _IMAGE_FILE_HEADER {
	WORD Machine;                     //+04h 运行平台
	WORD NumberOfSections;            //+06h 文件区块(Section)的数目
 	DWORD TimeDateStamp;              //+08h 文件的创建时间。这个值是从1970年1月1号以来格林威治时间计算的秒数
	DWORD PointerToSymbolTable;       //+0Ch 指向COFF符号表(用于调试)
	DWORD NumberOfSymbols;            //+10h 符号表中符号个数(用于调试)
 	WORD SizeOfOptionalHeader;        //+14h IMAGE_OPTINAL_HEADER结构的大小 32位文件一般是00E0h,64位文件一般是00F0h
	WORD Characteristics;             //+16h 文件属性,通过几个值运算得到,这些些标志定义在winnt.h 中的IMAGE_FILE_xx,exe文件一般是010fh,dll一般是210Eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

 

图片4

 

IMAGE_OPTIONAL_HEADER

 

IMAGE_OPTIONAL_HEADER 可选映像头,虽然是一个可选结构,但是事实上IMAGE_FILE_HEADER 不够用,需要IMAGE_OPTIONAL_HEADER定义更多的数据。

 

 

typedef struct _IMAGE_OPTIONAL_HEADER {
	WORD Magic;
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;
	DWORD SizeOfInitializedData;
	DWORD SizeOfUninitializedData;
	DWORD AddressOfEntryPoint;         // +28h 程序执行入口RVA,dll文件一般为0
	DWORD BaseOfCode;
	DWORD BaseOfData;
	DWORD ImageBase;                   // +34h 程序默认装入基地址
	DWORD SectionAlignment;            // +38h 内存中区块的对齐值,32位是 1000h(4K)
	DWORD FileAlignment;               // +3Ch 文件中区块的对齐值,一般是200h 或者 1000h
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;
	DWORD SizeOfHeaders;
	DWORD CheckSum;
	WORD Subsystem;                    // 标明可执行文件所希望的子系统(用户界面类型)的枚举值。即这个程序要不要图形界面等。
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;
	DWORD SizeOfStackCommit;
	DWORD SizeOfHeapReserve;
	DWORD SizeOfHeapCommit;
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;                                              // +74h 数据目录表的项数 。一直都是16
	IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];   // +78h 数据目录表。其中有导入表,到出表,资源表等
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

 

IMAGE_OPTIONAL_HEADER中的IMAGE_DATA_DIRECTORY是数据目录表。定义了导入表,到出表,资源表等的起始RVA与大小。

 

 

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;     // 数据快的起始RVA
    DWORD   Size;               // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

 

 

图片5 

通过这张图片我们可以知道,这个程序没有输出表,输入表单其实RVA为543Ch,大小为3Ch。

 

区块表

 

在PE文件头与原始数据之间存在一个区块表。一般PE文件至少需要 .text 跟 .data区块。

 

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];  // 块名,IMAGE_SIZEOF_SHORT_NAME  为8
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;            // 实际的区块大小(即没有对齐前的区块大小)
    } Misc;
    DWORD   VirtualAddress;                 // 该块装载到内存中的RVA。第一个块默认为1000h
    DWORD   SizeOfRawData;                  // 文件在磁盘中对齐后的尺寸
    DWORD   PointerToRawData;               // 该区块在磁盘中的偏移 
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;               // 区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;