PE文件结构(一)

时间:2022-01-05 12:28:46

一.     PE文件结构图

PE文件结构(一)

 

 

 

 

 

 

 

 

 

 

 

二.     DOS 头部

 PE文件结构(一)

其中最后一个字段DWORD e_lfanew;的值为PE文件头的相对偏移地址(RVA);

三.PE文件头

结构体的定义:IMAGE_NT_HEADERS:左边地址为相对PE文件头地址的偏移量。

 PE文件结构(一)

DWORD Signature:PE文件头标识,一般其值为:0x00004550;对应字符为PE..;

1.映像文件头(_IMAGE_FILE_HEADER FileHeader):

 PE文件结构(一)

左边地址为相对映像文件头的地址文件头地址的偏移量。

IMAGE_FILE_HEADER的大小为20字节.

第一个成员为WORD类型的Machine,这个通常为0x014C(intel 386系列CPU);

第二个成员为WORD类型的NumberOfSections,即文件中节的数量,这个对应的文件具体的节区数量。

第三个成员为DWORD类型的TimeDateStamp,是一个时间戳,对应于从1970年1月1日开始的秒数。

第四个和第五个成员分别为DWORD类型的PointerToSymbolTable和NumberOfSymbols,和调试相关,这里忽略。

第六个成员为WORD类型的SizeOfOptionalHeader,即后面可选映像头部分的大小,即为IMAGE_OPTIONAL_HEADER的大小,一般情况下32位的为0x00E0,64位的为0x00F0

第七个成员为WORD类型的Characteristics,反映出这个文件的类型以及运行平台的特征。

2.可选映像头(IMAGE_OPTIONAL_HEADER OptionalHeader)

PE文件结构(一)

比较重要的字段:

(1)WORD Magic:标记字,其说明了文件的映像类型:

ROM映像:0x0107

普通可执行的映像:0x010B(一般)

PE(32+)映像:0x020B

(2) 。DWORD AddressOfEntryPoint,这个字段是程序执行的入口RVA地址。

对于DLL文件,这个入口点是在进程初始化和关闭时以及线程创建和毁灭时被调用。大多数的的可执行文件中这个地址并不指向MAIN,WINMAIN或DLLMAIN,而是指向运行时库代码并由其来调用上述函数在DLL中这个域可以被置为0

BaseOfCode:代码段的起始RVA。通常为1000H

BaseOfData:  数据段的起始RVA。在64位的可执行文件中其是不存在的,了解就行

(3) ImageBase为建议的装载地址。对于可执行文件来说一般是

0x00400000。

SectionAlignment为内存中节的对齐大小,即每个区段装入得大小必须是该值得整数倍,一般为0x00001000H(4KB).FileAlignment为PE文件中节的对齐大小

(4) Subsystem: 指定程序的子系统类型.

微软定义如下:

Value

Meaning

 

 

IMAGE_SUBSYSTEM_UNKNOWN

0

Unknown subsystem.

 

 

IMAGE_SUBSYSTEM_NATIVE

1

No subsystem required (device drivers and native system processes).

 

 

IMAGE_SUBSYSTEM_WINDOWS_GUI

2

Windows graphical user interface (GUI) subsystem.

 

 

IMAGE_SUBSYSTEM_WINDOWS_CUI

3

Windows character-mode user interface (CUI) subsystem.

 

 

IMAGE_SUBSYSTEM_OS2_CUI

5

OS/2 CUI subsystem.

 

 

IMAGE_SUBSYSTEM_POSIX_CUI

7

POSIX CUI subsystem.

 

 

IMAGE_SUBSYSTEM_WINDOWS_CE_GUI

9

Windows CE system.

 

 

IMAGE_SUBSYSTEM_EFI_APPLICATION

10

Extensible Firmware Interface (EFI) application.

 

 

IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER

11

EFI driver with boot services.

 

 

IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER

12

EFI driver with run-time services.

 

 

IMAGE_SUBSYSTEM_EFI_ROM

13

EFI ROM image.

 

 

IMAGE_SUBSYSTEM_XBOX

14

Xbox system.

 

 

IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION

16

Boot application.

 

 

☆数据目录表DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];

其指向了输出表,输入表,资源块等数据。其由数个相同的IMAGE_DATA_DIRECTORY结构组成。数据目录成员(16个成员)定义如下:

#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
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of 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
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor
 
 
基本结构如右图:

PE文件结构(一)

 

对应的偏移地址(RVA)相对PE文件头地址的偏移量及数据块的大小:

Offset (PE/PE32+)

Description

96/112(78h)

Export table address and size(输出表的地址与大小)

104/120(80h)

Import table address and size(输入表的地址与大小)

112/128(88h)

