PE学习笔记知识点

PE学习笔记知识点

 
 

PE学习笔记

PE 的意思就是 Portable Executable(可移植的执行体)。PE文件结构的总体层次分布图:

--------------
|DOS MZ Header |
|--------------|
|DOS Stub |
|--------------|
|PE Header |
|--------------|
|Section Table |
|--------------|
|Section 1 |
|--------------|
|Section 2 |
|--------------|
|Section ... |
|--------------|
|Section n |
--------------

一、PE文件格式的概要

1.1、DOS MZ Header:
所有 PE文件(甚至32位的 DLLs)必须以一个简单的 DOS MZ Header 开始。有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随 MZ Header 之后的 DOS Stub。

1.2、DOS Stub:
DOS Stub(存根)实际上是个有效的 MS-DOS .EXE 或者.COM 程序(如果文件格式不对会报错),在不支持 PE文件格式的操作系统中,它将通过简单调用中断21h服务9来显示字符串"This program cannot run in DOS mode"或者根据程序员自己的意图实现完整的 DOS 代码。它的大小一般不能确定。利用链接器(linker)的 /STUB:filename 选项可以替换这个程序。

1.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。

1.4、Section Table:
PE Header 接下来的数组结构 Section Table (节表)。如果PE文件里有5个节,那么此 Section Table 结构数组内就有5个成员,每个成员包含对应节的属性、文件偏移量、虚拟偏移量等。

1.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"的命名只为了便于识别,惟有节的属性设置决定了节的特性和功能。

1.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

2.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.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;

IMAGE_NT_HEADERS 结构成员含义:

1.Signature:一DWORD 类型,值为50h, 45h, 00h, 00h(PE/0/0)。如果IMAGE_NT_HEADERS的Signature域值等于"PE/0/0",那么就是有效的PE文件。Microsoft定义了常量IMAGE_NT_SIGNATURE供我们使用,定义如下:

#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文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。

2.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)里面,其结构定义:

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;

IMAGE_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 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64

2.NumberOfSections:文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改这个值。

3.TimeDateStamp:文件创建日期和时间。其格式是自从1969年12 月31 日4:00 P.M. 之后的总秒数。据我计算,0xFFFFFFFFh是136.19251950152207001522070015221 年。

4.PointerToSymbolTable:COFF 符号表格的偏移位置。此域只对COFF 除错信息有用。

5.NumberOfSymbols:COFF 符号表格中的符号个数。

6.SizeOfOptionalHeade:指示紧随本结构之后的 Optional Header(IMAGE_OPTIONAL_HEADER)结构大小,必须为有效值。

7.Chracteristics:关于本文件信息的标记。一些比较重要的性质如下:

0x0001 文件中没有重定位(relocation)
0x0002 文件是一个可执行程序exe(也就是說不是OBJ 或LIB)
0x2000 文件是dll,不是exe。

一般情况下,如果要遍历节表就得使用 NumberOfSections,其它的几个域作用不大。

四、Optional Header

4.1、RVA 及其相关概念:

RAV 代表相对虚拟地址。RVA是虚拟空间中到参考点的一段距离。RVA就是类似文件偏移量的东西。当然它是相对虚拟空间里的一个地址,而不是文件头部。举例说明,如果PE文件装入虚拟地址(VA)空间的400000h处,且进程从虚址401000h开始执行,我们可以说进程执行起始地址在RVA 1000h。每个RVA都是相对于模块的起始VA的。虛址(VA)0x401000h - 基址(BA)0x400000h = RVA 0x1464h。基址(Base Address)用来描述被映射到内存中的exe或者dll的起始位置。

为什么PE文件格式要用到RVA呢? 这是为了减少PE装载器的负担。因为每个模块都有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了: 它只要将整个模块重定位到新的起始VA。这就象相对路径和绝对路径的概念: RVA类似相对路径,VA就象绝对路径。

在PE文件中大多数地址多是RVAs 而 RVAs只有当PE文件被PE装载器装入内存后才有意义。如果直接将文件映射到内存而不是通过PE装载器载入,则不能直接使用那些RVAs。必须先将那些RVAs转换成文件偏移量。

4.2、Optional Header 结构是 IMAGE_NT_HEADERS 中的最后成员。包含了PE文件的逻辑分布信息。该结构共有31个域,一些是很关键,另一些不太常用。其结构定义:

typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags; DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;

IMAGE_OPTIONAL_HEADER 结构成员含义:

1.Magic:用来定义 image 的状态

0x0107(IMAGE_ROM_OPTIONAL_HDR_MAGIC):一个 ROM image
0x010B(IMAGE_NT_OPTIONAL_HDR_MAGIC): 一个正常的(一般的)EXE image。大部份PE 文件都含此值。

2.MajorLinkerVersion、MinorLinkerVersion:产生此PE文件的链接器的版本。以十进制而非十六进制表示。例如2.23 版。

3.SizeOfCode:所有code section 的总和大校大部分程序只有一个 code section,所以此域通常就是 .text section 的大校
4.SizeOfInitializedData:所有包含初始化内容的 sections(但不包括 code section)的总和大校似乎不包括 initialized data sections 在内。

5.SizeOfUninitializedData:所有需要PE装载器将内存地址空间赋予它但是却不占用硬盘空间的所有 sections 的大小总和。这些 sections 在程序启动时并不需要特别内容,所以导致 Uninitialized Data 这种叫法。为初始化的内容通常放在 .bss section 中。

6.AddressOfEntryPoint:这是PE文件开始执行的位置。这是一个RVA,通常会落在 .text section.此域适用于 exe 或 dll。

