1 PE文件头(NT文件头)
从DOS文件头的_lfanew字段(文件头偏移003ch)得到真正的PE文件头位置[$0000 0100]后,现在来看看它的定义,PE文件头是由IMAGE_NT_HEADERS结构定义的:
PImageNtHeaders = ^TImageNtHeaders;
_IMAGE_NT_HEADERS = packed record
[$0100]Signature: DWORD;
[$0104]FileHeader: TImageFileHeader;
[$0118]OptionalHeader: TImageOptionalHeader;
end;
TImageNtHeaders = _IMAGE_NT_HEADERS;
IMAGE_NT_HEADERS = _IMAGE_NT_HEADERS;
PE文件头的第一个双字是一个标志,它被定义为00004550h,也就是字符“P”,“E”加上两个0,这也是“PE”这个称呼的由来,大部分的文件属性由标志后面的IMAGE_FILE_HEADER和IMAGE_OPTIONAL_HEADER32结构来定义,从名称看,似乎后面的这个PE文件表头结构是可选的(Optional),但实际上这个名称是名不符实的,因为它总是存在于每个PE文件中。
1. IMAGE_FILE_HEADER结构
IMAGE_FILE_HEADER结构的定义如下所示:
_IMAGE_FILE_HEADER = packed record
[$0104]Machine: Word; //014ch Intel 386
[$0106]NumberOfSections: Word; //文件中存在的节的数量
[$0108]TimeDateStamp: DWORD;
[$010c]PointerToSymbolTable: DWORD;
[$0110]NumberOfSymbols: DWORD;
[$0114]SizeOfOptionalHeader: Word; //IMAGE_OPTIONAL_HEADER32结构的长度,00e0h
[$0116]Characteristics: Word;
end;
TImageFileHeader = _IMAGE_FILE_HEADER;
IMAGE_FILE_HEADER = _IMAGE_FILE_HEADER;
2. IMAGE_OPTIONAL_HEADER32结构
定义IMAGE_OPTIONAL_HEADER32结构的本意在于让不同的开发者能够在PE文件头中使用自定义的数据,这就是结构名称中“Optional”一词的由来,但实际上IMAGE_FILE_HEADER结构不足以用来定义PE文件的属性,反而在这个“可选”的部分中有着更多的定义数据,对于读者来说,可以完全不必考虑这两个结构的区别在哪里,只要把它们当成是连在一起的“PE文件头结构”就可以了。
IMAGE_OPTIONAL_HEADER32结构的定义如下:
_IMAGE_OPTIONAL_HEADER = packed record
{ Standard fields. }
[$0118]Magic: Word;
[$011a]MajorLinkerVersion: Byte;
[$011b]MinorLinkerVersion: Byte;
[$011c]SizeOfCode: DWORD;
[$0120]SizeOfInitializedData: DWORD;
[$0124]SizeOfUninitializedData: DWORD;
[$0128]AddressOfEntryPoint: DWORD;
[$012c]BaseOfCode: DWORD;
[$0130]BaseOfData: DWORD;
{ NT additional fields. }
[$0134]ImageBase: DWORD; //程序的建议装载地址
[$0138]SectionAlignment: DWORD;
[$013c]FileAlignment: DWORD;
[$0140]MajorOperatingSystemVersion: Word;
[$0142]MinorOperatingSystemVersion: Word;
[$0144]MajorImageVersion: Word;
[$0146]MinorImageVersion: Word;
[$0148]MajorSubsystemVersion: Word;
[$014a]MinorSubsystemVersion: Word;
[$014c]Win32VersionValue: DWORD;
[$0150]SizeOfImage: DWORD;
[$0154]SizeOfHeaders: DWORD;
[$0158]CheckSum: DWORD;
[$015c]Subsystem: Word;
[$015e]DllCharacteristics: Word;
[$0160]SizeOfStackReserve: DWORD;
[$0164]SizeOfStackCommit: DWORD;
[$0168]SizeOfHeapReserve: DWORD;
[$016c]SizeOfHeapCommit: DWORD;
[$0170]LoaderFlags: DWORD;
[$0174]NumberOfRvaAndSizes: DWORD;
[$0178]DataDirectory: packed array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory;
end;
TImageOptionalHeader = _IMAGE_OPTIONAL_HEADER;
IMAGE_OPTIONAL_HEADER = _IMAGE_OPTIONAL_HEADER;
● AddressOfEntryPoint字段
指出文件被执行时的入口地址,这是一个RVA地址(RVA的含义在下一节中详细介绍)。如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了。
● ImageBase字段
指出文件的优先装入地址。也就是说当文件被执行时,如果可能的话,Windows优先将文件装入到由ImageBase字段指定的地址中,只有指定的地址已经被其他模块使用时,文件才被装入到其他地址中。链接器产生可执行文件的时候对应这个地址来生成机器码,所以当文件被装入这个地址时不需要进行重定位操作,装入的速度最快,如果文件被装载到其他地址的话,将不得不进行重定位操作,这样就要慢一点。
对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被其他模块占据,所以EXE总是能够按照这个地址装入,这也意味着EXE文件不再需要重定位信息。对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被其他的DLL使用,所以DLL文件中必须包含重定位信息以防万一。
一般EXE文件的默认优先装入地址被定为00400000h,而DLL文件的默认优先装入地址被定为10000000h。
● DataDirectory字段
这个字段,由16个相同的IMAGE_DATA_DIRECTORY结构组成,虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的。IMAGE_DATA_DIRECTORY结构指出了某种数据块的位置和长度。
_IMAGE_DATA_DIRECTORY = record
VirtualAddress: DWORD; //数据的起始RVA
Size: DWORD; //数据块的长度
end;
TImageDataDirectory = _IMAGE_DATA_DIRECTORY;
IMAGE_DATA_DIRECTORY = _IMAGE_DATA_DIRECTORY;
数据目录列表的含义
IMAGE_DIRECTORY_ENTRY_EXPORT = 0; { Export Directory }
IMAGE_DIRECTORY_ENTRY_IMPORT = 1; { Import Directory }
IMAGE_DIRECTORY_ENTRY_RESOURCE = 2; { Resource Directory }
IMAGE_DIRECTORY_ENTRY_EXCEPTION = 3; { Exception Directory }
IMAGE_DIRECTORY_ENTRY_SECURITY = 4; { Security Directory }
IMAGE_DIRECTORY_ENTRY_BASERELOC = 5; { Base Relocation Table }
IMAGE_DIRECTORY_ENTRY_DEBUG = 6; { Debug Directory }
IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7; { Description String }
IMAGE_DIRECTORY_ENTRY_GLOBALPTR = 8; { Machine Value (MIPS GP) }
IMAGE_DIRECTORY_ENTRY_TLS = 9; { TLS Directory }
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG = 10; { Load Configuration Directory }
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT = 11; { Bound Import Directory in headers }
IMAGE_DIRECTORY_ENTRY_IAT = 12; { Import Address Table }
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT = 13; { Delay Load Import Descriptors }
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14; { COM Runtime descriptor }