3. PE文件头
由 'PE' 00 00 4 bytes + PE文件头 20 bytes 组成
typedef struct _IMAGE_NT_HEADERS
{
DWORD Signature; //这个必需为50 45 00 (PE 00 00 )4字节 PE\0\0
//这是PE文件头的标记, 我们可以此识别给定文件是否为有效PE文件。
// 如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件
// 实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。 // IMAGE_DOS_SIGNATURE equ 5A4Dh MZ
// IMAGE_OS2_SIGNATURE equ 454Eh
// IMAGE_OS2_SIGNATURE_LE equ 454Ch
// IMAGE_VXD_SIGNATURE equ 454Ch
// IMAGE_NT_SIGNATURE equ 4550h
// 该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。
IMAGE_FILE_HEADER FileHeader; // 该结构包含了PE文件物理分布的信息,如节数目,PE文件的执行机器等
IMAGE_OPTIONAL_HEADER32 OptionalHeader; 可选头结构
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER
{
WORD Machine; //
INTEL 80386以上处理器必需为 4C 01 二字节
WORD NumberOfSections;
// 段的个数 // P: 1. 是指哪里的 Sections ? 2. 最多可以有多少个 Sections?
DWORD TimeDateStamp; // 文件的创建日期和时间
DWORD PointerToSymbolTable; // 用于调试
DWORD NumberOfSymbols; // 用于调试
WORD SizeOfOptionalHeader; //
_IMAGE_OPTIONAL_HEADER有224字节所以为 E0 00
WORD Characteristics; // 关于文件信息的标记,比如文件是EXE还是DLL? EXE可执行文件为02 00
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
注:
(1). Machine 字段表示这个PE文件被Build的目录机器种类,eg DEC(R) Alpha、MIPS R4000、Inter(R) x86
已知的合法值:
IMAGE_FILE_MACHINE_I386 (0x14c)
Intel 80386 处理器或更高
0x014d
Intel 80386 处理器或更高
0x014e
Intel 80386 处理器或更高
0x0160
R3000 (MIPS⑧)处理器,大尾⑨
IMAGE_FILE_MACHINE_R3000 (0x162)
R3000 (MIPS)处理器,小尾
IMAGE_FILE_MACHINE_R4000 (0x166)
R4000 (MIPS)处理器,小尾
IMAGE_FILE_MACHINE_R10000 (0x168)
R10000 (MIPS)处理器,小尾
IMAGE_FILE_MACHINE_ALPHA (0x184)
DEC Alpha AXP⑩处理器
IMAGE_FILE_MACHINE_POWERPC (0x1F0)
IBM Power PC,小尾
(2).
NumberOfSections 字段表示 这个PE文件有多少个段(多少个段头部和多少个段实体,每个段头部和段实体都在文件中连续排列, 所以要决定段头部和段实体在哪里结束的话,段的数目是必需的)
(3). 判断一个文件是否是合法的PE文件
a. 比较 MZ 头的 e_magic 值是否为 IMAGE_DOS_SIGNATURE以验证是否是有效的DOS header
b. 通过 MZ 头的 e_lfanew 所指向的偏移获取 PE 头
c. 比较 PE 头的 Signature 值是否为 IMAGE_NT_SIGNATURE
如果满足以上条件,则可以认为是一个合法的PE文件
(4). TimeDateStamp(32位),用来给出文件建立的时间。
同一个文件的不同版本必须唯一,时间戳的格式没有明文规定,但似乎是按照UTC?
(
时间“从1970年1月1日00:00:00算起的秒数值”----也就是大多数C语言编译器给time_t标志使用的格式。)
P: 这个时间戳是用来绑定各个输入目录的?
警告:有一些链接器往往将时间戳设为荒唐的值,而不是如前所述的time_t格式的链接时间。
(5).
PointerToSymbolTable 符号表指针
NumberOfSymbols 符号数
用于调试,
P: 总是为 0x00, 即使在 VC IDE 的 Project Setting 中设置了生成调试信息,也是为 0x00 , 为什么?
P: 1. NumberOfSections 表示 段的个数,那么 WIN32.PE 最多有多少段?
(6). SizeOfOptionalHeader
(16位)只是“IMAGE_OPTIONAL_HEADER(可选头)”项的大小,
P: 只是用于验证PE文件结构的正确性?? 除了
E0 00 外是否有其它值?
(7). Characteristics (16位) 由许多标志集合组成
位1 IMAGE_FILE_EXECUTABLE_IMAGE(可执行映象文件) 表示如果文件是一个可执行文件,
也即不是目标文件或者库文件时,置1。 如果链接器尝试创建一个可执行文件,却因为一些原因失败了,并保存映像以便下次例如增量链接时使用,此时此标志位也可能置1。
位2 IMAGE_FILE_LINE_NUMS_STRIPPED(行数被剥离文件) 表示如果行数信息被剥除,此位置1;此位也不用于可执行文件。P:可执行文件不也有行数信息(假如PE编译时加上调试信息的话)?
位3 IMAGE_FILE_LOCAL_SYMS_STRIPPED(本地符号被剥离文件) 表示如果文件中没有关于本地符号的信息时,此位置1(此位也不用于可执行文件)。P: 同问
位4 IMAGE_FILE_AGGRESIVE_WS_TRIM(强行工作集修剪文件) 表示如果操作系统被假定为:通过将正在运行的进程(它所使用的内存数量)强行的页清除来修剪它的工作集时,此位置1。如果一进程是大部分时间处于等待,且一天中仅被唤醒一次的演示性的应用程序之类时,此位也应该被置1。P: 没看懂这句的意思。
P: 第 5,6 位跑哪去了??
位7 IMAGE_FILE_BYTES_REVERSED_LO(低字节变换文件)和 位15IMAGE_FILE_BYTES_REVERSED_HI(高字节变换文件) 表示如果一文件的字节序不是机器所预期的形式,因此它在读入前必须调换字节时,此位置1。这样做对可执行文件是不可靠的(操作系统期望可执行文件都已经被正确地按字节排整齐了)。
位8 IMAGE_FILE_32BIT_MACHINE(32位机器文件) 表示如果使用的机器被期望为32位的机器时,此位置1。现在的应用程序总将此位置1;NT5系统可能工作不同。
位9 IMAGE_FILE_DEBUG_STRIPPED(调试信息被剥离文件) 表示如果文件中没有调试信息,此位置1。此位可执行文件不用。P: (PE文件不是可以包含调试信息?)
按照其它信息([6])(这里指的是参考书目中的第[6]种----译者注),此位被称作“恒定”,并且当一个映象文件只有在被装入优先的装入地址才能运行(亦即:此文件不可重定位)时,此位置1。
位10 IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP(移动介质文件从交换文件运行) 表示如果一个应用程序不可以从可移动的介质,如软盘或CD-ROM上运行时,此位置1。在这种情况下,建议操作系统将文件复制到交换文件并从那里执行。P: 不是很懂这里的意思,'软盘或CD-ROM上运行时,此位置1。'
位11 IMAGE_FILE_NET_RUN_FROM_SWAP(网络文件从交换文件运行) 表示如果一个应用程序不可以从网络上运行时,此位置1。在这种情况下,建议操作系统将文件复制到交换文件并从那里执行。
位12 IMAGE_FILE_SYSTEM(系统文件) 表示如果文件是一个象驱动程序那样的系统文件,此位置1。此位可执行文件不用;我所见过的所有NT系统的驱动程序也不用。
位13 IMAGE_FILE_DLL(DLL文件) 表示如果文件是一个DLL文件时,此位置1。
位14 IMAGE_FILE_UP_SYSTEM_ONLY(仅但处理器系统的文件) 表示如果文件不设计运行在多处理器系统上(也就是说,因为此文件严格地依赖单一处理器的一些方式工作,所以它会发生冲突)时,此位置1。
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
//
WORD Magic;
// 总是 0B 01 P: 可选头的标志?意义是?
BYTE MajorLinkerVersion; // 0 链接器的主版本号 // P:不同的链接器设置不同,因此不可靠?
BYTE MinorLinkerVersion; // 0 链接器的小版本号
DWORD SizeOfCode; // 0 // 可执行的代码的大小
P: 这里的可执行代码是指整个PE文件的可执行代码段?
DWORD SizeOfInitializedData; // 0 // 已初始化数据的大小 ,数据段
DWORD SizeOfUninitializedData; // 0 // 未初始化数据的大小, BSS段
P: 已初始化的数据跟未初始化的数据有什么区别?它们都在数据段?
DWORD AddressOfEntryPoint; // // 代码的入口点地址(是一个偏移量,RVA), 如 LibMain, WinMain....
DWORD BaseOfCode; // 0 // 可执行代码的偏移量, 代码基址
P: 不是很懂这里的意思,跟 AddressOfEntryPoint 的区别是?
DWORD BaseOfData; // 0 // 已初始化数据偏称量,数据基址 P: 同问
//
// NT additional fields.
//
DWORD ImageBase; //载入程序的RVA地址,LOADER可以改变 00 00 40 00 == 0x400000
DWORD SectionAlignment; //段加载后在内存的对齐方式 00 10 00 00
DWORD FileAlignment; //段在文件中的对齐方式 00 20 00 00
WORD MajorOperatingSystemVersion; // 0
WORD MinorOperatingSystemVersion; // 0
WORD MajorImageVersion; // 0
WORD MinorImageVersion; // 0
WORD MajorSubsystemVersion; //子系统版本号如果不是4.0 对话框不能显示3D风格 04 00
WORD MinorSubsystemVersion; // 0
DWORD Win32VersionValue; // 0
DWORD SizeOfImage; // 映射到内存的代码长度
DWORD SizeOfHeaders; //所有文件头长度之和 00 04 00 00 (对齐后的, 包所在节?)
DWORD CheckSum; // 校验和,用 0?
WORD Subsystem; // 子系统 02 00 或03 00 ?
WORD DllCharacteristics; // 00
DWORD SizeOfStackReserve; // 00
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags; // 00
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
// 16个结构,第二个结构(导入表)
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;