7.BaseOfCode:一个RVA,表示程序中的 code section 从何开始。code section 通常在 data section 之前,在PE 表头之后。微软链接器所产生的exes 中,此值通常为0x1000。Borland 的TLINK32则通常指定此值为0x10000。因为预设情况下TLINK时以64k为对齐粒度的,而MS用的是4k。

8.BaseOfData:一个RVA,表示程序中的 data section 从何开始。data section 一般位于code section 和 PE 表头之后。

9.ImageBase:PE文件的优先装载地址(Base Address)。比如,如果该值是400000h,PE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。

10.SectionAlignment:内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。

11.FileAlignment:文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,则下一节必定位于偏移量400h,即使偏移量512和1024之间还有很多空间没被使用或定义。预设值就是0x200h。

12.MajorOperatingSystemVersion/MinorOperatingSystemVersion:使用此可执行程序的操作系统的最小版本。WIN32程序的这两个域通常指定为1.0。

13.MajorSubsystemVersion/MinorSubsystemVersion:WIN32子系统版本。若PE文件是专门为WIN32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感。

14.MajorImageVersion/MinorImageVersion:使用者自定义的域,允许你拥有不同版本的exe或dll。可以利用链接器的 /VERSION 选项设定其值。例如:LINK /VERSION:2.0 myobj.obj。

15.Reserved1:似乎总是0。

16.SizeOfImage:内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大校也就是从image base 开始,直到最后一个 section为止。最后一个section 的尾端必需是SectionAlignment 的倍数。

17.SizeOfHeaders:所有头 + 节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量。

18.CheckSum:此程序的一个CRC 校验和。PE中此域通常被忽略并被设为0。然而,所有的driver DLLs、所有在开机时载入的DLLs、以及server DLLs 都必须有一个合法的 CheckSum。其演算法可以在IMAGEHLP.DLL中获得。IMAGEHLP.DLL 的代码可以在WIN32 SDK中找到。

19.Subsystem:用来识别PE文件属于哪个子系统。对于大多数Win32程序,只有两类值: Windows GUI 和 Windows CUI (控制台)。WINNT.h中定义如下:

#define IMAGE_SUBSYSTEM_UNKNOWN 0 Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE 1 不需要子系統(例如驱动程序)
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 在Windows GUI 子系统中运行
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 在Windows 字符模式子系统中运行(也就是console 应用程序)
#define IMAGE_SUBSYSTEM_OS2_CUI 5 在OS/2 字符模式子系统中运行(也就是OS/2 1.x 应用程序)
#define IMAGE_SUBSYSTEM_POSIX_CUI 7 在Posix 字符模式子系统中运行
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 一个Win9x驱动
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 在Win CE 子系统中运行

20.DllCharacteristics:一组标志位,用来指出dll的初始化函数(例如 DllMain)在什么环境下被调用。这个值总是0,但是操作系统会在四种情况发生式调用dll的初始化函数。此值的四个值的意义如下:

0x0001:当DLL被载入一个进程的地址空间时
0x0002:当一个线程结束时
0x0004:但一个线程开始时
0x0008:当DLL退出时
0x2000:一个WDM驱动

21.SizeOfStackReserve:线程初始堆栈的保留大校然而并不是所有的这些内存都被系统指定。此值预设为0x100000(1MB)。如果你的程序中调用CreateThread 并指定其堆栈大小为0,获得的线程就有一个与此值相同大小的堆栈。

22.SizeOfStackCommit:一开始就被指定给执行线程初始堆栈的内存数量。微软的链接器预设此值为0x1000(一个page),Borland 的TLINK32把它设为0x2000(两个page)。

23.SizeOfHeapReserve:保留给最初的进程堆(process heap)的虚拟内存数量。这个堆的句柄可以利用GetProcessHeap 获得。并不是所有的这些内存都被指定。

24.SizeOfHeapCommit:一开始就被指定给进程堆(process heap)的内存数量。此值预设为0x1000个字节(位元组)。

25.LoaderFlags:Debug用。可能作用:
a.在开始这个进程之前引发一个中断?
b.在进程被载入之后引发一个除错器执行?

26.NumberOfRvaAndSizes:在DataDirectory(下一个域)数组的成员结构个数。目前的工具总是把此值设为16。

27.DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:一个IMAGE_DATA_DIRECTORY 结构数组。每个结构给出一个重要数据结构的RVA。数组的第一个元素代表 Exported Function Table(如果有的话)的地址和大小,第二个元素代表Imported Function Table 的地址和大小,依此类推。下面是其顺序的完整列表:

// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // Description String
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // Machine Value (MIPS GP)
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table

96/112 8 Export Table Export Table address and size.
104/120 8 Import Table Import Table address and size
112/128 8 Resource Table Resource Table address and size.
120/136 8 Exception Table Exception Table address and size.
128/144 8 Certificate Table Attribute Certificate Table address and size.
136/152 8 Base Relocation Table Base Relocation Table address and size.
144/160 8 Debug Debug data starting address and size.
152/168 8 Architecture Architecture-specific data address and size.
160/176 8 Global Ptr Relative virtual address of the value to be stored in the global pointer register. Size member of this structure must be set to 0.
168/184 8 TLS Table Thread Local Storage (TLS) Table address and size.
176/192 8 Load Config Table Load Configuration Table address and size.
184/200 8 Bound Import Bound Import Table address and size.
192/208 8 IAT Import Address Table address and size.
200/216 8 Delay Import Descriptor Address and size of the Delay Import Descriptor.
208/224 8 COM+ Runtime Header COM+ Runtime Header address and size
216/232 8 Reserved

rivershan原创于2003.1.18

呵呵,转的rivershan的