继续PE系列笔记的更新
PE其它笔记索引可前往:
PE文件笔记一 PE介绍
继续具体学习PE的各个结构细节,前面学完了标准PE头,接着学习扩展PE头
由于PE文件头的内容较多,故要拆分为多个笔记,此笔记主要为扩展PE头
PS:扩展PE头的成员较多,可以先看个大概后结合下面的实战分析来学习扩展PE头的成员
扩展PE头
扩展PE头所属
扩展PE头是PE文件头中的一个成员
32位所属
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE文件头标识
IMAGE_FILE_HEADER FileHeader; //标准PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //扩展PE头 32位
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
64位所属
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature; //PE文件头标识
IMAGE_FILE_HEADER FileHeader; //标准PE头
IMAGE_OPTIONAL_HEADER64 OptionalHeader; //扩展PE头 64位
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
两种扩展PE头差异
两种结构
扩展PE头的结构根据程序是32位或64位而分成了两种结构,而PE文件头则因扩展PE头的差异也被分成了两种结构
PE文件头结构 |
说明 |
_IMAGE_NT_HEADERS |
32位程序对应的PE文件头结构 |
_IMAGE_NT_HEADERS64 |
64位程序对应的PE文件头结构 |
_IMAGE_NT_HEADERS |
对应C中的结构体(类型) |
说明 |
"PE",0,0 |
DOWRD |
PE标识 |
IMAGE_FILE_HEADER |
IMAGE_FILE_HEADER |
标准PE头 |
IMAGE_OPTIONAL_HEADER32 |
IMAGE_OPTIONAL_HEADER32 |
扩展PE头 32位 |
_IMAGE_NT_HEADERS64 |
对应C中的结构体(类型) |
说明 |
"PE",0,0 |
DOWRD |
PE标识,固定值不可变 |
IMAGE_FILE_HEADER |
IMAGE_FILE_HEADER |
标准PE头 |
IMAGE_OPTIONAL_HEADER64 |
IMAGE_OPTIONAL_HEADER64 |
扩展PE头 64位 |
结构体比较
32位结构体
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;
64位结构体
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG 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;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
差异
成员\扩展PE头 |
_IMAGE_OPTIONAL_HEADER |
_IMAGE_OPTIONAL_HEADER64 |
BaseOfData |
DWORD |
无此成员 |
ImageBase |
DWORD |
ULONGLONG |
SizeOfStackReserve |
DWORD |
ULONGLONG |
SizeOfStackCommit |
DWORD |
ULONGLONG |
SizeOfHeapReserve |
DWORD |
ULONGLONG |
SizeOfHeapCommit |
DWORD |
ULONGLONG |
可以看到,64位相比于32位其实并没有太大的区别,只是删去了一个成员 以及 五个成员的数据类型由DWORD变为ULONGLONG
因为其区别并不明显,因此以32位结构体作为例子分析,64位结构体也近似相同
32位扩展PE头分析
扩展PE头中的成员较多,一般情况只需掌握部分重点即可(重点为表格中加黑的成员)
下面的分析参考自官方文档:ns-winnt-image_optional_header32和《Windows PE 权威指南》
PS:下面的分析较为冗长,类似参考文档,可以先跳过,先看下面的实战分析,根据实战里的数据到对应的成员参考其含义
成员 |
数据宽度 |
说明 |
Magic |
WORD(2字节) |
镜像文件的状态,可用于判断程序是32位还是64位 |
MajorLinkerVersion |
BYTE(字节) |
链接器的主要版本号 |
MinorLinkerVersion |
BYTE(字节) |
链接器的次要版本号 |
SizeOfCode |
DWORD(4字节) |
代码段的大小 |
SizeOfInitializedData |
DWORD(4字节) |
初始化数据段的大小 |
SizeOfUninitializedData |
DWORD(4字节) |
未初始化数据段的大小 |
AddressOfEntryPoint |
DWORD(4字节) |
程序入口 |
BaseOfCode |
DWORD(4字节) |
代码开始的基址 |
BaseOfData |
DWORD(4字节) |
数据开始的基址 |
ImageBase |
DWORD(4字节) |
内存镜像基址 |
SectionAlignment |
DWORD(4字节) |
内存对齐 |
FileAlignment |
WORD(2字节) |
文件对齐 |
MajorOperatingSystemVersion |
WORD(2字节) |
标识操作系统版本号 主版本号 |
MinorOperatingSystemVersion |
WORD(2字节) |
标识操作系统版本号 次版本号 |
MajorImageVersion |
WORD(2字节) |
PE文件自身的版本号 |
MinorImageVersion |
WORD(2字节) |
PE文件自身的版本号 |
MajorSubsystemVersion |
WORD(2字节) |
运行所需子系统版本号 |
MinorSubsystemVersion |
WORD(2字节) |
运行所需子系统版本号 |
Win32VersionValue |
DWORD(4字节) |
子系统版本的值,必须为0 |
SizeOfImage |
DWORD(4字节) |
Image大小 |
SizeOfHeaders |
DWORD(4字节) |
所有头+节表按照文件对齐后的大小 |
CheckSum |
DWORD(4字节) |
校验和 |
Subsystem |
WORD(2字节) |
子系统 |
DllCharacteristics |
WORD(2字节) |
文件特性 不只是针对DLL文件的 |
SizeOfStackReserve |
DWORD(4字节) |
初始化时保留的栈大小 |
SizeOfStackCommit |
DWORD(4字节) |
初始化时实际提交的大小 |
SizeOfHeapReserve |
DWORD(4字节) |
初始化时保留的堆大小 |
SizeOfHeapCommit |
DWORD(4字节) |
初始化时实践提交的大小 |
LoaderFlags |
DWORD(4字节) |
调试相关 |
NumberOfRvaAndSizes |
DWORD(4字节) |
目录项数目 |
DataDirectory[16] |
IMAGE_DATA_DIRECTORY[16]=128字节 |
指向数据目录中第一个IMAGE_DATA_DIRECTORY结构的指针(数据目录项) |
Magic
镜像文件的状态。该成员可以是以下值之一
值 |
含义 |
IMAGE_NT_OPTIONAL_HDR_MAGIC |
该文件是一个可执行的映像。这个值在32位应用程序中定义为IMAGE_NT_OPTIONAL_HDR32_MAGIC,在64位应用程序中定义为IMAGE_NT_OPTIONAL_HDR64_MAGIC |
IMAGE_NT_OPTIONAL_HDR32_MAGIC=0x10b |
该文件是一个可执行的映像(32位) |
IMAGE_NT_OPTIONAL_HDR64_MAGIC=0x20b |
该文件是一个可执行的映像(64位) |
IMAGE_ROM_OPTIONAL_HDR_MAGIC=0x107 |
该文件是ROM镜像 |
MajorLinkerVersion
链接器版本号
MinorLinkerVersion
链接器次要版本号
SizeOfCode
代码段的大小(以字节为单位),如果有多个代码段,则为所有这些代码段的总和。是文件对齐后的大小 编译器填的 没用(不一定准确)
SizeOfInitializedData
初始化数据段的大小(以字节为单位),如果有多个初始化数据段,则为所有这些数据段的总和。是文件对齐后的大小 编译器填的 没用(不一定准确)
SizeOfUninitializedData
未初始化数据段的大小(以字节为单位),如果有多个未初始化数据段,则为所有这些数据段的总和。是文件对齐后的大小 编译器填的 没用(不一定准确)
AddressOfEntryPoint
一个指向入口点函数的指针,相对于Image的基址。
- 对于可执行文件,这是起始地址
- 对于设备驱动程序,这是初始化函数的地址
- 入口点函数对于dll是可选的。当没有入口点存在时,该成员为零
BaseOfCode
指向代码段开头的指针,相对于ImageBase。编译器填的 没用(不一定准确)
BaseOfData
指向数据段开头的指针,相对于ImageBase。编译器填的 没用(不一定准确)
ImageBase
Image(PE文件)载入内存时第一个字节的首选地址。该值是64K字节的倍数
- dll的默认值是0x10000000
- 应用程序的默认值为0x00400000,
- Windows CE上的默认值为0x00010000
SectionAlignment
加载到内存中的节的对齐方式,以字节为单位。该值必须大于或等于FileAlignment(文件对齐)成员。默认值是系统的页面大小
FileAlignment
Image(PE文件)中各节的原始数据(以字节为单位)的对齐方式。该值应该是512到64K(包括)之间2的幂。缺省值是512。如果SectionAlignment成员小于系统页面大小,则该成员必须与SectionAlignment相同
MajorOperatingSystemVersion
所需操作系统的主要版本号
MinorOperatingSystemVersion
所需操作系统的次要版本号
MajorImageVersion
镜像(PE文件)的主版本号
MinorImageVersion
镜像(PE文件)的次要版本号
MajorSubsystemVersion
子系统的主要版本号
Win32VersionValue
该成员是保留的,并且必须为0
SizeOfImage
Image的大小,以字节为单位,包括所有头。必须是多个SectionAlignment
内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍
下列项的组合大小,舍入为“文件对齐”成员中指定值的倍数
- IMAGE_DOS_HEADER(DOS MZ头) 中的最后一个成员e_lfanew
- PE文件头标志 signature 的 大小 4字节
- IMAGE_FILE_HEADER(标准PE头)的大小
- 扩展PE头的大小
- 所有节头(节表)的大小
SizeOfHeaders=
{
e_lfanew
+sizeof(signature)
+sizeof(_IMAGE_FILE_HEADER)
+sizeof(_IMAGE_OPTIONAL_HEADER)
+sizeof(_IMAGE_SECTION_HEADER)
}(文件对齐)
即 DOS部首+PE文件头+节表 按照文件对齐后的大小
CheckSum
Image(PE文件)校验和。以下文件在加载时进行验证:所有驱动程序,在引导时加载的任何DLL,以及加载到关键系统进程中的任何DLL
Subsystem
运行此映像所需的子系统。定义了以下值:
宏定义 |
值 |
含义 |
IMAGE_SUBSYSTEM_UNKNOWN |
0 |
未知的子系统 |
IMAGE_SUBSYSTEM_NATIVE |
1 |
不需要子系统(设备驱动程序和本机系统进程) |
IMAGE_SUBSYSTEM_WINDOWS_GUI |
2 |
Windows图形用户界面子系统 |
IMAGE_SUBSYSTEM_WINDOWS_CUI |
3 |
Windows字符模式用户界面(CUI)子系统 |
IMAGE_SUBSYSTEM_OS2_CUI |
5 |
OS/2 CUI子系统 |
IMAGE_SUBSYSTEM_POSIX_CUI |
7 |
POSIX CUI子系统 |
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI |
9 |
Windows CE系统 |
IMAGE_SUBSYSTEM_EFI_APPLICATION |
10 |
可扩展固件接口(EFI)应用程序 |
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER |
11 |
带有引导服务的EFI驱动程序 |
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER |
12 |
带有运行时服务的EFI驱动程序 |
IMAGE_SUBSYSTEM_EFI_ROM |
13 |
EFI ROM镜像 |
IMAGE_SUBSYSTEM_XBOX |
14 |
Xbox系统 |
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION |
16 |
启动应用程序 |
DllCharacteristics
官方文档版本
Image的DLL特性,定义了以下值
宏定义 |
值 |
含义 |
无 |
0x0001 |
保留,必须为0 |
无 |
0x0002 |
保留,必须为0 |
无 |
0x0004 |
保留,必须为0 |
无 |
0x0008 |
保留,必须为0 |
IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA |
0x0020 |
具有64位地址空间的ASLR |
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE |
0x0040 |
DLL可以在加载时重新定位 |
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY |
0x0080 |
强制进行代码完整性检查。如果你设置了这个标志,并且section只包含未初始化的数据,那么将该section的IMAGE_SECTION_HEADER的PointerToRawData成员设置为0;否则,由于无法验证数字签名,Image将无法加载 |
IMAGE_DLLCHARACTERISTICS_NX_COMPAT |
0x0100 |
该映像与数据执行预防(DEP)兼容 |
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION |
0x0200 |
映像可以被隔离,但不应该被隔离 |
IMAGE_DLLCHARACTERISTICS_NO_SEH |
0x0400 |
该映像不使用结构化异常处理(SEH)。在此映像中不能调用任何处理程序 |
IMAGE_DLLCHARACTERISTICS_NO_BIND |
0x0800 |
不要绑定映像 |
IMAGE_DLL_CHARACTERISTICS_APPCONTAINER |
0x1000 |
映像应该在AppContainer中执行 |
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER |
0x2000 |
一个WDM驱动 |
IMAGE_DLL_CHARACTERISTICS_GUARD_CF |
0x4000 |
映像支持控制流保护(Control Flow Guard) |
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE |
0x8000 |
该映像是终端服务器感知的 |
PE权威指南版本
SizeOfStackReserve
为堆栈保留的字节数。只有SizeOfStackCommit成员指定的内存在加载时被提交;其余的页面每次只提供一个页面,直到达到这个预留大小
SizeOfStackCommit
要提交给堆栈的字节数
SizeOfHeapReserve
为本地堆保留的字节数。只有SizeOfHeapCommit成员指定的内存在加载时被提交;其余的页面每次只提供一个页面,直到达到这个预留大小
SizeOfHeapCommit
要为本地堆提交的字节数
LoaderFlags
该成员已过时
NumberOfRvaAndSizes
可选头的其余部分中的目录条目数。每个条目都描述了一个位置和大小
DataDirectory
指向数据目录中第一个IMAGE_DATA_DIRECTORY结构的指针
所需目录条目的索引号。该参数可以是以下值之一:
宏定义 |
值 |
含义 |
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE |
7 |
特定于体系结构的数据,预留为0 |
IMAGE_DIRECTORY_ENTRY_BASERELOC |
5 |
基地址重定位表 |
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT |
11 |
绑定导入表 |
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR |
14 |
COM描述符表 |
IMAGE_DIRECTORY_ENTRY_DEBUG |
6 |
调试表 |
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT |
13 |
延迟导入表 |
IMAGE_DIRECTORY_ENTRY_EXCEPTION |
3 |
异常表 |
IMAGE_DIRECTORY_ENTRY_EXPORT |
0 |
导出表 |
IMAGE_DIRECTORY_ENTRY_GLOBALPTR |
8 |
全局指针的相对虚拟地址 |
IMAGE_DIRECTORY_ENTRY_IAT |
12 |
导入地址表 |
IMAGE_DIRECTORY_ENTRY_IMPORT |
1 |
导入表 |
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG |
10 |
加载配置表 |
IMAGE_DIRECTORY_ENTRY_RESOURCE |
2 |
资源表 |
IMAGE_DIRECTORY_ENTRY_SECURITY |
4 |
安全表 |
IMAGE_DIRECTORY_ENTRY_TLS |
9 |
线程本地存储表 |
实战分析
获取数据
从先前分析的标准PE头的结尾开始看起,选中部分为扩展PE头,共占224字节
按顺序依次将数据填入对应的成员得到:
成员 |
值 |
Magic |
0x010B |
MajorLinkerVersion |
0x09 |
MinorLinkerVersion |
0x00 |
SizeOfCode |
0x00199200 |
SizeOfInitializedData |
0x000BEC00 |
SizeOfUninitializedData |
0x00000000 |
AddressOfEntryPoint |
0x0016AF12 |
BaseOfCode |
0x00001000 |
BaseOfData |
0x0019B000 |
ImageBase |
0x00400000 |
SectionAlignment |
0x00001000 |
FileAlignment |
0x00000200 |
MajorOperatingSystemVersion |
0x0005 |
MinorOperatingSystemVersion |
0x0000 |
MajorImageVersion |
0x0000 |
MinorImageVersion |
0x0000 |
MajorSubsystemVersion |
0x0005 |
MinorSubsystemVersion |
0x0000 |
Win32VersionValue |
0x00000000 |
SizeOfImage |
0x00298000 |
SizeOfHeaders |
0x00000400 |
CheckSum |
0x0025cd89 |
Subsystem |
0x0002 |
DllCharacteristics |
0x8140 |
SizeOfStackReserve |
0x00100000 |
SizeOfStackCommit |
0x00001000 |
SizeOfHeapReserve |
0x00100000 |
SizeOfHeapCommit |
0x00001000 |
LoaderFlags |
0x00000000 |
NumberOfRvaAndSizes |
0x00000010 |
DataDirectory[16] |
留作之后的笔记 |
分析数据
鉴于篇幅和实用性考虑,只分析比较重要的成员
Magic
Magic的值为0x010B,根据前面的分析可知:该文件是一个可执行的映像(32位)
AddressOfEntryPoint
AddressOfEntryPoint的值为0x0016AF12,根据前面的分析可知:一个指向入口点函数的指针,相对于Image的基址是0x0016AF12
而Image的基址就是后面的 ImageBase = 0x00400000
于是该指针的的绝对地址为基址+偏移=0x00400000+0x0016AF12=0x0056AF12
于是得到了该程序的程序入口点为0x0056AF12,为了验证其准确性,使用OD打开程序
可以看到OD自动中断在了程序入口点0x0056AF12
PS:如果OD暂停到了其它地方,则需要设置一下OD的中断点:
选项→调试设置(或使用快捷键Alt+0)
事件→主模块入口点
ImageBase
ImageBase的值为0x00400000,根据前面的分析可知:ImageBase正好是应用程序的默认值0x00400000
且ImageBase=0x00400000是64K字节的倍数
有关内存对齐和文件对齐的相关内容 在PE文件笔记二 PE文件的两种状态已经学习过了,这里不再赘述
SizeOfImage
SizeOfImage的值为0x00298000,根据前面的分析可知:
SizeOfImage表示内存中整个PE文件的映射的尺寸,可比实际的值大,必须是SectionAlignment的整数倍
于是打开程序,并使用WinHex查看其在内存中的状态(在PE文件笔记二 PE文件的两种状态中已经演示过,这里不再赘述,直接看结果)
用WinHex附加上程序后,拉到最底部,查看文件的最大偏移量
可以看到此时的最大偏移量为697FFF,用最大偏移量减去ImageBase 0x400000得到相对偏移为297FFF=SizeOfImage-1
验证了SizeOfImage可以比实际的值大
再验证SizeOfImage为内存对齐SectionAlignment的整数倍:
SizeOfImage/SectionAlignment=0x00298000/0x1000=0x298,可以整除,验证完毕
CheckSum
CheckSum从头部开始 两两字节不断相加,一直相加到最后,期间如果溢出,则直接舍去溢出部分,最后加完的结果再加上整个文件的长度就得到了CheckSum,操作系统就是通过这种方式来检验文件是否被修改
但只对以下文件在加载时进行验证:所有驱动程序,在引导时加载的任何DLL,以及加载到关键系统进程中的任何DLL
此时程序本身并不属于上面提到的文件类型,于是此时的CheckSum并不生效
由于CheckSum的计算比较麻烦,于是这里就略去CheckSum计算的验证,简单说明了CheckSum的计算原理
下面来验证一下CheckSum的作用范围,此时程序的CheckSum应该是无效的
CheckSum的值为0x0025cd89,将它修改为0
1.找到CheckSum
2.选中CheckSum,鼠标右键→编辑
3.填充选块
4.填充0
5.修改完毕
6.保存,然后打开程序
使用快捷键 Ctrl+S 保存,确定
打开程序,依旧可以正常运行,验证完毕
Subsystem
Subsystem的值为0x0002,根据前面的分析可知:运行该程序所需子系统为:Windows图形用户界面子系统
DllCharacteristics
DllCharacteristics的值为0x8140
通过PE权威指南
转化为二进制得到:1000000101000000
数据位为1的位数有:第6位,第8位,第15位
根据前面的分析可知:DLL可以在加载时重新定位、该映像与数据执行预防(DEP)兼容、该映像是终端服务器感知的
通过官方文档
DllCharacteristics的值为0x8140=0x8000+0x0100+0x0040
根据前面的分析可知:DLL可以在加载时重新定位、该映像与数据执行预防(DEP)兼容、该映像是终端服务器感知的
自写代码解析PE文件头
在先前代码的基础上,进一步改进
// PE.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <windows.h>
#include <winnt.h>
//在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义
#define IMAGE_FILE_MACHINE_AMD64 0x8664
int main(int argc, char* argv[])
{
//创建DOS对应的结构体指针
_IMAGE_DOS_HEADER* dos;
//读取文件,返回文件句柄
HANDLE hFile = CreateFileA("C:\\Users\\lyl610abc\\Desktop\\dbgview64.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
//根据文件句柄创建映射
HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0);
//映射内容
LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
//类型转换,用结构体的方式来读取
dos = (_IMAGE_DOS_HEADER*)pFile;
//输出dos->e_magic,以十六进制输出
printf("dos->e_magic:%X\n", dos->e_magic);
//创建指向PE文件头标志的指针
DWORD* peId;
//让PE文件头标志指针指向其对应的地址=DOS首地址+偏移
peId = (DWORD*)((UINT)dos + dos->e_lfanew);
//输出PE文件头标志,其值应为4550,否则不是PE文件
printf("peId:%X\n", *peId);
//创建指向可选PE头的第一个成员magic的指针
WORD* magic;
//让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小
magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
//输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序
printf("magic:%X\n", *magic);
//根据magic判断为32位程序还是64位程序
switch (*magic) {
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
printf("32位程序\n");
//确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS* nt;
//让PE文件头指针指向其对应的地址
nt = (_IMAGE_NT_HEADERS*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
break;
}
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
printf("64位程序\n");
//确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS64* nt;
nt = (_IMAGE_NT_HEADERS64*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);
break;
}
default:
{
printf("error!\n");
break;
}
}
return 0;
}
运行结果
32位程序
64位程序
代码说明
代码基于上一次的笔记PE文件笔记四 PE文件头之标准PE头改进了判断程序32位或64位的方法,并没有什么太大的变动,有关代码的分析在上一次笔记已经给出,这里也就不再赘述
总结
- 扩展PE头根据程序是32位或64位而对应两种不同的结构
- 根据扩展PE头的第一个成员Magic可以判断出程序是32位的还是64位的
- 两种扩展PE头结构实际相差不多,掌握了32位的结构其实也相当于掌握了64位的结构
- 扩展PE头英文虽然为optional header,译为可选头,但它其实必不可少且极其重要
- 扩展PE头中的DataDirectory内容涉及较多,留作之后
附件
附上本笔记中分析的EverEdit文件:点我下载