一:PE整体结构
PE
的意思就是
Portable Executable
(可移植的执行体)。
PE
文件的整体大概结构描述:
struct pe
{
DOS MZ header
:
所有
PE
文件
(
甚至
32
位的
DLLs)
必须以一个简单的
DOS MZ header
开始。有了它,一旦程序在
DOS
下执行,
DOS
就能识别出这是有效的执行体,然后运行紧随
MZ header
之后的
DOS stub
。
DOS stub
:
DOS stub
实际上是个有效的
EXE
,在不支持
PE
文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串
"This program requires Windows"
或者程序员可根据自己的意图实现完整的
DOS
代码。大多数情况下它是由汇编器
/
编译器自动生成。通常,它简单调用中断
21h
服务
9
来显示字符串
"This program cannot run in DOS mode"
。
PE header
:
PE header
是
PE
相关结构
IMAGE_NT_HEADERS
的简称,其中包含了许多
PE
装载器用到的重要域。执行体在支持
PE
文件结构的操作系统中执行时,
PE
装载器将从
DOS MZ header
中找到
PE header
的起始偏移量(跳过了
DOS stub
直接定位到真正的文件头
PE header
)。
Section table
:
节表。
每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果
PE
文件里有
5
个节,那么此结构数组内就有
5
个成员。
Section 1
:
PE
文件的真正内容划分成块,称之为
sections
(节)。每节是一块拥有共同属性的数据,比如代码
/
数据、读
/
写等。当
PE
装载器映射节内容时,它会检查相关节属性并置对应内存块为指定属性。
…
Section n
}
装载一个
PE
文件的主要步骤
:
1
.当
PE
文件被执行,
PE
装载器检查
DOS MZ header
里的
PE header
偏移量。如果找到,则跳转到
PE header
。
2
.
PE
装载器检查
PE header
的有效性。如果有效,就跳转到
PE header
的尾部。
3
.紧跟
PE header
的是节表。
PE
装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
4
.
PE
文件映射入内存后,
PE
装载器将处理
PE
文件中类似
import table
(引入表)逻辑部分。
二:DOS MZ header解析
DOS MZ header
结构如下(
asm
描述,来自汇编头文件
windows.ini
):
IMAGE_DOS_HEADER STRUCT
e_magic WORD ?
//
魔术数字
e_cblp WORD ?
//
文件最后页的字节数
e_cp WORD ?
//
文件页数
e_crlc WORD ?
//
重定义元素个数
e_cparhdr WORD ?
//
头部尺寸,以段落为单位
e_minalloc WORD ?
//
所需的最小附加段
e_maxalloc WORD ?
//
所需的最大附加段
e_ss WORD ?
//
初始的
SS
值(相对偏移量)
e_sp WORD ?
//
初始的
SP
值
e_csum WORD ?
//
校验和
e_ip WORD ?
//
初始的
IP
值
e_cs WORD ?
//
初始的
CS
值(相对偏移量)
e_lfarlc WORD ?
//
重分配表文件地址
e_ovno WORD ?
//
覆盖号
e_res WORD 4 dup(?)
//
保留字
e_oemid WORD ?
// OEM
标识符(相对
e_oeminfo
)
e_oeminfo WORD ?
// OEM
信息
e_res2 WORD 10 dup(?)
//
保留字
e_lfanew DWORD ?
//
新
exe
头部的文件地址
IMAGE_DOS_HEADER ENDS //sizeof(IMAGE_DOS_HEADER)==0x40
IMAGE_DOS_HEADER
结构体大小为
0x40 bytes
。
其中e_magic是标记,它应该等于IMAGE_DOS_SIGNATURE(字符为MZ),从而指明当前是DOS头部信息。
windows.ini
中定义的同类各种系统标记如下:
IMAGE_DOS_SIGNATURE equ 5A4Dh
IMAGE_OS2_SIGNATURE equ 454Eh
IMAGE_OS2_SIGNATURE_LE equ 454Ch
IMAGE_VXD_SIGNATURE equ 454Ch
IMAGE_NT_SIGNATURE equ 00004550h
其中的
e_lfanew
是指向
PE header
的文件偏移,单位为
byte
。
三:DOS stub解析:
这个部分没有找到详细资料。基本上我们也不需要知道,因为这部分是为了在
MS-DOS
下运行本
exe
文件时显示“
This program cannot be run in DOS mode.
”,也就是说这是一段
MS-DOS
程序,似乎可以称为“
实模式残余程序
”。
此部分的起始偏移应该是由
IMAGE_DOS_HEADER .e_lfarlc
指出的,单位为
byte
。
四:PE header解析
PE header
的
正式命名是
IMAGE_NT_HEADERS
,结构如下
IMAGE_NT_HEADERS STRUCT
Signature dd ? // PE 标记,值为 50h, 45h, 00h, 00h ( PE/0/0 )。
Signature dd ? // PE 标记,值为 50h, 45h, 00h, 00h ( PE/0/0 )。
FileHeader IMAGE_FILE_HEADER <> //
包含了关于
PE
文件物理分布的一般信息
OptionalHeader IMAGE_OPTIONAL_HEADER32 <> // 包含了关于 PE 文件逻辑分布的信息
IMAGE_NT_HEADERS ENDS
OptionalHeader IMAGE_OPTIONAL_HEADER32 <> // 包含了关于 PE 文件逻辑分布的信息
IMAGE_NT_HEADERS ENDS
IMAGE_FILE_HEADER STRUCT
Machine WORD ?//
该文件运行所要求的
CPU
。对于
Intel
平台,该值是
IMAGE_FILE_MACHINE_I386 (14Ch)
。
NumberOfSections WORD ?//
文件的节数目
TimeDateStamp DWORD ?//
文件创建日期和时间,从
1970.1.1 00:00:00
以来的秒数
PointerToSymbolTable DWORD ?//
用于调试
NumberOfSymbols DWORD ?//
用于调试
SizeOfOptionalHeader WORD ?//
指示紧随本结构之后的
OptionalHeader
结构大小,
必须为有效值
Characteristics WORD ?//
关于文件信息的标记,比如文件是
exe
还是
dll
IMAGE_FILE_HEADER ENDS
Characteristics
详解如下:
Bit 0 (IMAGE_FILE_RELOCS_STRIPPED)
:置
1
表示文件中没有重定向信息。每个段都
有它们自己的重定向信息。这个标志在可执行文件中没有使用,在可执行文件中是用一个叫做
基址重定向目录表来表示重定向信息的,这将在下面介绍。
Bit 1 (IMAGE_FILE_EXECUTABLE_IMAGE) :置 1 表示该文件是可执行文件(也就是说
不是一个目标文件或库文件)。
Bit 2 (IMAGE_FILE_LINE_NUMS_STRIPPED) :置 1 表示没有行数信息;在可执行文件
中没有使用。
Bit 3 (IMAGE_FILE_LOCAL_SYMS_STRIPPED) :置 1 表示没有局部符号信息;在可执行
文件中没有使用。
Bit 4 (IMAGE_FILE_AGGRESIVE_WS_TRIM) :
Bit 7 (IMAGE_FILE_BYTES_REVERSED_LO)
Bit 8 (IMAGE_FILE_32BIT_MACHINE) :表示希望机器为 32 位机。这个值永远为 1 。
Bit 9 (IMAGE_FILE_DEBUG_STRIPPED) :表示没有调试信息,在可执行文件中没有使
用。
Bit 10 (IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) :置 1 表示该程序不能运行于可移
动介质中(如软驱或 CD-ROM )。在这种情况下, OS 必须把文件拷贝到交换文件中执行。
Bit 11 (IMAGE_FILE_NET_RUN_FROM_SWAP) :置 1 表示程序不能在网上运行。在这种
情况下, OS 必须把文件拷贝到交换文件中执行。
Bit 12 (IMAGE_FILE_SYSTEM) :置 1 表示文件是一个系统文件例如驱动程序。在可执
行文件中没有使用。
Bit 13 (IMAGE_FILE_DLL) :置 1 表示文件是一个动态链接库( DLL )。
Bit 14 (IMAGE_FILE_UP_SYSTEM_ONLY) :表示文件被设计成不能运行于多处理器系
统中。
Bit 15 (IMAGE_FILE_BYTES_REVERSED_HI) :表示文件的字节顺序如果不是机器所期
望的,那么在读出之前要进行交换。在可执行文件中它们是不可信的(操作系统期望按正确的
字节顺序执行程序)。
有它们自己的重定向信息。这个标志在可执行文件中没有使用,在可执行文件中是用一个叫做
基址重定向目录表来表示重定向信息的,这将在下面介绍。
Bit 1 (IMAGE_FILE_EXECUTABLE_IMAGE) :置 1 表示该文件是可执行文件(也就是说
不是一个目标文件或库文件)。
Bit 2 (IMAGE_FILE_LINE_NUMS_STRIPPED) :置 1 表示没有行数信息;在可执行文件
中没有使用。
Bit 3 (IMAGE_FILE_LOCAL_SYMS_STRIPPED) :置 1 表示没有局部符号信息;在可执行
文件中没有使用。
Bit 4 (IMAGE_FILE_AGGRESIVE_WS_TRIM) :
Bit 7 (IMAGE_FILE_BYTES_REVERSED_LO)
Bit 8 (IMAGE_FILE_32BIT_MACHINE) :表示希望机器为 32 位机。这个值永远为 1 。
Bit 9 (IMAGE_FILE_DEBUG_STRIPPED) :表示没有调试信息,在可执行文件中没有使
用。
Bit 10 (IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP) :置 1 表示该程序不能运行于可移
动介质中(如软驱或 CD-ROM )。在这种情况下, OS 必须把文件拷贝到交换文件中执行。
Bit 11 (IMAGE_FILE_NET_RUN_FROM_SWAP) :置 1 表示程序不能在网上运行。在这种
情况下, OS 必须把文件拷贝到交换文件中执行。
Bit 12 (IMAGE_FILE_SYSTEM) :置 1 表示文件是一个系统文件例如驱动程序。在可执
行文件中没有使用。
Bit 13 (IMAGE_FILE_DLL) :置 1 表示文件是一个动态链接库( DLL )。
Bit 14 (IMAGE_FILE_UP_SYSTEM_ONLY) :表示文件被设计成不能运行于多处理器系
统中。
Bit 15 (IMAGE_FILE_BYTES_REVERSED_HI) :表示文件的字节顺序如果不是机器所期
望的,那么在读出之前要进行交换。在可执行文件中它们是不可信的(操作系统期望按正确的
字节顺序执行程序)。
IMAGE_OPTIONAL_HEADER32 STRUCT
Magic WORD ?//
标记
MajorLinkerVersion BYTE ?//
链接器的版本号
MinorLinkerVersion BYTE ?//
链接器的版本号
SizeOfCode DWORD ?//
可执行代码的长度
SizeOfInitializedData DWORD ?//
初始化数据的长度(数据段)
SizeOfUninitializedData DWORD ?//
未初始化数据的长度(
bss
段)
AddressOfEntryPoint DWORD ? //
代码的入口
RVA
地址,程序从这儿开始执行。
PE
装载器准备运行的
PE
文件的第一个指令的
RVA
。若您要改变整个执行的流程,可以将该值指定到新的
RVA
,这样新
RVA
处的指令首先被执行。
BaseOfCode DWORD ?//
可执行代码起始位置
BaseOfData DWORD ?//
初始化数据起始位置
ImageBase DWORD ?//
载入程序首选的
RVA
地址。
PE
文件的优先装载地址。比如,如果该值是
400000h
,
PE
装载器将尝试把文件装到虚拟地址空间的
400000h
处。字眼
"
优先
"
表示若该地址区域已被其他模块占用,那
PE
装载器会选用其他空闲地址。
SectionAlignment DWORD ?//
段加载后在内存中的对齐方式。内存中节对齐的粒度。例如,如果该值是
4096 (1000h)
,那么每节的起始地址必须是
4096
的倍数。若第一节从
401000h
开始且大小是
10
个字节,则下一节必定从
402000h
开始,即使
401000h
和
402000h
之间还有很多空间没被使用。
FileAlignment DWORD ?//
段在文件中的对齐方式。文件中节对齐的粒度。例如,如果该值是
(200h),
,那么每节的起始地址必须是
512
的倍数。若第一节从文件偏移量
200h
开始且大小是
10
个字节,则下一节必定位于偏移量
400h:
即使偏移量
512
和
1024
之间还有很多空间没被使用
/
定义。
MajorOperatingSystemVersion WORD ?//
操作系统版本
MinorOperatingSystemVersion WORD ? //
操作系统版本
MajorImageVersion WORD ?//
程序版本
MinorImageVersion WORD ? //
程序版本
MajorSubsystemVersion WORD ?//
子系统版本号。
win32
子系统版本。若
PE
文件是专门为
Win32
设计的,该子系统版本必定是
4.0
否则对话框不会有
3
维立体感。
MinorSubsystemVersion WORD ?//
子系统版本号
Win32VersionValue DWORD ?//
一般为
0
SizeOfImage DWORD ?//
程序调入后占用内存大小(字节),等于所有段的长度之和。所有头和节经过节对齐处理后的大小。
SizeOfHeaders DWORD ?//
所有文件头的长度之和
(
从文件开始到第一个段之间的大小
)
。所有头
+
节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为
PE
文件第一节的文件偏移量
CheckSum DWORD ?//
校验和。它仅用在驱动程序中,在可执行文件中可能为
0
。它的计算方法
Microsoft
不公开,在
imagehelp.dll
中的
CheckSumMappedFile()
函数可以计算它
Subsystem WORD ?// NT
子系统,可能是以下的值:
IMAGE_SUBSYSTEM_NATIVE (1) 不需要子系统。用在驱动程序中。
IMAGE_SUBSYSTEM_WINDOWS_GUI(2) WIN32 graphical 程序(它可用 AllocConsole() 来打开一个控制台,但是不能在 一开始自动得到)。
IMAGE_SUBSYSTEM_WINDOWS_CUI(3) WIN32 console 程序(它可以一开始自动建立)。
IMAGE_SUBSYSTEM_OS2_CUI(5) OS/2 console 程序(因为程序是 OS/2 格式,所以它很少用在 PE )。
IMAGE_SUBSYSTEM_POSIX_CUI(7) POSIX console 程序。
Windows95 程序总是用 WIN32 子系统,所以只有 2 和 3 是合法的值
IMAGE_SUBSYSTEM_NATIVE (1) 不需要子系统。用在驱动程序中。
IMAGE_SUBSYSTEM_WINDOWS_GUI(2) WIN32 graphical 程序(它可用 AllocConsole() 来打开一个控制台,但是不能在 一开始自动得到)。
IMAGE_SUBSYSTEM_WINDOWS_CUI(3) WIN32 console 程序(它可以一开始自动建立)。
IMAGE_SUBSYSTEM_OS2_CUI(5) OS/2 console 程序(因为程序是 OS/2 格式,所以它很少用在 PE )。
IMAGE_SUBSYSTEM_POSIX_CUI(7) POSIX console 程序。
Windows95 程序总是用 WIN32 子系统,所以只有 2 和 3 是合法的值
DllCharacteristics WORD ?// Dll
状态
SizeOfStackRese