PE文件格式
介绍
PE文件是Windows操作系统下使用的可执行文件格式。它是微软在UNIX平台的COFF(Common Object File Format,通用对象文件格式)基础上制作而成的。
PE文件是指32位的可执行文件,也称PE32。64位的可执行未见成为PE+或PE32+,是PE(PE32)文件的一种扩展形式。
PE文件格式
PE文件种类如下表:
种类 | 主扩展名 |
---|---|
可执行系列 | exe、scr |
库系列 | dll、ocx、cpl、drv |
驱动程序系列 | sys、vxd |
对象文件系列 | obj |
严格来说,obj(对象)文件之外的所有文件都是可执行的。dll、sys文件等虽然不能直接在Shell(Explorer.exe)中运行,但是可以使用其他方法(调试器、服务等)执行。
这里以xp下的notepad.exe为例:
基础结构
PE文件 从DOS头(DOS header)到 节区头(Section header)是PE头部分
,剩下的节区合称 PE体
。
文件中使用 偏移(offset),内存中使用 VA(Virtual Address,虚拟地址)来表示位置。
文件加载到内存时,情况会发生变化(节区大小、位置等)。文件的内容一般可分为 代码节(.text)、数据节(.data)、资源节(.rsrc)。
当PE文件加载到内存中后,各个节区位置将会发生偏移变化,但都遵循一定规则,下面以xp下的notepad.exe为例:
VA&RVA
VA指的是进程虚拟内存的绝对地址;
RVA(Relative Virtual Address,相对虚拟地址)指从某个基地址(ImageBase)开始的相对地址。
VA 与 RVA 满足这样的换算关系:RVA + ImageBase = VA
。
PE头内部信息大多以RVA形式存在。当PE文件加载到进程虚拟内存中时,可能并没有一个连贯的内存空间用来加载PE文件,此时必须通过重定位(Relocation)将其加载到其他空白的位置,如果PE头信息使用VA,那么将无法正常访问。因此采用RVA来定位,即使发生了重定位,只要相对于基地址的相对地址没有变化,就能正常访问到指定信息。
PE文件结构
PE文件由多个数据结构体组成,
DOS头
微软创建PE
文件格式时,当时正在广泛使用DOS
文件,为了向下兼容,在PE
头的最前面添加了一个IMAGE_DOS_HEADER
结构体,用来扩展已有的DOS EXE
头。
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结构体的大小是64
字节,该结构体中最为重要的成员是:e_magic
、e_lfanew
。
e_magic:DOS签名(signature,4D5A => ASCII值 "MZ")
e_lfanew:标明NT头(IMAGE_NT_HEADERS)的偏移(不同的文件偏移可能不一样)
在winnt.h
中存在如下的宏定义DOS
签名:
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
DOS存根
DOS存根在DOS头下方,是个可选项,却大小不固定,DOS存根由代码和数据混合而成,主要是用来在DOS环境下执行时输出的,现在已经用不上了。
NT头(IMAGE_NT_HEADERS)
在DOS头中,e_lfanew
标明了NT
头的偏移,这样系统在执行的时候能准确的找到NT头的地址,并从中读取数据,该结构体的内容如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
IMAGE_NT_HEADERS结构体由3个成员组成:签名结构体(Signature)4
字节、文件头(FileHeader)20
字节、可选头(OptionalHeader)。在实际编译过程中,分为PIMAGE_NT_HEADERS32
和PIMAGE_NT_HEADERS64
,两者的不变的是Signature
和IMAGE_FILE_HEADER
;区别在于IMAGE_OPTIONAL_HEADER32
和IMAGE_OPTIONAL_HEADER64
。
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
在winnt.h
中存在如下的宏定义NT
签名:
#define IMAGE_NT_SIGNATURE 0x00004550 // PE00
NT头:文件头(IMAGE_FILE_HEADER)
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 运行平台
WORD NumberOfSections; // 区块数
DWORD TimeDateStamp; // 创建时间和日期
DWORD PointerToSymbolTable; // 符号表指针
DWORD NumberOfSymbols; // 符号表中的符号数
WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER 长度
WORD Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
IMAGE_FILE_HEADERS
结构体中有四个重要成员:Machine
、NumberOfSections
、SizeOfOptionalHeader
、Characteristics
。
1.Machine
顾名思义,标识机器码,一般情况下,在个人PC
上遇到的应该是IMAGE_FILE_MACHINE_I386
(x86)和IMAGE_FILE_MACHINE_AMD64
(x64)
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_TARGET_HOST 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest.
#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_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
NumberOfSections
PE文件把代码、数据、资源等依据属性分类到各节区中存储,NumberOfSections
则是标明文件中存在的节区数量的。该值一定大于0。
SizeOfOptionalHeader
这是一个指明IMAGE_OPTIONAL_HEADER
长度的结构体,由于在64位程序中,IMAGE_OPTIONAL_HEADER64
与32位程序是不同的,所以需要一个专用结构体来指明长度。
Characteristics
该字段用于标识文件的属性,文件是否是可执行,是否为dll文件等信息。最终结果以bit or
的形式存放,例如:该PE文件不存在重定位信息(0x0001)、是可执行文件(0x0002)、行号已删除(0x0004)、本地符号已删除(0x0008)、是32位程序(0x0100),最后得出该标识结果为:0x010F`
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved external references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File.
#define IMAGE_FILE_DLL 0x2000 // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
NT头:可选头(IMAGE_OPTIONAL_HEADER)
IMAGE_OPTIONAL_HEADER
的起始偏移地址,可以由0x3C
处的NT
起始地址加上24
得出。
IMAGE_OPTIONAL_HEADER
可分为32位结构体96字节
和64位结构体112字节
(不含MAGE_DATA_DIRECTORY DataDirectory
),这里用IMAGE_OPTIONAL_HEADER32
举例说明;
IMAGE_OPTIONAL_HEADER32
是PE头结构体中最大的。
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; // 标志位
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; // 保留,通常为0
DWORD SizeOfImage; // 映像载入内存后的总尺寸
DWORD SizeOfHeaders; // DOS 头、PE 头、区块表总大小
DWORD CheckSum; // 映像校验和
WORD Subsystem; // 文件子系统
WORD DllCharacteristics; // 显示 DLL 特性的旗标
DWORD SizeOfStackReserve; // 初始化时栈的大小
DWORD SizeOfStackCommit; // 初始化时实际提交栈的大小
DWORD SizeOfHeapReserve; // 初始化时保留堆的大小
DWORD SizeOfHeapCommit; // 初始化时实际保留堆的大小
DWORD LoaderFlags; // 与调试相关,默认为0
DWORD NumberOfRvaAndSizes; // 数据目录表项数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
在IMAGE_OPTIONAL_HEADER32
结构体中需要关注的成员是:Magic
、AddressOfEntryPoint
、ImageBase
、SectionAlignment
、FileAlignment
、SizeOfImage
、SizeOfHeaders
、Subsystem
、DllCharacteristics
、NumberOfRvaAndSizes
、DataDirectory
Magic
x86程序的magic码为:0x10b
,x64程序的magic码为:0x20b
。
#define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b
#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b
AddressOfEntryPoint
存放EP的RVA值,用于指明程序最先执行的代码起始地址。
ImageBase
进程虚拟内存的范围是0~FFFFFFFF(32位系统)。PE文件被加载到如此大的内存中时,ImageBase用于指出文件的优先装入地址。
exe、dll文件被装载的范围是:0~0x7FFFFFFF;
sys文件被装载的范围是:0x80000000~0xFFFFFFFF;
SectionAlignment,FileAlignment
PE
文件的Body部分被划分成若干个区域,SectionAlignment
指定了节区在内存中的最小单位,而FileAlignment
则指定了节区在磁盘文件中的最小单位,同一个文件中,两者的值可能相同,也有可能不同。磁盘文件或内存节区大小,必定是二者的整数倍。
SizeOfImage
PE
文件被加载到内存中时,SizeOfImage
指定了PE Image
在虚拟地址中所占的空间大小,一般情况下,文件的大小和加载到内存中的大小是不一样的。
SizeOfHeaders
SizeOfHeaders
指明PE头的大小,该值必须是FileAlignment
的整数倍。
第一节区所在位置和`SizeOfHeaders`距文件开始偏移的量相同。
Subsystem
Subsystem
用来区分系统驱动文件( .sys
)与普通可执行文件( .exe
, .dll
)。主要的对照关系如下表:
值 | 含义 | 备注 |
---|---|---|
1 | Driver文件 | 系统驱动 |
2 | GUI文件 | 带窗口的应用程序 |
3 | CUI文件 | 控制台的应用程序 |
#define IMAGE_SUBSYSTEM_NATIVE 1 // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Image runs in the Windows character subsystem.
DllCharacteristics
如果当前文件是一个DLL
,该DLL
特征则是以下宏定义的集合:
#define IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA 0x0020 // Image can handle a high entropy 64-bit virtual address space.
#define IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE 0x0040 // DLL can move.
#define IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY 0x0080 // Code Integrity Image
#define IMAGE_DLLCHARACTERISTICS_NX_COMPAT 0x0100 // Image is NX compatible
#define IMAGE_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
#define IMAGE_DLLCHARACTERISTICS_NO_BIND 0x0800 // Do not bind this image.
#define IMAGE_DLLCHARACTERISTICS_APPCONTAINER 0x1000 // Image should execute in an AppContainer
#define IMAGE_DLLCHARACTERISTICS_WDM_DRIVER 0x2000 // Driver uses WDM model
#define IMAGE_DLLCHARACTERISTICS_GUARD_CF 0x4000 // Image supports Control Flow Guard.
#define IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE 0x8000
NumberOfRvaAndSizes
用来指明DataDirectory
数组的个数。从winnt.h
的结构体申明中可以看到,虽然定义了数组个数为16,但也有可能不是16,具体的还是需要查看NumberOfRvaAndSizes
来进行判定。
DataDirectory 该结构体如下:这是一个只有8字节大小端数组,对不同的PE文件而言,数量可能存在差异,但每一个的结构是一致的。
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory
是由IMAGE_DATA_DIRECTORY
结构体组成的数组,数组的每项都有被定义的值,结构体偏移对应如下:
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导出目录
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 导入目录
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // 基址重定位表
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 调试目录
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // 架构特定数据
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // 全局指针
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // 加载配置目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // 绑定导入目录
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // 导入地址表(IAT)
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // 延迟加载导入描述符
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM 运行时描述符
区块头(IMAGE_SECTION_HEADER)
IMAGE_SECTION_HEADER
的起始偏移地址,可以由0x3C
处的NT
起始地址加上文件头
中SizeOfOptionalHeader
(NT偏移0x14
处WORD
型)得出,其实就是紧接着IMAGE_OPTIONAL_HEADER
。
区块头中定义了各节区的属性,大致可分为三种:code
(代码)、data
(数据)、resource
(资源)节区。
类别 | 访问权限 |
---|---|
code | 执行,读取权限 |
data | 不可执行,读写权限 |
resource | 不可执行,读取权限 |
IMAGE_SECTION_HEADER
区块头是由IMAGE_SECTION_HEADER
结构体组成的数组,每个结构体对应一个节区共计40
字节。
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 区块名
union { // 区块尺寸
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; // 区块的起始地址 RVA
DWORD SizeOfRawData; // 磁盘文件中节区所占大小
DWORD PointerToRawData; // 磁盘文件中节区起始位置
DWORD PointerToRelocations; // 在 OBJ 文件中使用,重定位的偏移
DWORD PointerToLinenumbers; // 行号表的偏移(调试用)
WORD NumberOfRelocations; // 在 OBJ 文件中使用,重定位项数量
WORD NumberOfLinenumbers; // 行号表中行号的数量
DWORD Characteristics; // 区块的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Characteristics
的值由以下组合(bit or
)而成,标识当前区块的属性
#define IMAGE_SCN_CNT_CODE 0x00000020 // 区块包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA 0x00000040 // 区块包含初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA 0x00000080 // 区块包含未初始化数据
#define IMAGE_SCN_LNK_INFO 0x00000200 // 区块包含注释或其它类型的信息
#define IMAGE_SCN_LNK_REMOVE 0x00000800 // 区块内容不会成为镜像的一部分
#define IMAGE_SCN_LNK_COMDAT 0x00001000 // 区块包含 comdat
#define IMAGE_SCN_NO_DEFER_SPEC_EXC 0x00004000 // 重置此区块 TLB 条目中的推测性异常处理位
#define IMAGE_SCN_GPREL 0x00008000 // 区块内容可被GP访问
// 对其方式指定
#define IMAGE_SCN_ALIGN_1BYTES 0x00100000 //
#define IMAGE_SCN_ALIGN_2BYTES 0x00200000 //
#define IMAGE_SCN_ALIGN_4BYTES 0x00300000 //
#define IMAGE_SCN_ALIGN_8BYTES 0x00400000 //
#define IMAGE_SCN_ALIGN_16BYTES 0x00500000 // 如果未指定其它对齐方式,则默认对齐
#define IMAGE_SCN_ALIGN_32BYTES 0x00600000 //
#define IMAGE_SCN_ALIGN_64BYTES 0x00700000 //
#define IMAGE_SCN_ALIGN_128BYTES 0x00800000 //
#define IMAGE_SCN_ALIGN_256BYTES 0x00900000 //
#define IMAGE_SCN_ALIGN_512BYTES 0x00A00000 //
#define IMAGE_SCN_ALIGN_1024BYTES 0x00B00000 //
#define IMAGE_SCN_ALIGN_2048BYTES 0x00C00000 //
#define IMAGE_SCN_ALIGN_4096BYTES 0x00D00000 //
#define IMAGE_SCN_ALIGN_8192BYTES 0x00E00000 //
#define IMAGE_SCN_ALIGN_MASK 0x00F00000
#define IMAGE_SCN_LNK_NRELOC_OVFL 0x01000000 // 区块包含扩展重定位
#define IMAGE_SCN_MEM_DISCARDABLE 0x02000000 // 区块可被丢弃
#define IMAGE_SCN_MEM_NOT_CACHED 0x04000000 // 区块不可缓存
#define IMAGE_SCN_MEM_NOT_PAGED 0x08000000 // 区块不可分页
#define IMAGE_SCN_MEM_SHARED 0x10000000 // 区块可共享
#define IMAGE_SCN_MEM_EXECUTE 0x20000000 // 区块可执行
#define IMAGE_SCN_MEM_READ 0x40000000 // 区块可读
#define IMAGE_SCN_MEM_WRITE 0x80000000 // 区块可写
数据目录(IMAGE_DATA_DIRECTORY)
在表述完NT
头的结构后,还需要深入了解IMAGE_OPTIONAL_HEADER
结构体中包含的IMAGE_DATA_DIRECTORY DataDirectory
数组结构,其中包含了系统默认的16
个数据目录的内存地址(RVA
)及大小,接下来将逐个分析
导出目录(IMAGE_EXPORT_DIRECTORY)
导出表(EAT
)中则存储了相应库中导出函数的起始地址,且PE
文件中仅有一个用来说明库EAT
的IMAGE_EXPORT_DIRECTORY
结构体。
IMAGE_OPTIONAL_HEADER
的IMAGE_DATA_DIRECTORY
首个偏移位中存放着EAT
的起始地址(RVA
)及大小。
IMAGE_EXPORT_DIRECTORY
结构体代码如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用,默认为0
DWORD TimeDateStamp; // 文件生成时间
WORD MajorVersion; // 主版本号,一般为0
WORD MinorVersion; // 次版本号,一般为0
DWORD Name; // 模块的真实名称 RVA
DWORD Base; // 基数,序数减基数等于函数地址数组的索引
DWORD NumberOfFunctions; // 导出函数表个数
DWORD NumberOfNames; // 导出函数名个数
DWORD AddressOfFunctions; // 指向函数地址数组的内存地址 RVA
DWORD AddressOfNames; // 指向函数名称指针的内存地址 RVA
DWORD AddressOfNameOrdinals; // 指向输出序列号数组的内存地址 RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
在导出表中Name
指向按字节存放名称字符串的内存偏移地址,Base
是导出函数地址表的基数,通过函数地址表的序数减去基数就是函数地址表的索引,仔细观察整个数组可以发现,函数名称表存在两个数组:AddressOfNames
和AddressOfNameOrdinals
,而函数地址表只存在一个数组:AddressOfFunctions
,默认第一条函数地址的序号为1
,减去索引Base
的值,就应当和AddressOfNameOrdinals
索引一一对应,以完成函数地址和函数名的匹配。(一般情况下Base
的值为1
,但也有可能人为修改,混淆文件结构)
导入目录(IMAGE_IMPORT_DESCRIPTOR)
IMAGE_IMPORT_DESCRIPTOR
结构体中记录着PE文件要导入哪些库文件。
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 用于终止空导入描述符
DWORD OriginalFirstThunk; // 导入名称表的 RVA (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 时间日期
DWORD ForwarderChain; // API 多引用索引,一般为0 ,-1 表示没有引用索引
DWORD Name; // DLL 名称指针地址 RVA
DWORD FirstThunk; // 导入地址表的 RVA (PIMAGE_THUNK_DATA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
OriginalFirstThunk
与FirstThunk
实际上都是IMAGE_THUNK_DATA
结构数组:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // 指向导入名称字符串的 RVA
DWORD Function; // 导入的函数内存地址
DWORD Ordinal; // 导入函数的 API 序数值
DWORD AddressOfData; // 指向 PIMAGE_IMPORT_BY_NAME 的 RVA
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
IMAGE_THUNK_DATA
数组结构可导向IMAGE_IMPORT_BY_NAME
结构,其中包含了Hint
和Name
,
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 该函数在 DLL 导出表中的序号
CHAR Name[1]; // 导入函数的函数名
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
以上是层层包含关系的说明,实际上,一个PE
文件中可能包含多个IMAGE_IMPORT_DESCRIPTOR
结构体,他们以此排列,可以从上往下获取,在一个IMAGE_IMPORT_DESCRIPTOR
结构体中,指向的IMAGE_THUNK_DATA
结构体又往往是多个依次排列的,所以想要遍历出所有的导入函数需要层层递进、逐个列举。
资源目录(IMAGE_RESOURCE_DIRECTORY)
IMAGE_IMPORT_DESCRIPTOR
目录由两个部分共同组成,16
字节的基本信息以及紧随其后的可变长度的目录条目数组:
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 属性标志,通常为 0
DWORD TimeDateStamp; // 资源建立的时间日期
WORD MajorVersion; // 资源主版本号,通常为0
WORD MinorVersion; // 资源次版本号,通常为0
WORD NumberOfNamedEntries; // 采用名称的资源条目个数
WORD NumberOfIdEntries; // 采用 ID 的资源条目个数
// IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
IMAGE_RESOURCE_DIRECTORY_ENTRY
目录包含两个union
,每个4
字节,共计8
字节。
第一个union
用于标识当前目录的属性是Name
还是ID
。如果目录是采用名称的资源,那么高位的NameIsString
设置为1
,低31
位的NameOffset
则是名称字符串偏移量。如果目录是采用ID
的资源,那么高位清零,低位16
位是标识此资源目录的。
第二个union
用于标识当前目录是Data
还是Directory
。如果目录是数据Data
,那么高位的DataIsDirectory
则为0
,OffsetToData
则是资源数据偏移量,如果目录是子目录Directory
,那么高位的DataIsDirectory
则为1
,低31
位的OffsetToDirectory
则是资源子目录偏移量:
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31; // 名称字符串结构偏移地址,相当于当前目录的起始地址的偏移
DWORD NameIsString:1; // 是否是字符串目录标识
} DUMMYSTRUCTNAME;
DWORD Name;
WORD Id; // 预定义资源类型 ID 号
} DUMMYUNIONNAME;
union {
DWORD OffsetToData; // 数据入口的偏移地址,相对于资源根目录起始地址的偏移
struct {
DWORD OffsetToDirectory:31; // 资源目录的偏移地址,相对于资源根目录起始地址的偏移
DWORD DataIsDirectory:1; // 是否是资源目录标识
} DUMMYSTRUCTNAME2;
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;
对于采用名称的资源目录,偏移处对应的数据结构类型为IMAGE_RESOURCE_DIRECTORY_STRING
。但目前的应用程序生成时基本都采用Unicode
编码,故应当使用IMAGE_RESOURCE_DIR_STRING_U
获取数据:
typedef struct _IMAGE_RESOURCE_DIRECTORY_STRING {
WORD Length;
CHAR NameString[ 1 ];
} IMAGE_RESOURCE_DIRECTORY_STRING, *PIMAGE_RESOURCE_DIRECTORY_STRING;
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; // 名称字符串长度
WCHAR NameString[ 1 ]; // 名称字符串
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
在获取资源数据时,应当采用IMAGE_RESOURCE_DATA_ENTRY
结构体获取,
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // 资源数据的存放地址 RVA
DWORD Size; // 资源数据的长度
DWORD CodePage; // 代码页,一般为0
DWORD Reserved; // 保留字段
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
对于ID
资源,可分为预定义资源类型和自定义资源类型,当ID
数值在1-24
之间时,属于预定义资源:
/*
* Predefined Resource Types
*/
#define RT_CURSOR MAKEINTRESOURCE(1) // 鼠标指针
#define RT_BITMAP MAKEINTRESOURCE(2) // 位图
#define RT_ICON MAKEINTRESOURCE(3) // 图标
#define RT_MENU MAKEINTRESOURCE(4) // 菜单
#define RT_DIALOG MAKEINTRESOURCE(5) // 对话框
#define RT_STRING MAKEINTRESOURCE(6) // 字符串列表
#define RT_FONTDIR MAKEINTRESOURCE(7) // 字体目录
#define RT_FONT MAKEINTRESOURCE(8) // 字体
#define RT_ACCELERATOR MAKEINTRESOURCE(9) // 快捷键
#define RT_RCDATA MAKEINTRESOURCE(10) // 非格式化资源
#define RT_MESSAGETABLE MAKEINTRESOURCE(11) // 消息列表
#define DIFFERENCE 11
#define RT_GROUP_CURSOR MAKEINTRESOURCE((ULONG_PTR)(RT_CURSOR) + DIFFERENCE) // 鼠标指针组
#define RT_GROUP_ICON MAKEINTRESOURCE((ULONG_PTR)(RT_ICON) + DIFFERENCE) // 图标组
#define RT_VERSION MAKEINTRESOURCE(16) // 版本信息
#define RT_DLGINCLUDE MAKEINTRESOURCE(17) // DLGINCLUDE
#if(WINVER >= 0x0400)
#define RT_PLUGPLAY MAKEINTRESOURCE(19) // PLUGPLAY
#define RT_VXD MAKEINTRESOURCE(20) // VXD
#define RT_ANICURSOR MAKEINTRESOURCE(21) // 动态指针
#define RT_ANIICON MAKEINTRESOURCE(22) // 动态图标
#endif /* WINVER >= 0x0400 */
#define RT_HTML MAKEINTRESOURCE(23) // HTML
#define RT_MANIFEST MAKEINTRESOURCE(24) // MANIFEST
让我们来理一理这个资源目录是如何定位资源数据入口的:
- 资源目录中会包含若干个不同类型(
Name&ID
)的目录入口(ENTRY
); - 每一个同类型(
Name&ID
)的目录入口(ENTRY
)会存在一个目录(DIRECTORY
)用来描述当前类型的资源信息; - 而这个目录(
DIRECTORY
)往往会有多个目录入口(ENTRY
),用于指定同类型的多个资源信息; - 在单个资源目录入口(
ENTRY
)中,会存在一个目录(DIRECTORY
),用来描述当前条目资源的信息; - 而这个目录(
DIRECTORY
)中仅仅会包含一个目录入口(ENTRY
),用来指定当前条目数据入口的信息; - 最终由这个目录入口(
ENTRY
)定位到当前资源的数据入口PIMAGE_RESOURCE_DATA_ENTRY
;
结构流程图如下:
异常目录(IMAGE_EXCEPTION_DIRECTORY)
.pdata
部分包含一组用于异常处理的函数表入口。它由图像数据目录中的异常表指向。在目前的编译架构中,大都以IMAGE_RUNTIME_FUNCTION_ENTRY
结构体存储
typedef struct _IMAGE_RUNTIME_FUNCTION_ENTRY {
DWORD BeginAddress; // 开始地址
DWORD EndAddress; // 结束地址
union { // Unwind 信息
DWORD UnwindInfoAddress;
DWORD UnwindData;
} DUMMYUNIONNAME;
} _IMAGE_RUNTIME_FUNCTION_ENTRY, *_PIMAGE_RUNTIME_FUNCTION_ENTRY;
IMAGE_RUNTIME_FUNCTION_ENTRY
结构体通常情况下是一个接着一个的,直到最后一个为空则结束。
证书目录(IMAGE_SECURITY_DIRECTORY)
在微软的官方说明里,证书目录指向的是一组连续的、四字对齐的属性证书条目。在文件的原始结尾和属性证书表的开头之间插入零填充以实现这种对齐。每个属性证书条目都包含以下字段:
偏移 | 大小 | 字段 | 说明 |
---|---|---|---|
0 | 4 | dwLength | 指定属性证书条目的长度 |
4 | 2 | wRevision | 指定证书版本号 |
6 | 2 | wCertificateType | 指定 bCertificate 中的内容类型 |
8 | 剩余长度大小 | bCertificate | 证书详细内容 |
wRevision
可分为以下两个部分:
值 | 类型 | 说明 |
---|---|---|
0x0100 | WIN_CERT_REVISION_1_0 | 版本 1,Win_Certificate 结构的旧版本。 仅出于验证旧 Authenticode 签名的目的而支持它 |
0x0200 | WIN_CERT_REVISION_2_0 | 版本 2 是 Win_Certificate 结构的当前版本。 |
wCertificateType
可分为以下四个部分:
值 | 类型 | 说明 |
---|---|---|
0x0001 | WIN_CERT_TYPE_X509 | bCertificate 包含不支持的 X.509 证书 |
0x0002 | WIN_CERT_TYPE_PKCS_SIGNED_DATA | bCertificate 包含一个 PKCS#7 SignedData 结构 |
0x0003 | WIN_CERT_TYPE_RESERVED_1 | 保留 |
0x0004 | WIN_CERT_TYPE_TS_STACK_SIGNED | 不支持终端服务器协议栈证书签名 |
有关 Authenticode 数字签名格式的详细信息,请参阅Windows Authenticode Portable Executable Signature Format
基址重定位表(IMAGE_BASERELOC_DIRECTORY)
在reloc
区块中,存放着基址重定位表,并非所有的PE
文件都具有重定位表,当基址是随机的时候,文件内的地址结构就不能按照定式进行寻找,这时候通过重定位表,就能将文件的区块结构一一对应起来,进行定位。
重定位目录结构指向若干个IMAGE_BASE_RELOCATION
结构体
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 重定位的虚拟地址 RVA
DWORD SizeOfBlock; // 当前重定位块大小
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
每一个IMAGE_BASE_RELOCATION
结构体后,都跟随着若干个word
型(2
字节)条目,这些条目的高4
位指定基重定位类型,剩余12
位指定的起始地址的偏移量,在winnt.h
中,并未进行结构体定义,但我们可以自定义一个结构体类型来表示该条目内容:
typedef struct _RELOCATIONDATA {
WORD Offset : 12; // 数据偏移量,相当于当前重定位块的起始虚拟地址 RVA
WORD Type : 4; // 重定位类型
}RELOCATIONDATA, * PRELOCATIONDATA;
RELOCATIONDATA
结构体的具体个数,是可计算的:(SizeOfBlock - 8)/ 2
。SizeOfBlock
指定了当前重定位块的总大小,减去IMAGE_BASE_RELOCATION
的长度(8
字节),由于数据偏移是2
字节大小,除以2
得到当前块的重定位条目数。RELOCATIONDATA
结构体中的Type
重定位的类型,在winnt.h
中存在以下预定义:
#define IMAGE_REL_BASED_ABSOLUTE 0 // 基址重定位被跳过
#define IMAGE_REL_BASED_HIGH 1 // 基本重定位将差异的高 16 位添加到偏移处的 16 位字段
#define IMAGE_REL_BASED_LOW 2 // 基本重定位将差异的低 16 位添加到偏移处的 16 位字段
#define IMAGE_REL_BASED_HIGHLOW 3 // 基本重定位将所有 32 位差异应用于偏移处的 32 位字段
#define IMAGE_REL_BASED_HIGHADJ 4 // 基本重定位将差异的高 16 位添加到偏移处的 16 位字段
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5 // 重定位解释取决于机器类型
#define IMAGE_REL_BASED_RESERVED 6 // 保留,必须为零
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7 // 重定位解释取决于机器类型
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8 // 重定位解释取决于机器类型
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9 // 重定位解释取决于机器类型
#define IMAGE_REL_BASED_DIR64 10 // 基本重定位将差异应用于偏移处的 64 位字段
调试目录(IMAGE_DEBUG_DIRECTORY)
该目录指向一个IMAGE_DEBUG_DIRECTORY
结构体,该结构体保存着调试信息的相关属性以及寻址地址:
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics; // 未使用,一般为0
DWORD TimeDateStamp; // 时间日期
WORD MajorVersion; // 主版本
WORD MinorVersion; // 次版本
DWORD Type; // 调试信息类型
DWORD SizeOfData; // 调试数据大小
DWORD AddressOfRawData; // 调试数据的内存地址 RVA
DWORD PointerToRawData; // 调试数据的文件偏移地址
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
针对Type
,winnt.h
中进行了以下预定义:
#define IMAGE_DEBUG_TYPE_UNKNOWN 0 // 所有工具都忽略的未知值
#define IMAGE_DEBUG_TYPE_COFF 1 // COFF 调试信息
#define IMAGE_DEBUG_TYPE_CODEVIEW 2 // Visual C++ 调试信息
#define IMAGE_DEBUG_TYPE_FPO 3 // 帧指针省略 (FPO) 信息
#define IMAGE_DEBUG_TYPE_MISC 4 // DBG 文件的位置
#define IMAGE_DEBUG_TYPE_EXCEPTION 5 // .pdata 部分的副本
#define IMAGE_DEBUG_TYPE_FIXUP 6 // 保留
#define IMAGE_DEBUG_TYPE_OMAP_TO_SRC 7 // 从图像中的 RVA 到源图像中的 RVA 的映射
#define IMAGE_DEBUG_TYPE_OMAP_FROM_SRC 8 // 从源图像中的 RVA 到图像中的 RVA 的映射
#define IMAGE_DEBUG_TYPE_BORLAND 9 // 为 Borland 保留
#define IMAGE_DEBUG_TYPE_RESERVED10 10 // 保留
#define IMAGE_DEBUG_TYPE_CLSID 11 // 保留
#define IMAGE_DEBUG_TYPE_VC_FEATURE 12 //
#define IMAGE_DEBUG_TYPE_POGO 13 //
#define IMAGE_DEBUG_TYPE_ILTCG 14 //
#define IMAGE_DEBUG_TYPE_MPX 15 //
#define IMAGE_DEBUG_TYPE_REPRO 16 // PE 确定性或再现性
#define IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS 20 // 扩展的 DLL 特性位
架构特定数据(IMAGE_ARCHITECTURE_DIRECTORY)
在官方说明中指出,该目录必须为空:
全局指针(IMAGE_GLOBALPTR_DIRECTORY)
在官方说明中指出:RVA
的值要存储在全局指针寄存器中。 此结构的size
成员必须设置为零。
TLS目录(IMAGE_TLS_DIRECTORY)
.tls
部分为静态线程本地存储 (TLS
) 提供直接的PE
和COFF
支持。TLS
是Windows
支持的特殊存储类,其中数据对象不是自动(堆栈)变量,而是运行代码的每个单独线程的本地。因此,每个线程可以为使用TLS
声明的变量维护不同的值。TLS
目录指向的是一个IMAGE_TLS_DIRECTORY
结构体:
typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData; // 起始地址
DWORD EndAddressOfRawData; // 结束地址
DWORD AddressOfIndex; // 索引地址 PDWORD
DWORD AddressOfCallBacks; // 回调地址 PIMAGE_TLS_CALLBACK
DWORD SizeOfZeroFill; // 零填充大小
union { // 特征
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;
} IMAGE_TLS_DIRECTORY32;
typedef IMAGE_TLS_DIRECTORY32 * PIMAGE_TLS_DIRECTORY32;
加载配置目录(IMAGE_LOAD_CONFIG_DIRECTORY)
加载配置目录指向的加载配置结构IMAGE_LOAD_CONFIG_DIRECTORY
用于描述在文件头或图像的可选头中难以描述或太大而无法描述的各种功能,
typedef struct _IMAGE_LOAD_CONFIG_DIRECTORY32 {
DWORD Size; // 结构的大小
DWORD TimeDateStamp; // 时间日期
WORD MajorVersion; // 主要版本号
WORD MinorVersion; // 次要版本号
DWORD GlobalFlagsClear; // 全局标志清除
DWORD GlobalFlagsSet; // 全局标志集合
DWORD CriticalSectionDefaultTimeout; // 临界区默认超时值
DWORD DeCommitFreeBlockThreshold; // 在释放(取消提交)之前必须释放的最小块的大小,以字节为单位。该值是建议性的
DWORD DeCommitTotalFreeThreshold; // 在进程堆中释放(取消提交)之前必须释放的最小总内存大小,以字节为单位。该值是建议性的
DWORD LockPrefixTable; // 使用 LOCK 前缀的地址列表的 RVA
DWORD MaximumAllocationSize; // 最大分配大小,以字节为单位。此成员已过时,仅用于调试目的
DWORD VirtualMemoryThreshold; // 可以从堆段分配的最大块大小,以字节为单位
DWORD ProcessHeapFlags; // 进程堆标志
DWORD ProcessAffinityMask; // 进程关联掩码
WORD CSDVersion; // 服务包版本
WORD DependentLoadFlags; // 操作系统解析模块的静态链接导入时使用的默认加载标志
DWORD EditList; // 保留供系统使用
DWORD SecurityCookie; // 指向由 Visual C++ 或 GS 实现使用的 cookie 的指针
DWORD SEHandlerTable; // SE 处理程序表,图像中每个有效的唯一处理程序的 RVA 排序表的 RVA
DWORD SEHandlerCount; // SE 处理程序表计数,表中唯一处理程序的计数
DWORD GuardCFCheckFunctionPointer; // 存储控制流保护检查函数指针的 RVA
DWORD GuardCFDispatchFunctionPointer; // 存储控制流保护调度函数指针的 RVA
DWORD GuardCFFunctionTable; // 控制流保护函数表
DWORD GuardCFFunctionCount; // 控制流保护函数计数
DWORD GuardFlags; // 控制流保护相关标志
IMAGE_LOAD_CONFIG_CODE_INTEGRITY CodeIntegrity; // 代码完整性
DWORD GuardAddressTakenIatEntryTable; // 存储控制流保护地址取IAT表的 RVA
DWORD GuardAddressTakenIatEntryCount; // 存储控制流保护地址取IAT表 RVA 的计数
DWORD GuardLongJumpTargetTable; // 控制流保护长跳转目标表存储的 RVA
DWORD GuardLongJumpTargetCount; // 控制流保护长跳转目标表存储的 RVA 的计数
DWORD DynamicValueRelocTable; // 动态重定位表的 RVA
DWORD CHPEMetadataPointer; // CHPE元数据指针
DWORD GuardRFFailureRoutine; // 回流保护故障例程的 RVA
DWORD GuardRFFailureRoutineFunctionPointer; // 回流保护故障例程函数指针的 RVA
DWORD DynamicValueRelocTableOffset; // 动态重定位表偏移
WORD DynamicValueRelocTableSection; // 动态重定位表块
WORD Reserved2;
DWORD GuardRFVerifyStackPointerFunctionPointer; // 回流保护验证堆栈指针函数指针的 RVA
DWORD HotPatchTableOffset; // 热补丁表偏移
DWORD Reserved3;
DWORD EnclaveConfigurationPointer; // Enclave 配置指针的 RVA
DWORD VolatileMetadataPointer; // 易失性元数据指针的 RVA
DWORD GuardEHContinuationTable; // EH 延续表的 RVA
DWORD GuardEHContinuationCount; // EH 延续表的 RVA 的计数
} IMAGE_LOAD_CONFIG_DIRECTORY32, *PIMAGE_LOAD_CONFIG_DIRECTORY32;
在代码完整性项CodeIntegrity
,包含了一个IMAGE_LOAD_CONFIG_CODE_INTEGRITY
结构体,
typedef struct _IMAGE_LOAD_CONFIG_CODE_INTEGRITY {
WORD Flags; // 标志位,指示 Code Integrity 信息是否可用的标志
WORD Catalog; // 目录属性,0xFFFF 表示不可用
DWORD CatalogOffset; // 目录偏移
DWORD Reserved; // 保留项
} IMAGE_LOAD_CONFIG_CODE_INTEGRITY, *PIMAGE_LOAD_CONFIG_CODE_INTEGRITY;
绑定导入目录(IMAGE_BOUND_IMPORT_DESCRIPTOR)
该目录指向的是一组IMAGE_BOUND_IMPORT_DESCRIPTOR
结构体和IMAGE_BOUND_FORWARDER_REF
结构体构成的符合结构,IMAGE_BOUND_IMPORT_DESCRIPTOR
结构体描述了时间日期,dll
名称及结构结束后紧跟的IMAGE_BOUND_FORWARDER_REF
数组个数,从此往复,直到为空的IMAGE_BOUND_IMPORT_DESCRIPTOR
结构体出现:
typedef struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD TimeDateStamp; // 时间日期
WORD OffsetModuleName; // 模块名称偏移
WORD NumberOfModuleForwarderRefs; // REF 数组个数
} IMAGE_BOUND_IMPORT_DESCRIPTOR, *PIMAGE_BOUND_IMPORT_DESCRIPTOR;
IMAGE_BOUND_FORWARDER_REF
结构体内容也很简单:
typedef struct _IMAGE_BOUND_FORWARDER_REF {
DWORD TimeDateStamp; // 时间日期
WORD OffsetModuleName; // 名称偏移
WORD Reserved; // 保留项
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
导入地址表(IMAGE_IAT_DIRECTORY)
在导入目录IMAGE_IMPORT_DESCRIPTOR
中,IMAGE_THUNK_DATA
结构数组所指向的IMAGE_IMPORT_BY_NAME
结构集合,便是导入地址的详细列表。
延迟加载导入描述符(IMAGE_DELAY_IMPORT_DIRECTORY)
这些表被添加到图像中以支持应用程序的统一机制,以延迟加载 DLL,直到第一次调用该 DLL
typedef struct _IMAGE_DELAYLOAD_DESCRIPTOR {
union { // 属性
DWORD AllAttributes;
struct {
DWORD RvaBased : 1;
DWORD ReservedAttributes : 31;
} DUMMYSTRUCTNAME;
} Attributes;
DWORD DllNameRVA; // 模块名称
DWORD ModuleHandleRVA; // 模块句柄
DWORD ImportAddressTableRVA; // 延迟导入地址表(PIMAGE_THUNK_DATA)
DWORD ImportNameTableRVA; // 延迟导入名称表 (PIMAGE_THUNK_DATA::AddressOfData)
DWORD BoundImportAddressTableRVA; // 绑定延迟导入表
DWORD UnloadInformationTableRVA; // 卸载延迟导入表
DWORD TimeDateStamp; // 时间日期
} IMAGE_DELAYLOAD_DESCRIPTOR, *PIMAGE_DELAYLOAD_DESCRIPTOR;
COM 运行时描述符(CLICK_IMAGE_COM_DESCRIPTOR_DIRECTORY)
该目录原本是一个空闲目录,但ms.net
发布后,该目录便用于保存.net
信息的最高级信息结构IMAGE_COR20_HEADER
,CLR 元数据存储在此部分中。它用于指示目标文件包含托管代码。元数据的格式没有记录,但可以交给 CLR 接口来处理元数据。:
typedef struct IMAGE_COR20_HEADER
{
// 标头版本控制
DWORD cb;
WORD MajorRuntimeVersion; // 主要运行时版本
WORD MinorRuntimeVersion; // 次要运行时版本
// 符号表和启动信息
IMAGE_DATA_DIRECTORY MetaData; // 元数据目录
DWORD Flags;
// 如果没有设置 COMIMAGE_FLAGS_NATIVE_ENTRYPOINT,EntryPointToken 代表一个托管入口点。
// 如果设置了 COMIMAGE_FLAGS_NATIVE_ENTRYPOINT,EntryPointRVA 代表一个本地入口点的 RVA。
union {
DWORD EntryPointToken;
DWORD EntryPointRVA;
} DUMMYUNIONNAME;
// 绑定信息
IMAGE_DATA_DIRECTORY Resources; // 资源数据目录
IMAGE_DATA_DIRECTORY StrongNameSignature; // 签名数据目录
// 定期修复和绑定信息
IMAGE_DATA_DIRECTORY CodeManagerTable; // 代码管理器表
IMAGE_DATA_DIRECTORY VTableFixups; // 修复表
IMAGE_DATA_DIRECTORY ExportAddressTableJumps; // 导出地址表跳转
// 预编译图像信息(仅供内部使用 - 设置为零)
IMAGE_DATA_DIRECTORY ManagedNativeHeader; // 托管本机标头
} IMAGE_COR20_HEADER, *PIMAGE_COR20_HEADER;
RVA to RAW
PE文件加载到内存时,每个节区都要准确的完成内存地址和文件偏移间的映射,这种映射一般称为RVA to RAW
(RAW
为文件偏移,又称:File Offset
)。
根据IMAGE_SECTION_HEADER
结构体,换算公式如下:
RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData
注:这里的PointerToRawData
和VirtualAddress
(RVA形式存储)是IMAGE_SECTION_HEADER
结构体中代表在磁盘和内存中,同节区起始位置,因为磁盘和内存是一个映射关系,所以两者是不同的。
下面已 test节区为例:
参考资料
https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
代码摘录来源为:Microsoft Platform SDK - winnt.h