PE学习笔记(一) 选择自 rivershan 的 Blog

类别:VC语言 点击:0 评论:0 推荐:

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的

本文地址:http://com.8s8s.com/it/it563.htm