PE文件结构
文章目录
- PE文件结构
- 前置知识
- 整体结构
- DOS头
- DOS头的定义(C语言):
- DOS程序
- NT头
- NT头的定义(C语言):
- PE签名(Signature)
- PE文件头(IMAGE_FILE_HEADER)
- PE文件头的定义(C语言):
- 结构解释:
- PE可选头(IMAGE_OPTIONAL_HEADER)
- PE可选头的定义(C语言):
- 结构解释:
- 节表(IMAGE_SECTION_HEADER)
- 节表的定义(C语言):
- 结构解释:
- 节(块)
- 导入表和导出表
- 导入表和导出表统一使用以下结构来寻找:
- 导入表的定义(C语言):
- 结构解释:
- 结构解释:
- 导出表的定义(C语言):
- 结构解释:
- 重定向表
- 重定向表的定义(C语言):
- 结构解释:
前置知识
VA、RVA、RWA
虚拟地址(Virtual Address,VA):装入内存中的实际地址。
相对虚拟地址(Relative Virtual Address,RVA):在内存中相对于PE文件装入地址的偏移位置,是一个相对地址。
文件偏移(Fill Offset):PE文件存储在磁盘上时,相对于文件头的偏移位置。16进制文件编辑器打开后的地址为文件偏移地址。
内存偏移 - 该段起始的RVA(VirtualAddress) = 文件偏移 - 该段的PointerToRawData
内存偏移 = 该段起始的RVA(VirtualAddress) + (文件偏移 - 该段的PointerToRawData)
文件偏移 = 该段的PointerToRawData + (内存偏移 - 该段起始的RVA(VirtualAddress))
整体结构
PE(Portable Execute)文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任何扩展名。
从起始位置开始依次是DOS头,NT头,节表以及具体的节。
结构如下:
DOS头:{
DOS头
DOS程序
}
NT头:{
PE签名
PE文件头
PE可选头
}
节表
.idata节
.text节
.data节
节可以有很多,不一定是这三种。
DOS头
DOS头的定义(C语言):
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;
一般有用的只有第一个和最后一个,
e_magic:WORD型,一般为0x4D5A(MZ),PE文件必须都是’MZ’开头。
e_lfanew:LONG型,用来表示DOS头之后的NT头相对文件起始地址的偏移。
DOS程序
紧跟着E_lfanew的是一个MS-DOS程序,在DOS中能够运行的程序,在windows中不能运行,如果为了节省文件空间大小,也可以直接省略
NT头
NT头的定义(C语言):
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; //PE文件头标志 => 4字节
IMAGE_FILE_HEADER FileHeader; //标准PE头 => 20字节
IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选PE头 => 32位下224字节(0xE0) 64位下240字节(0xF0)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
PE签名(Signature)
Signature:DWORD型,类似于DOS头中的e_magic,其高16位是0,低16是0x4550,用字符表示是’PE‘。
PE文件头(IMAGE_FILE_HEADER)
PE文件头的定义(C语言):
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //可以运行在什么平台上 任意:0 ,Intel 386以及后续:14C x64:8664
WORD NumberOfSections; //节的数量
DWORD TimeDateStamp; //编译器填写的时间戳
DWORD PointerToSymbolTable; //调试相关
DWORD NumberOfSymbols; //调试相关
WORD SizeOfOptionalHeader; //标识扩展PE头大小
WORD Characteristics; //文件属性 => 16进制转换为2进制根据哪些位有1,可以查看相关属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
结构解释:
Machine
该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。
#defineIMAGE_FILE_MACHINE_UNKNOWN 0
#defineIMAGE_FILE_MACHINE_I386 0x014c // Intel 386.
#defineIMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160big-endian
#defineIMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#defineIMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#defineIMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#defineIMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#defineIMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#defineIMAGE_FILE_MACHINE_SH3DSP 0x01a3
#defineIMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#defineIMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#defineIMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#defineIMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#defineIMAGE_FILE_MACHINE_THUMB 0x01c2
#defineIMAGE_FILE_MACHINE_AM33 0x01d3
#defineIMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#defineIMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#defineIMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#defineIMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#defineIMAGE_FILE_MACHINE_ALPHA64 0x0284 //ALPHA64
#defineIMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#defineIMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#defineIMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#defineIMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#defineIMAGE_FILE_MACHINE_CEF 0x0CEF
#defineIMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#defineIMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#defineIMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#defineIMAGE_FILE_MACHINE_CEE 0xC0EE
NumberOfSections
该PE文件中有多少个节,也就是节表中的项数。
TimeDateStamp
PE文件的创建时间,一般有连接器填写。表明文件是何时被创建的。这个值是自1970年1月1日以来用格林威治时间(GMT)计算的秒数,这个值是比文件系统(FILESYSTEM)的日期时间更加精确的指示器。
PointerToSymbolTable
COFF文件符号表在文件中的偏移,主要指向调式信息
NumberOfSymbols
符号表的数量。
SizeOfOptionalHeader
紧随其后的可选头的大小,对于32位系统,通常为0X00E0H,64位系统为0X00F0H。
Characteristics
可执行文件的属性,可以是下面这些值按位相或,定义在winnt.h头文件中。
关于Characteristics各位代表的意义:
Bit 0 :表示重定位信息已从文件中删除。该文件必须以其首选的基地址加载。如果基址不可用,则加载程序报告错误。
Bit 1 :表示该文件是可执行文件,是合法的。
Bit 2 ;COFF行号已从文件中删除。
Bit 3 :COFF符号条目已从文件中删除。
Bit 4 :保留
Bit 5 :表示应用程序可以处理大于2GB的地址
Bit 6-7 :保留
Bit 8 :表示机器基于32位体系结构。
Bit 9 :表示没有调试信息
Bit 10:表示该程序不能运行于可移动介质中(如软驱或CD-ROM)。
Bit 11:表示程序不能在网络介质运行。
Bit 12:表示文件是一个系统文件例如驱动程序。
Bit 13:表示文件是一个动态链接库(DLL)。
Bit 14:表示文件只能在单处理机器上运行。
Bit 15:保留
注:bit代表权值,bit0为最低位,bit15为最高位
PE可选头(IMAGE_OPTIONAL_HEADER)
PE可选头的定义(C语言):
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields. 必选的
//
WORD Magic; //PE32: 010B PE64: 020B
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; //所有含有代码的区块的大小 编译器填入 没用(可改)
DWORD SizeOfInitializedData; //所有初始化数据区块的大小 编译器填入 没用(可改)
DWORD SizeOfUninitializedData; //所有含未初始化数据区块的大小 编译器填入 没用(可改)
DWORD AddressOfEntryPoint; //程序入口RVA
DWORD BaseOfCode; //代码区块起始RVA
DWORD BaseOfData; //数据区块起始RVA
//
// NT additional fields. 可选的
//
DWORD ImageBase; //内存镜像基址(程序默认载入基地址)
DWORD SectionAlignment; //内存中对齐大小
DWORD FileAlignment; //文件中对齐大小(提高程序运行效率)
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage; //内存中整个PE文件的映射的尺寸,可比实际值大,必须是SectionAlignment的整数倍
DWORD SizeOfHeaders; //所有的头加上节表文件对齐之后的值
DWORD CheckSum; //映像校验和,一些系统.dll文件有要求,判断是否被修改
WORD Subsystem;
WORD DllCharacteristics; //文件特性,不是针对DLL文件的,16进制转换2进制可以根据属性对应的表格得到相应的属性
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录表,结构体数组
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
结构解释:
Magic
表示可选头的类型。
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b // 32位PE可选头
#defineIMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b // 64位PE可选头
#defineIMAGE_ROM_OPTIONAL_HDR_MAGIC 0x107
MajorLinkerVersion
链接器的版本号
MinorLinkerVersion
链接器的版本号
SizeOfCode
代码段的长度,如果有多个代码段,则是代码段长度的总和。
SizeOfInitializedData
初始化的数据长度。
SizeOfUninitializedData
未初始化的数据长度。
AddressOfEntryPoint
程序入口的RVA,对于exe可以理解为WinMain的RVA。对于DLL可以理解为DllMain的RVA,对于驱动程序,可以理解为DriverEntry的RVA。当然,实际上入口点并非是WinMain,DllMain和DriverEntry,在这些函数之前还有一系列初始化要完成。
BaseOfCode
代码段起始地址的RVA。
BaseOfData
数据段起始地址的RVA。
可选字段部分ImageBase
SectionAlignment
节对齐,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。
FileAlignment
节在文件中按此值对齐,SectionAlignment必须大于或等于FileAlignment。
MajorOperatingSystemVersion
所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。
MinorOperatingSystemVersion
所需操作系统的版本号,随着操作系统版本越来越多,这个好像不是那么重要了。
MajorImageVersion
映象的版本号,这个是开发者自己指定的,由连接器填写。
MinorImageVersion
映象的版本号,这个是开发者自己指定的,由连接器填写。
MajorSubsystemVersion
所需子系统版本号。
MinorSubsystemVersion
所需子系统版本号。
Win32VersionValue
保留,必须为0。
SizeOfImage
映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
SizeOfHeaders
所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
CheckSum
映象文件的校验和。
Subsystem
运行该PE文件所需的子系统,可以是下面定义中的某一个:
#defineIMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move.
#defineIMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image
#defineIMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#defineIMAGE_DLLCHARACTERISTICS_NO_ISOLATION 0x0200 // Image understands isolation and doesn't want it
#define IMAGE_DLLCHARACTERISTICS_NO_SEH 0x0400 // Image does not use SEH. No SE handler may reside in this image
#defineIMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.// 0x1000 // Reserved.
#defineIMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 //Driver uses WDM model// 0x4000 // Reserved.
#defineIMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000
SizeOfStackReserve
运行时为每个线程栈保留内存的大小。
SizeOfStackCommit
运行时每个线程栈初始占用内存大小。
SizeOfHeapReserve
运行时为进程堆保留内存大小。
SizeOfHeapCommit
运行时进程堆初始占用内存大小。
LoaderFlags
保留,必须为0。
NumberOfRvaAndSizes
数据目录的项数,即下面这个数组的项数。
DataDirectory
数据目录,这是一个数组,数组的项定义如下:
typedefstruct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;VirtualAddress
节表(IMAGE_SECTION_HEADER)
节表的定义(C语言):
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //ASCII字符串 可自定义 只截取8个字节
union { //该节在没有对齐之前的真实尺寸,该值可以不准确
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; //内存中的偏移地址
DWORD SizeOfRawData; //节在文件中对齐的尺寸
DWORD PointerToRawData; //节区在文件中的偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
结构解释:
Name
区块名。这是一个由8个ASCII码组成,用来定义区块的名称的数组。多数区块名都习惯性以一个“.”作为开头(例如:.text),这个“.”实际上是不是必须的。值得我们注意的是,如果区块名达到8 个字节,后面就没有0字符了。前边带有一个“$” 的区块名字会从连接器那里得到特殊的待遇,前边带有“$”的相同名字的区块在载入时候将会被合并,在合并之后的区块中,他们是按照“$”后边的字符的字母顺序进行合并的。每个区块的名称都是唯一的,不能有同名的两个区块。但事实上节的名称不代表任何含义,他的存在仅仅是为了正规统一编程的时候方便程序员查看方便而设置的一个标记而已。所以将包含代码的区块命名为“.Data”或者说将包含数据的区块命名为“.Code”都是合法的。当我们要从PE 文件中读取需要的区块时候,不能以区块的名称作为定位的标准和依据,正确的方法是按照IMAGE_OPTIONAL_HEADER32 结构中的数据目录字段结合进行定位。
VirtualSize
对表对应的区块的大小,这是区块的数据在没有进行对齐处理前的实际大小。
VirtualAddress
该区块装载到内存中的RVA地址。这个地址是按照内存页来对齐的,因此它的数值总是SectionAlignment的值的整数倍。
PointerToRawData
指出节在磁盘文件中所处的位置。这个数值是从文件头开始算起的偏移量。
SizeOfRawData
该区块在磁盘中所占的大小,这个数值等于VirtualSize字段的值按照FileAlignment的值对齐以后的大小。
Characteristics
该区块的属性。该字段是按位来指出区块的属性(如代码/数据/可读/可写等)的标志。
Characteristics的各二进制位的意义如下:
Bit 0-4 :保留
Bit 5 :表示包含可执行代码。
Bit 6 :表示包含初始化数据。
Bit 7 :表示包含未初始化数据,即程序执行前初始化未0。
Bit 8 :保留
Bit 9 :表示包含注释或其他信息。这仅对目标文件有效。
Bit 10 :保留
Bit 11 :表示该节在可执行文件链接后,作为文件一部分的数据被清除。
Bit 12 :表示包含公共块数据,这仅对目标文件有效。
Bit 13 :保留
Bit 14 :重置本部分TLB条目中的推测性异常处理位。
Bit 15 :表示本节包含全局指针引用的数据,需要在执行前侵入。
Bit 16-19 :保留
Bits 20-23 :指定对齐。一般是库文件的对象对齐。
Bit 24 :表示节包含扩展的重定位。
Bit 25 :表示该节可以根据需要丢弃。
Bit 26 :表示节的数据不得缓存。
Bit 27 :表示节的数据不得交换出去。
Bit 28 :表示节的数据在所有映象例程内共享
Bit 29 :表示进程得到“执行”访问节内存。
Bit 30 :表示进程得到“读出”访问节内存。
Bit 31 :表示进程得到“写入”访问节内存。
节(块)
每个节实际上是一个容器,可以包含代码、数据等等,每个节可以有独立的内存权限,比如代码节默认有读/执行权限,节的名字和数量可以自己定义。
通常,区块中的数据在逻辑上是关联的。PE 文件一般至少都会有两个区块:一个是代码块,另一个是数据块。每一个区块都需要有一个截然不同的名字,这个名字主要是用来表达区块的用途。例如有一个区块叫.rdata,表明他是一个只读区块。注意:区块在映像中是按起始地址(RVA)来排列的,而不是按字母表顺序。
另外,使用区块名字只是人们为了认识和编程的方便,而对操作系统来说这些是无关紧要的。微软给这些区块取了个有特色的名字,但这不是必须的。当编程从PE 文件中读取需要的内容时,如输入表、输出表,不能以区块名字作为参考,正确的方法是按照数据目录表中的字段来进行定位。
下列是一些常见的节名:
节名 | 内容 |
.bss | 未初始化的数据 |
.data | 代码节 |
.edata | 导出表 |
.idata | 导入表 |
.idlsym | 包含已注册的SEH,它们用以支持IDL属性 |
.pdata | 异常信息 |
.rdata | 只读的已初始化数据(用于常量) |
.reloc | 重定位信息 |
.rsrc | 资源目录 |
.sbss | 与GP相关的未初始化数据 |
.sdata | 与GP相关的已初始化数据 |
.srdata | 与GP相关的只读数据 |
.text | 默认代码节 |
导入表和导出表
导入表和导出表统一使用以下结构来寻找:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
导入表的定义(C语言):
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA 指向 INT (PIMAGE_THUNK_DATA结构数组)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name; //RVA指向dll名字,以0结尾
DWORD FirstThunk; // RVA 指向 IAT (PIMAGE_THUNK_DATA结构数组)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
结构解释:
OriginalFirstThunk
指向一个IMAGE_THUNK_DATA数组叫做输入名称表Import Name Table(INT),用来保存函数。
TimeDateStamp
该字段可以忽略。如果那里有绑定的话它包含时间/数据戳(time/data stamp)。如果它是0,就没有绑定在被导入的DLL中发生。在最近,它被设置为0xFFFFFFFF以表示绑定发生。
ForwarderChain
一般情况下我们也可以忽略该字段。在老版的绑定中,它引用API的第一个forwarder chain(传递器链表)。它可被设置为0xFFFFFFFF以代表没有forwarder。
Name
DLL名字的指针, 指向一个用NULL作为结束符的ASCII字符串的一个RVA,该字符串是该导入DLL文件的名称,如:KERNEL32.DLL
FirstThunk
它也指向IMAGE_THUNK_DATA数组叫做输入地址表Import Address Table(IAT)。
导入表还需要使用另外两种结构体:_IMAGE_THUNK_DATA64和**_IMAGE_IMPORT_BY_NAME**
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // 指向一个转向者字符串的RVA
ULONGLONG Function; // 被输入的函数的内存地址
ULONGLONG Ordinal; // 被输入API的序数值
ULONGLONG AddressOfData; // 指针指向 IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; //可能为空,编译器决定,如果不为空,是函数在导出表的索引
BYTE Name[1]; //函数名称,以0结尾
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
结构解释:
当IMAGE_THUNK_DATA 的值最高位为1时,表示函数是以序号方式输入,这时低31为被当作函数序号。当最高位是0时,表示函数是以字符串类型的函数名方式输入的,这时,IMAGE_THUNK_DATA 的值为指向IMAGE_IMPORT_BY_NAME 的结构的RVA。
typedef struct_IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME;
Hint
表示这个函数在其所驻留DLL的输出表的序号,不是必须的。
Name
表示 函数名,是一个ASCII字符串以0结尾,大小不固定。
导出表的定义(C语言):
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // 指针指向该导出表文件名字符串
DWORD Base; // 导出函数起始序号
DWORD NumberOfFunctions; // 所有导出函数的个数
DWORD NumberOfNames; // 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 指针指向导出函数地址表RVA
DWORD AddressOfNames; // 指针指向导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 指针指向导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
结构解释:
Characteristics
现在没有用到,一般为0。
TimeDateStamp
导出表生成的时间戳,由连接器生成。
MajorVersion,MinorVersion
看名字是版本,实际貌似没有用,都是0。
Name
模块的名字。
Base
序号的基数,按序号导出函数的序号值从Base开始递增。
NumberOfFunctions
所有导出函数的数量。
NumberOfNames
按名字导出函数的数量。
AddressOfFunctions
一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同。
AddressOfNames
一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字。
AddressOfNameOrdinals
一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号。
导出表里面最后还有三个表,这三个表可以让我们找到函数真正的地址,在编写PE格式解析器的时候可以用到,AddressOfFunctions 是函数地址表,指向每个函数真正的地址,AddressOfNames 和 AddressOfNameOrdinals 分别是函数名称表和函数序号表,我们知道DLL文件有两种调用方式,一种是用名字,一种是用序号,通过这两个表可以用来寻找函数在 AddressOfFunctions 表中真正的地址。
重定向表
重定向表的定义(C语言):
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 重定位数据的开始 RVA 地址
DWORD SizeOfBlock; // 重定位块的长度
// WORD TypeOffset[1]; // 重定位项数组
} IMAGE_BASE_RELOCATION;
结构解释:
VirtualAddress
是这一组重定位数据的开始RVA地址.各重定位项的地址加上这个值才是该重定位项完整的RVA地址.
SizeOfBlock
是重定位结构的大小
TypeOffset
是一个数组.数组每项大小为两个字节,共16位.它又分为高4位和低12位,高4位代表重定位类型;低12位是重定位地址,它与VirtualAddress相加即是指向PE映像中需要修改的地址数据的指针.