PE文件笔记四 PE文件头之标准PE头
转载好文 (๑•̀ㅂ•́)و✧
继续具体学习PE的各个结构细节,前面学完了DOS部首,接着学习PE文件头
由于PE文件头的内容较多,故要拆分为多个笔记,此笔记主要为标准PE头
PE文件头
PE文件头结构
两种PE文件头
PE文件头的结构有两种,分别对应32位的程序和64位的程序,它们的差异在于扩展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_NT_HEADERS { |
64位结构体
复制代码 隐藏代码typedef struct _IMAGE_NT_HEADERS64 { |
PE文件头标志
实例分析
根据DOS MZ头的最后一个成员找到PE文件头的首部,也就是PE文件头标志的首部
可以看到,PE文件头标志固定为 50 45 00 00 ,对应ASCII为“PE ” ,是用来判断文件是否为PE文件的标识之一,还有一个PE标识为MZ头
PE文件头标志 | 对应C语言变量 | 数据宽度 | 值 | 说明 |
---|---|---|---|---|
“PE”,0,0 | Signature | DWORD(4字节) | 50 45 00 00对应ASCII为“PE ” | PE文件标识 |
标准PE头
结构体截图
结构体代码
复制代码 隐藏代码typedef struct _IMAGE_FILE_HEADER { |
成员详情
成员 | 数据宽度 | 说明 | 值 |
---|---|---|---|
Machine | WORD(2字节) | 程序支持的CPU | 任意:0 Intel 386以及后续:14C x64:8664 |
NumberOfSections | WORD(2字节) | 节的数量 | 不大于96 |
TimeDateStamp | DWORD(4字节) | 编译器填写的时间戳 | 与文件属性里面(创建时间、修改时间)无关 |
PointerToSymbolTable | DWORD(4字节) | 指向符号表 | 调试相关 |
NumberOfSymbols | DWORD(4字节) | 符号表中的符号个数 | 调试相关 |
SizeOfOptionalHeader | WORD(2字节) | 可选PE头结构大小 | 32位PE文件:0xE0 64位PE文件:0xF0 |
Characteristics | WORD(2字节) | 文件属性 | 由数据位拼接而成,详见下方 |
Machine
计算机的体系结构类型。映像文件只能在指定的计算机或模拟指定计算机的系统上运行。此成员可以是以下值之一:
值 | 含义 |
---|---|
宏定义IMAGE_FILE_MACHINE_I386 = 0x014c | x86 |
宏定义IMAGE_FILE_MACHINE_IA64 = 0x0200 | Intel IPF |
宏定义IMAGE_FILE_MACHINE_AMD64 = 0x8664 | x64 |
IA64:就是所谓的安腾(Itanium)(IPF),Intel跟HP联合折腾的一种64-bits全新架构,与x86系列不兼容
NumberOfSections
节数。这表示紧跟在PE文件头后面的节表的大小。请注意,Windows加载程序将节数限制为96。
TimeDateStamp
Image时间戳的低32位。这表示链接器创建Image的日期和时间。根据系统时钟,该值以自1970年1月1日午夜(00:00:00)后经过的秒数表示。
PointerToSymbolTable
符号表的偏移量,以字节为单位,如果不存在COFF符号表,则为零。
COFF是指通用对象文件格式,在Microsoft 实现叫做可移植可执行 (PE) 文件格式,在Linux上的实现叫做(可执行与可链接)ELF文件格式;COFF全拼为:Common Object File Format
NumberOfSymbols
符号表中的符号数
SizeOfOptionalHeader
扩展PE头的大小,以字节为单位。对于对象文件(object files),此值应为0。
32位的PE文件默认值为0xE0 64位PE文件默认值为0xF0 该值可变
Characteristics
Image的文件属性,其值对应的数据位含义为:
Characteristics的数据宽度为WORD(2字节=16 bits)
假设Characteristics的十六进制为0102,分析其文件属性
首先将十六进制转化为二进制:0000 0001 0000 0010
此时可以发现数据位1和8的位置的值为1(数据位由0开始),对照上面可得出:文件属性为 文件是可执行的、只在32位平台上运行
实例分析
紧跟着上面PE文件头标志的实例分析,继续分析标准PE头对应的各个属性
根据标准PE头各个成员的数据宽度不难得出标准PE头的总宽度为:20字节(4个WORD+3个DWORD=4×2+3×4=20)
因此从前面PE文件头标志后再数20个字节都是标准PE头的数据
复制代码 隐藏代码 |
得到:
成员 | 说明 | 值 |
---|---|---|
Machine | x86 | 14C |
NumberOfSections | 有5个节 | 5 |
TimeDateStamp | 编译器填充的时间戳 | 55 AE 01 6B |
PointerToSymbolTable | 调试相关 | 00 00 00 00 |
NumberOfSymbols | 调试相关 | 00 00 00 00 |
SizeOfOptionalHeader | 可选PE头结构大小为E0 | E0 |
Characteristics | 文件属性为 文件可执行且只在32位平台上运行 | 102 |
自写代码解析PE文件头
因为有人提议用VS2019来编写,于是这里改成VS2019中的代码,但其实在VC6中也通用
复制代码 隐藏代码#include <windows.h> |
运行结果
分别演示32位程序和64位程序的运行结果
32位程序
64位程序
代码小解
代码中判断程序是32位或64位是看file->Machine的值来进行判断的,但其实这里并不一定准确,实际上应当判断opt->Magic才最为准确的。但关于扩展PE头的内容留作之后,这里为了学习标准PE头,故先采用这种方式进行判断,后面也会修正为使用opt->Magic来判断程序为32位或64位
代码中大部分都有注释,并不难理解,主要说明一下 让指针指向对应地址 的代码
复制代码 隐藏代码//让PE文件头标志指针指向其对应的地址=DOS首地址+偏移 |
指针的地址 = 首地址 + 偏移 这个没有什么好说的,主要在指针前的一个(UINT)强制类型转换
为什么要在指针前加一个(UINT)的强制类型转换?
这就涉及到指针的加减问题了,详解可参考:指针的加减
这里简单引用一下指针加减的结论:
无论是指针的加亦或是减(这里只演示了加法,但减法同理),其加或减的单位为去掉一个*后的数据宽度
也就是实际增减的数值=去掉一个*后的数据宽度 × 增减的数值
上面的指针都是一级结构体指针,DWORD,_IMAGE_FILE_HEADER,_IMAGE_OPTIONAL_HEADER,_IMAGE_OPTIONAL_HEADER64
去掉一个*后的数据宽度为结构体的大小,但是我们这里想要进行的增减的单位应该为字节,而不是结构体的大小,于是要将指针类型强转为UINT(无符号整数)类型(数据宽度为字节),使得其每次增减的单位为字节
总结
- PE文件头的起始位置由DOS MZ头的最后一个成员确定
- PE文件头标志固定ASCII为“PE ”,若不是则说明该文件非PE文件
- 标准PE头的第一个成员Machine可以判断程序为32位或64位
- 标准PE头的第二个成员NumberOfSections表示后面节的个数
- 可选PE头结构大小可变,且在标准PE头的第六个成员SizeOfOptionalHeader指定
- 标准PE头的最后一个成员Characteristics说明了该文件的属性
附件
附上本笔记中分析的EverEdit文件:点我下载
转载自吾爱破解的大佬