PE文件结构详解
- 一、前言
- 1.概述
- 2.PE文件结构
- 3.所用工具
- 二、DOS头(DOS Header)解析
- 1.作用
- 2.图例
- 3.参数详解
- 4.总结
- 三、DOS Stub
- 1.作用
- 2.图例
- 四、NT头(NT Header)解析
- 1.作用
- 2.PE标识图例
- 3.文件头(COFF头)图例
- 4.可选头(Optional Header)图例
- 五、区段头(Section Header)
- 1.作用
- 2.图例
- 六、附C++解析源码
一、前言
1.概述
PE文件(Portable Executable File)是Windows上最常见的可执行文件,按文件后缀来说就是.exe
、.dll
文件,还有一些其他的文件,例如.sys
系统文件,不过最常见以及常用的就是.exe
和.dll
,在初学阶段狭义上也可以就把PE文件就理解成.exe和.dll文件。
2.PE文件结构
- DOS头(DOS Header)
- DOS Stub
- NT头(NT Header)
- PE标识(Signature)
- 文件头(File Header)
- 可选头(OptionHeader)
- 区段头(Section Header)
3.所用工具
WinHex-20.7-x86-x64.exe
PETool v1.0.0.5.exe
010EditorProtable
二、DOS头(DOS Header)解析
1.作用
- 兼容性: 让老的DOS系统识别这是一个可执行文件,即使它不能运行。
- 定位PE头: e_lfanew字段指向PE头的开始位置,操作系统通过这个字段找到PE文件的真正头部,从而加载和执行文件。
2.图例
范围:起始地址开始,长度64字节
我们可以通过使用WinHex
软件来打开一个PE文件,其中如下图红框包裹的部分就是DOS头的内容,长度固定为64个字节。
3.参数详解
其中注释的内容不重要,可以忽略。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE头
WORD e_magic; // 魔数(Magic number),固定MZ
//WORD e_cblp; // 文件最后一页的字节数
//WORD e_cp; // 文件中的页数
//WORD e_crlc; // 重定位项数目
//WORD e_cparhdr; // 头部大小,以段落(16字节)为单位
//WORD e_minalloc; // 程序所需的最小额外段数
//WORD e_maxalloc; // 程序所需的最大额外段数
//WORD e_ss; // 初始(相对)SS值
//WORD e_sp; // 初始SP值
//WORD e_csum; // 校验和
//WORD e_ip; // 初始IP值
//WORD e_cs; // 初始(相对)CS值
//WORD e_lfarlc; // 重定位表的文件地址
//WORD e_ovno; // 覆盖编号
//WORD e_res[4]; // 保留字
//WORD e_oemid; // OEM标识符(用于e_oeminfo)
//WORD e_oeminfo; // OEM信息(由e_oemid指定)
//WORD e_res2[10]; // 保留字
LONG e_lfanew; // 新EXE头的文件地址(PE头的偏移量)
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
4.总结
其中的e_magic参数可以用来判断这个文件是不是PE文件。
e_lfanew表示的是新的PE头的偏移位置,例如程序的起始地址是0x01,e_lfanew的值是0xF0,那么新的PE头的位置就是0x01+0xF0。
三、DOS Stub
1.作用
一个典型的DOS Stub可能会包含一个简短的DOS程序,这个程序通常会执行以下操作:
- 显示一条消息,说明该程序需要在Windows环境下运行。
- 终止程序的执行。
约等于没有用,因为DOS Stub在windwos系统上是不会执行的。
2.图例
范围:DOS头后开始,至e_lfanew偏移量结束。
我们可以看到红框的右边解析的Ascii码,里面有这么一段文字。
This program cannot be run in DOS mode.
可得出结论这玩意在windows上没什么用,就是在DOS系统上显示消息用的。
范围为之后 至 e_lfanew偏移量之前。
四、NT头(NT Header)解析
1.作用
NT头,其中包含三个部分
- PE标识(PE签名,Signature)
- 标准文件头(COFF头,Common Object File Format Header)
- 可选头(Optional Header)
PE头就是我们PE文件的最重要的部分之一了,其中包含了很多重要的信息。
2.PE标识图例
范围:e_lfanew偏移量开始,长度4字节。
其中数据0x00004550(从小到大读)就是:
50=P
45=E
00=\0
00=\0
3.文件头(COFF头)图例
范围:PE标识开始,长度20字节
注释的内容不重要,可以忽略。
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 指定目标机器类型
WORD NumberOfSections; // 文件中的节数
//DWORD TimeDateStamp; // 文件创建的时间戳
//DWORD PointerToSymbolTable; // 指向符号表的指针(通常为0)
//DWORD NumberOfSymbols; // 符号表中的符号数(通常为0)
WORD SizeOfOptionalHeader; // 可选头的大小
WORD Characteristics; // 文件的属性标志
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
4.可选头(Optional Header)图例
文件头开始,长度为文件头的SizeOfOptionalHeader属性
注释的内容不重要,可以忽略。
其中的DllCharacteristics中有一条DYNAMIC_BASE表示是否动态基址,可以用010 editor来查看这个值,如下图。
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 标识文件类型,0x10B表示PE32,0x20B标识PE64
// BYTE MajorLinkerVersion; // 链接器的主版本号
// BYTE MinorLinkerVersion; // 链接器的次版本号
// DWORD SizeOfCode; // 所有代码节的总大小
// DWORD SizeOfInitializedData; // 所有已初始化数据节的总大小
// DWORD SizeOfUninitializedData; // 所有未初始化数据节的总大小
DWORD AddressOfEntryPoint; // 程序入口点的地址(RVA)OEP
// 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; // 保留字段,应为0
DWORD SizeOfImage; // 文件在内存中的大小,按照SectionAlignment对齐后
DWORD SizeOfHeaders; // 所有头和节表(区段头)的总大小,按照FileAlignment对齐后
// DWORD CheckSum; // 校验和
// WORD Subsystem; // 子系统类型
// WORD DllCharacteristics; // DLL的特性
// DWORD SizeOfStackReserve; // 保留的栈大小
// DWORD SizeOfStackCommit; // 初始提交的栈大小
// DWORD SizeOfHeapReserve; // 保留的堆大小
// DWORD SizeOfHeapCommit; // 初始提交的堆大小
// DWORD LoaderFlags; // 加载器标志,应为0
DWORD NumberOfRvaAndSizes; // 数据目录的数量
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
五、区段头(Section Header)
1.作用
区段也成为“节”,区段头也叫节表
注释的不重要,可以忽略
2.图例
位置:可选头开始,区段头多个的,每个的固定大小为40个字节,区段头的数量存放在标准头的NumerOfSections
属性
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节的名称,通常是一个8字节长的字符串,如“.text”、“.data”等
// union {
// DWORD PhysicalAddress; // 物理地址,不常用
// DWORD VirtualSize; // 节在内存中的实际大小
// } Misc;
DWORD VirtualAddress; // 区段在内存中的偏移位值
// DWORD SizeOfRawData; // 区段在文件中对齐后的大小,文件对齐(File Alignment)后的大小
DWORD PointerToRawData; // 区段在文件中的偏移值
// DWORD PointerToRelocations; // 重定位信息表在文件中的位置偏移,通常为0
// DWORD PointerToLinenumbers; // 行号信息在文件中的位置偏移,调试信息相关,通常为0
// WORD NumberOfRelocations; // 重定位项的数量
// WORD NumberOfLinenumbers; // 行号信息的数量
DWORD Characteristics; // 节的属性标志,描述节的特性(可执行、可读、可写等)
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
六、附C++解析源码
C++解析PE文件源码github地址