PE文件(portable executable)
大致结构
1.DOS文件头 64byte
其中最重要的是: e_magic 表示MS-DOS文件头的签名
e_lfanew 指出 image_nt_header在映像中的位置
2.DOS stub程序 128byte
运行在DOS下的16位程序,目的是指出当windows程序在dos下运行时,显示信息表明该程序不能
在dos下执行并终止执行
3.NT文件头
PE文件的核心部分,被定义为s64和s32两个版本
其中包含有三个部分包括一个‘PE’字样的签名,PE文件头(IMAGE_FILE_HEADER),和PE可选头(IMAGE_OPTIONAL_HEADER32)
(1)Signature:类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是'PE‘。
(2)IMAGE_FILE_HEADER
- typedef struct _IMAGE_FILE_HEADER {
- WORD Machine;
- WORD NumberOfSections;
- DWORD TimeDateStamp;
- DWORD PointerToSymbolTable;
- DWORD NumberOfSymbols;
- WORD SizeOfOptionalHeader;
- WORD Characteristics;
- } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine:该文件的运行平台,是x86、x64还是I64等等
NumberOfSections:该PE文件中有多少个节,也就是节表中的项数。
TimeDateStamp:PE文件的创建时间,一般有连接器填写。
PointerToSymbolTable:COFF文件符号表在文件中的偏移。
NumberOfSymbols:符号表的数量。
SizeOfOptionalHeader:紧随其后的可选头的大小。
Characteristics:可执行文件的属性,可以是下面这些值按位相或。
(3)IMAGE_OPTIONAL_HEADER
- typedef struct _IMAGE_OPTIONAL_HEADER {
- WORD Magic;
- 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;
- 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;
- IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
- } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic:表示可选头的类型。
MajorLinkerVersion和MinorLinkerVersion:链接器的版本号。
SizeOfCode:代码段的长度,如果有多个代码段,则是代码段长度的总和。
SizeOfInitializedData:初始化的数据长度。
SizeOfUninitializedData:未初始化的数据长度。
AddressOfEntryPoint:程序入口的RVA,对于exe这个地址可以理解为WinMain的RVA。对于DLL,这个地址可以理解为DllMain的RVA,如果是驱动程序,可以理解为DriverEntry的RVA。当然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成,当然,这些不是本文的重点。
BaseOfCode:代码段起始地址的RVA。
BaseOfData:数据段起始地址的RVA。
ImageBase:映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。
SectionAlignment:节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。
FileAlignment:节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。
MajorOperatingSystemVersion、MinorOperatingSystemVersion:所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。
MajorImageVersion、MinorImageVersion:映象的版本号,这个是开发者自己指定的,由连接器填写。
MajorSubsystemVersion、MinorSubsystemVersion:所需子系统版本号。
Win32VersionValue:保留,必须为0。
SizeOfImage:映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
SizeOfHeaders:所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
CheckSum:映象文件的校验和。
Subsystem:运行该PE文件所需的子系统
DllCharacteristics:DLL的文件属性,只对DLL文件有效
SizeOfStackReserve:运行时为每个线程栈保留内存的大小。
SizeOfStackCommit:运行时每个线程栈初始占用内存大小。
SizeOfHeapReserve:运行时为进程堆保留内存大小。
SizeOfHeapCommit:运行时进程堆初始占用内存大小。
LoaderFlags:保留,必须为0。
NumberOfRvaAndSizes:数据目录的项数,即下面这个数组的项数。
DataDirectory:数据目录,这是一个数组,数组的项定义如下:
- typedef struct _IMAGE_DATA_DIRECTORY {
- DWORD VirtualAddress;
- DWORD Size;
- } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
这个结构十分重要,它用来描述 windows 执行映象中所使用的各种表格的位置和大小。VirtualAddress 域是一个 RVA (Relative Virtual Address)值,更明白一点就是:它是一个偏移量(基于 PE 文件头),Size 域表示这个表格有多大。 这个数组有 16 个元素,也就是表示,在执行映象中最多可以使用 16 个表格。
实际上这 16 个表格是固定的,对于这些表格 Microsoft 都作了统一的规定,在 WinNT.h 里都作了定义:
// Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory |
介绍见Microsofthttp://msdn.microsoft.com/en-us/library/ms680305(VS.85).asp
下面是t..exe 映象中使用的表
00000148 00 00 00 00 // IMAGE_DATA_DIRECTORY[0] |
我们的示例程序 t.exe 仅仅使用了 3 个 driectory 表格:import table、relocation table 以及 import address table
import table 的 RVA 是 0x00002010,size 是 0x28,relocation table 的 RVA 是 0x4000,size 是 0x0c, import address table 的 RVA 是 0x2000,size 是 0x10
t.exe 的 ImageBase 是 0x00000001_40000000,那么 import table 则在 0x00000001_40002010,relocation table 则在 0x00000001_40004000,import address table 则在 0x00000001_40002000。
(4)导入表
mport table 在 WinNT.h 中定义为一个 IMAGE_IMPORT_DESCRIPTOR 结构,如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { DWORD ForwarderChain; // -1 if no forwarders |

IMAGE_NT_HEADER 结构后面紧接着就是 section table(节表)结构, 从 0x1c8 ~ 0x267 共 160 bytes。
这个节表结构在 WinNT.h 中定义为
// #define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { #define IMAGE_SIZEOF_SECTION_HEADER 40 |
映象中包括有多少个节表结构,由 IMAGE_NT_HEADER 结构中的 IMAGE_FILE_HEADER 结构中的 NumberOfSections 域指出。在前面所述的 t.exe 映象里 NumberOfSections 值是 4 那么表示将有 4 个 sections 存在于映象中。从前的面 dumpbin 工具输出可以得出。
在 IMAGE_SECTION_HEADER 结构的第 1 个域 Name,用来标识 section table 的名字。它的长度固定为 8 bytes(前面定义的宏),这将意味着,不存在超过 8 bytes 的节表名。接下来使用 VirtualSize 来用表示 section talbe 大小。VirtualAddress 表示 section table 的 RVA
域
|
size
|
描述
|
Name
|
8 bytes
|
Section 表名字
|
VirtualSize
|
DWORD
|
Section 表的大小
|
VirtualAddress
|
DWORD
|
Section 表的 RVA,即:section 表的位置
|
SizeOfRawData
|
DWORD
|
section 表占用映像的大小,这个 size 是以 0x200 为单位的
|
PointerToRawData
|
DWORD
|
section 表在映像中的物理位置,即是:file 位置,而非 virtual 位置
|
PointerToRelocation
|
DOWRD
| |
PointerToLinenumber
|
DWORD
| |
NumberOfRelocation
|
WORD
| |
NumberOfLineumbers
|
WORD
| |
Characteristics
|
DWORD
|
section 的属性 flags,可用于 '|' 多个属性值
|
所有的 Characteristics 都在 WinNT.h 中有定义,下面是一些常用的 flags:
#define IMAGE_SCN_CNT_CODE 0x00000020 // Section contains code. |
下面以 .text 节为例,看看 t.exe 的 .text 是什么。
.text 节的 Name 是 ".text",VirtualSize 是 0x42 bytes,.text 节在 RVA 为 0x00001000 的位置上,section 的分配单位为 0x1000(4K bytes),第 1 个 section 一般都分配在 0x1000 位置上,SizeOfRawData 为 0x200,这是映像文件分配单位。PointerToRawData 为 0x400,说明 .text 节在映像文件的 0x400 处。Characteristics 是 0x60000020,说明 .text 是 executable/readable/code 属性。