PE学习笔记
PE 的意思就是 Portable Executable(可移植的执行体)。
一、PE文件结构的总体层次分布图
--------------
|DOS MZ Header |
|--------------|
|DOS Stub |
|--------------|
|PE Header |
|--------------|
|Section Table |
|--------------|
|Section 1 |
|--------------|
|Section 2 |
|--------------|
|Section ... |
|--------------|
|Section n |
--------------
二、PE文件格式的概要
1、 DOS MZ Header:
所有 PE文件(甚至32位的 DLLs)必须以一个简单的 DOS MZ Header 开始。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ Header 之后的 DOS Stub。
2、DOS Stub:
DOS Stub(存根)实际上是个有效的 EXE,在不支持 PE文件格式的操作系统中,它将通过简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"或者根据程序员自己的意图实现完整的 DOS代码。大多数情况下它是由汇编器/编译器自动生成。它的大小一般不能确定。
3、PE Header:
紧接着 DOS Stub 的是 PE Header。PE Header 是PE相关结构 IMAGE_NT_HEADERS 的简称,其中包含了许多PE装载器用到的重要域。执行体在支持PE文件结构的操作系统中执行时,PE装载器将从 DOS MZ Header (IMAGE_DOS_HEADER)中找到 PE Header 的起始偏移量。因而跳过了DOS Stub 直接定位到真正的文件头PE Header。
4、Section Table:
PE Header 接下来的数组结构 Section Table (节表)。如果PE文件里有5个节,那么此 Section Table 结构数组内就有5个成员,每个成员包含对应节的属性、文件偏移量、虚拟偏移量等。
5、Sections:
PE文件的真正内容被划分成块,称之为Section(节)。每个标准节的名字均以圆点开头。Sections 是以其起始位址来排列,而不是以其字母次序来排列。下面是常见的节名及作用:
节名 作用
.arch 最初的构建信息(Alpha Architecture Information)
.bss 未经初始化的数据
.CRT C运行期只读数据
.data 已经初始化的数据
.debug 调试信息
.didata 延迟输入文件名表
.edata 导出文件名表
.idata 导入文件名表
.pdata 异常信息(Exception Information)
.rdata 只读的初始化数据
.reloc 重定位表信息
.rsrc 资源
.text .exe或.dll文件的可执行代码
.tls 线程的本地存储器
.xdata 异常处理表
节的划分是基于各组数据的共同属性,而不是逻辑概念。每节是一块拥有共同属性的数据,比如代码/数据、读/写等。如果PE文件中的数据/代码拥有相同属性,它们就能被归入同一节中。节名称仅仅是个区别不同节的符号而已,类似"data", "code"的命名只为了便于识别,惟有节的属性设置决定了节的特性和功能。
6、装载一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 和 PE Header
1、DOS MZ Header 定义成结构 IMAGE_DOS_HEADER(64字节) 。结构定义如下:
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE Header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of Header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe Header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向 PE Header 的 RVA。e_magic 包含字符串"MZ"。
2、PE Header 实际就是一个 IMAGE_NT_HEADERS 结构。定义如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER OptionalHeader;
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;
成员含义:
1.Signature:一DWORD 类型,值为50h, 45h, 00h, 00h(PE\0\0)。如果IMAGE_NT_HEADERS的Signature域值等于"PE\0\0",那么就是有效的PE文件。Microsoft定义了常量IMAGE_NT_SIGNATURE供我们使用,定义如下(来源于WINNT.h):
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
#define IMAGE_OS2_SIGNATURE 0x454E // NE
#define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
#define IMAGE_VXD_SIGNATURE 0x454C // LE
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
2.FileHeader:该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。
3.OptionalHeader:该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。
3、检验PE文件的有效性步骤总结如下:
1.首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ Header 有效。
2.一旦证明文件的 DOS Header 有效后,就可用e_lfanew来定位 PE Header 了。
3.比较 PE Header 的第一个字的值是否等于 IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。
下面将通过一个VC++ 6.0的例子来检验PE文件的有效性:
我们首先调用打开文件通用对话框(GetOpenFileName),选择打开一个文件并映射到内存(CreateFile,CreateFileMapping、MapViewOfFile等),获得目标文件大小(m_buffer = new unsigned char[m_size];)。然后获取目标文件的头2个字节(((unsigned short*)m_buffer)[0];),看是否为"MZ"。如果相同,获得目标文件PE header的位置(((unsigned int*)(2*m_buffer + 0x3c));), 与0x00004550(PE)比较。由此验证PE有效性。
三、File Header(文件头)
File Header(IMAGE_FILE_HEADER)包含在PE Header(IMAGE_NT_HEADERS)里面,其结构定义(来源于MSDN):
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
File Header域名含义:
1.Machine:该文件运行所要求的CPU。对于Intel平台,该值是IMAGE_FILE_MACHINE_I386 (14Ch)。我们尝试了LUEVELSMEYER的pe.txt声明的14Dh和14Eh,但Windows不能正确执行。
一些CPU识别码的定义:
Intel I386 0x14C
Intel i860 0x14D
MIPS R300 0x162
MIPS R400 0x166
DEC Alpha AXP 0x184
Power PC 0x1F0(little endian)
Motorola 68000 0x268
PA RISC 0x290(Precision Architecture)
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16