WORD NumberOfSections; // 文件的节数目
DWORD TimeDateStamp; // 文件创建日期和时间
DWORD PointerToSymbolTable; // 指向符号表(用于调试)
DWORD NumberOfSymbols; // 符号表中的符号数量
WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HANDLER32结构的长度
WORD Characteristics; // 文件的属性 exe=010fh dll=210eh
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
此外IMAGE_NT_HEADERS
还包含了IMAGE_OPTIONAL_HEADER
可选头的信息,用于描述PE文件的高级结构信息,包括各种代码段、数据段、栈大小、堆大小、程序入口点、镜像基址等等。
我们继续跟进_IMAGE_NT_HEADERS
结构体里面的第二个结构IMAGE_OPTINAL_HEADER
,该头结构非常重要要,里面存储着程序的数据目录表,可选头紧挨着文件头,文件头的结束位置在0x000000DF
,那么可选头的起始位置为0x000000E0
,可选头的大小在文件头中已经给出,其大小为0x00E0
字节,其结束位置为0x000000E0 + 0x00E0 – 1 = 0x000001BF
,可选头非常容易辨别,只需要找到PE字眼就是了。
可选头是对文件头的一个扩展,文件头主要描述文件的相关信息,而可选头主要用来管理PE文件被操作系统装载时所需要的信息,该头是有32位版本与64位版本之分的,其实IMAGE_OPTIONAL_HEADER
是一个宏,定义如下所示;
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
#define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107
#ifdef _WIN64
typedef IMAGE_OPTIONAL_HEADER64 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER64 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR64_MAGIC
#else
typedef IMAGE_OPTIONAL_HEADER32 IMAGE_OPTIONAL_HEADER;
typedef PIMAGE_OPTIONAL_HEADER32 PIMAGE_OPTIONAL_HEADER;
#define IMAGE_NT_OPTIONAL_HDR_MAGIC IMAGE_NT_OPTIONAL_HDR32_MAGIC
#endif
32位版本和64位版本的选择是根据是否定义了_WIN64
而决定的,这里只讨论其32位的版本,IMAGE_OPTIONAL_HEADER32
的定义如下所示;
typedef struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic; // 0x10b(可执行文件) 0x107(ROM文件)
BYTE MajorLinkerVersion; // 主连接器版本号
BYTE MinorLinkerVersion; // 次连接器版本号
DWORD SizeOfCode; // 所有包含代码节的总大小
DWORD SizeOfInitializedData; // 所有已初始化数据的节总大小
DWORD SizeOfUninitializedData; // 所有未初始化数据的节总大小
DWORD AddressOfEntryPoint; // 程序执行入口RVA
DWORD BaseOfCode; // 代码节的起始RVA
DWORD BaseOfData; // 数据节的起始RVA
DWORD ImageBase; // 程序镜像基地址
DWORD SectionAlignment; // 内存中节的对其粒度
DWORD FileAlignment; // 文件中节的对其粒度
WORD MajorOperatingSystemVersion; // 要求最低操作系统的主版本号
WORD MinorOperatingSystemVersion; // 要求最低操作系统的次版本号
WORD MajorImageVersion; // 可执行文件的主版本号
WORD MinorImageVersion; // 可执行文件的次版本号
WORD MajorSubsystemVersion; // 可运行于操作系统的最小子版本号
WORD MinorSubsystemVersion;
DWORD Win32VersionValue; // 该成员变量是被保留的
DWORD SizeOfImage; // 内存中整个PE映像尺寸
DWORD SizeOfHeaders; // 所有头加节表的大小
DWORD CheckSum; // 校验和值
WORD Subsystem; // 可执行文件的子系统类型
WORD DllCharacteristics; // 指定DLL文件的属性,该值大部分时候为0
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;
从上方结构体定义中可知,最后一个结构属性IMAGE_DATA_DIRECTORY
其又指向了数据目录列表,该表由16个相同的IMAGE_DATA_DIRECTORY
结构组成,这16个数据目录结构定义很简单,仅仅指出了某种数据的位置和长度,该结构的定义如下;
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_DATA_DIRECTORY
{
DWORD VirtualAddress; // 数据起始RVA
DWORD Size; // 数据块的长度
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
有了上方的解析流程,读者应该能理解如何实现分析PE头了,首先读者找到DOS
头,并从该头部找到NT
头,当读者得到了NT头就可以根据NT头向下分别解析FileHeader
及OptionalHeader
中的参数,根据参数定义依次输出即可得到所有的NT头部数据,其完整代码如下所示;
int main(int argc, char * argv[])
{
BOOL PE = IsPeFile