Resource table address and size

120/136(90h)

Exception table address and size

128/144(98h)

Certificate table address and size

136/152(A0h)

Base relocation table address and size

144/160(A8h)

Debugging information starting address and size

152/168(B0h)

Architecture-specific data address and size

160/176(B8h)

Global pointer register relative virtual address

168/184(C0h)

Thread local storage (TLS) table address and size

176/192(C8h)

Load configuration table address and size

184/200(D0h)

Bound import table address and size

192/208(D8h)

Import address table address and size

200/216(E0h)

Delay import descriptor address and size

208/224(E8h)

The CLR header address and size

216/232

Reserved(保留)

 

●输出表(IMAGE_DIRECTORY_ENTRY_EXPORT)

输出表的内容即结构体IMAGE_DIRECTORY_ENTRY_EXPORT所指向的地址的内容

 PE文件结构(一)

1.当PE文件被执行的时候,windows装载器将文件装入内存并将输入表中登记的DLL文件一并装入,再根据DLL文件中的函数输出信息对被执行文件的IAT表进行修正。在这些包含输出函数的DLL文件中,输出信息被保存在输出表中,通过输出表,DLL文件向系统提供输出函数的名称,序号和入口地址等信息。以便windows装载器通过这些信息来完成动态链接的过程。一般来说,exe文件不存在输出表,DLL文件大部分都包含输出表,不过这不是绝对的。

2.Base:导出函数序号的起始值,将AddressOfFunctions 字段指向的入口地址表的索引号加上这个起始值就是对应函数的导出 序号。假如Base 字段的值为x,那么入口地址表指定的第1个导出函数的序号就是x;第2个导出函数的序号就是x+1。总之,一个导出函数的导出序号等 于Base 字段的值加上其在入口地址表中的位置索引值。

3.AddressOfNames 和 AddressOfNameOrdinals:均为RVA 值。前者指向函数名字符串地址表。这个地址表是一个双字数组,数组中的每一项指向一个函数名称字符串的RVA。数组的项数等于NumberOfNames 字段的值,所有有名称的导出函数的名称字符串都定义在这个表中;后者指向另一个word 类型的数组(注意不是双字数组)。数组项目与文件名地址表中的项目一一对应,项目值代表函数入口地址表的索引,这样函 数名称与函数入口地址关联起来。(举个例子说,加入函数名称字符串地址表的第n 项指向一个字符串“MyFunction”,那么可以去查找 AddressOfNameOrdinals 指向的数组的第n 项,假如第n 项中存放的值是x,则表示AddressOfFunctions 字段描述的地址表中的第x 项函数入口地址对应的名称就是“MyFunction”)

  • ●输入表(IMAGE_DIRECTORY_ENTRY_IMPORT  )☆

IID(_IMAGE_IMPORT_DESCRIPTOR)结构如下图所示:

 PE文件结构(一)

每个被PE文件隐式地连接进来的DLL文件都有一个IID。在这个结构中没有字段指出该结构数组的项数,但它的最后一个单元式NULL,可以由此计算出该数组的项数

如下图所示:

IMAGE_THUNK_DATA为DWORD大小

当 IMAGE_THUNK_DATA 值的最高位为 1时,表示函数以序号方式输入,这时候低 31位被看作一个函数序号。当

IMAGE_THUNK_DATA 值的最高位为 0时,表示函数以字符串类型的函数名方式输入,这时双字的值是一个 RVA,指向一个 IMAGE_IMPORT_BY_NAME 结构。

上图中最后加的14位NULL标记为最后一项。

IMAGE_IMPORT_BY_NAME结构如右图。

PE文件结构(一)

结构中的 Hint 字段也表示函数的序号,不过这个字段是可选的,有些编译器总是将它设置为 0,Name 字段定义了导入函数的名称字符串,这是一个以 0 为结尾的字符串。

为什么由两个并行的指针数组同时指向 IMAGE_IMPORT_BY_NAME 结构呢?第一个数组(由 OriginalFirstThunk 所指向)是单独的一项,而且不能被改写,我们前边称为 INT。第二个数组(由 FirstThunk 所指向)事实上是由 PE 装载器重写的。

PE 装载器首先搜索 OriginalFirstThunk ,找到之后加载程序迭代搜索数组中的每个指针,找到每个 IMAGE_IMPORT_BY_NAME 结构所指向的输入函数的地址,然后加载器用函数真正入口地址来替代由 FirstThunk 数组中的一个入口,因此我们称为输入地址表(IAT)。

OriginalFirstThunk指向INT表,存储API函数名称。FirstThunk执行IAT表,存储API函数地址,即是PE文件在影射到内存空间后,所用到的地址表,此时OringinalFirstThunK 就无用啦。