结构体源代码如下:
typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // +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]; // 数据目录表 ***(必须了解,重点)*** winNT发布到win10,IMAGE_NUMBEROF_DIRECTORY_ENTRIES一直都是16 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
AddressOfEntryPoint ***(必须了解)***
程序开始执行的地址,这是一个RVA(相对虚拟地址)。对于exe文件,这里是启动代码;对于dll文件,这里是libMain()的地址。如果在一个可执行文件上附加了一段代码并想让这段代码首先被执行,那么只需要将这个入口地址指向附加的代码就可以了。在脱壳时第一件事就是找入口点,指的就是这个值。
ImageBase ***(必须了解)***
PE文件的优先装入地址。也就是说,当文件被执行时,如果可能的话(当前地址没有被使用),Windows优先将文件装入到由ImageBase字段指定的地址中。
对于EXE文件来说,由于每个文件总是使用独立的虚拟地址空间,优先装入地址不可能被**模块占据,所以EXE总是能够按照这个地址装入。
这也意味着EXE文件不再需要重定位信息。
对于DLL文件来说,由于多个DLL文件全部使用宿主EXE文件的地址空间,不能保证优先装入地址没有被**的DLL使用,所以DLL文件中必须包含重定位信息以防万一。
因此,在前面介绍的 IMAGE_FILE_HEADER 结构的 Characteristics 字段中,DLL 文件对应的 IMAGE_FILE_RELOCS_STRIPPED 位总是为0,而EXE文件的这个标志位总是为1。
如果没有指定的话,dll文件默认为0x10000000;exe文件默认为0x00400000,但是在Windows CE平台上是0x00010000。此值必须是64K bytes的倍数!
SectionAlignment ***(必须了解)***
内存中区块的对齐单位。区块总是对齐到这个值的整数倍。此字段必须大于或等于 FileAlignment ,默认值是系统页面的大小。32位cpu通常值为 0x1000(十六进制),即4096,即4KB。64位cpu通常为 8kB
FileAlignment ***(必须了解)***
pe文件中区块的对齐单位,以bytes(字节)为单位。此值必须是2的次方倍,但是必须在512和64K区间之间(闭区间[521, 64*1024=65536]),如果SectionAlignment小于系统页面的大小,那么SectionAlignment的大小就和FileAlignment相同。pe文件中默认值为 521 字节(0.5KB) 即 0x200(十六进制)。
Subsystem ***(必须了解)***
pe文件的用户界面使用的子系统类型。定义如下:
#define IMAGE_SUBSYSTEM_UNKNOWN 0 // 未知子系统 #define IMAGE_SUBSYSTEM_NATIVE 1 // 不需要子系统(如驱动程序) #define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Windows GUI 子系统 #define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Windows 控制台子系统 #define IMAGE_SUBSYSTEM_OS2_CUI 5 // OS/2 控制台子系统 #define IMAGE_SUBSYSTEM_POSIX_CUI 7 // Posix 控制台子系统 #define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 // 镜像是原生 Win9x 驱动程序 #define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Windows CE 图形界面
例如,Visual Studio 2015中编译程序时可以在图形界面设置链接选项:
更多请查看:
微软官方文档:https://msdn.microsoft.com/en-us/library/windows/desktop/ms680339(v=vs.85).aspx
DataDirectory ***(必须了解,重要)***
这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成。其结构如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 相对虚拟地址
DWORD Size; // 数据块的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
也就是定义了某块的位置和大小。
虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的(如下表所示)。IMAGE_DATA_DIRECTORY结构的定义很简单,它仅仅指出了某种数据块的位置和长度。
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出表 #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入表 #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源表 #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 异常表(具体资料不详) #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 安全表(具体资料不详) #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // 重定位表 #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 调试表 // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) 版权信息 #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // 版权信息 #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP (具体资料不详) #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory (线程位置存储,具体资料不详) #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory (不详) #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers(不详) #define IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入函数地址表 #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors(不详) #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor(不详)