PE文件笔记五 PE文件头之扩展PE头
继续具体学习PE的各个结构细节,前面学完了标准PE头,接着学习扩展PE头
由于PE文件头的内容较多,故要拆分为多个笔记,此笔记主要为扩展PE头
PS:扩展PE头的成员较多,可以先看个大概后结合下面的实战分析来学习扩展PE头的成员
扩展PE头
扩展PE头所属
扩展PE头是PE文件头中的一个成员
32位所属
复制代码 隐藏代码typedef struct _IMAGE_NT_HEADERS { |
64位所属
复制代码 隐藏代码typedef struct _IMAGE_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 { |
64位结构体
复制代码 隐藏代码typedef struct _IMAGE_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的整数倍
SizeOfHeaders
下列项的组合大小,舍入为“文件对齐”成员中指定值的倍数
- 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字节的倍数
SectionAlignment、FileAlignment、SizeOfHeaders
有关内存对齐和文件对齐的相关内容 在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. |
运行结果
32位程序
64位程序
代码说明
代码基于上一次的笔记PE文件笔记四 PE文件头之标准PE头改进了判断程序32位或64位的方法,并没有什么太大的变动,有关代码的分析在上一次笔记已经给出,这里也就不再赘述
总结
- 扩展PE头根据程序是32位或64位而对应两种不同的结构
- 根据扩展PE头的第一个成员Magic可以判断出程序是32位的还是64位的
- 两种扩展PE头结构实际相差不多,掌握了32位的结构其实也相当于掌握了64位的结构
- 扩展PE头英文虽然为optional header,译为可选头,但它其实必不可少且极其重要
- 扩展PE头中的DataDirectory内容涉及较多,留作之后
附件
附上本笔记中分析的EverEdit文件:点我下载
转载自吾爱破解上的大佬