PE文件格式

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_magice_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_HEADERS32PIMAGE_NT_HEADERS64,两者的不变的是SignatureIMAGE_FILE_HEADER;区别在于IMAGE_OPTIONAL_HEADER32IMAGE_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结构体中有四个重要成员:MachineNumberOfSectionsSizeOfOptionalHeaderCharacteristics

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结构体中需要关注的成员是:MagicAddressOfEntryPointImageBaseSectionAlignmentFileAlignmentSizeOfImageSizeOfHeadersSubsystemDllCharacteristicsNumberOfRvaAndSizesDataDirectory

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偏移0x14WORD型)得出,其实就是紧接着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文件中仅有一个用来说明库EATIMAGE_EXPORT_DIRECTORY结构体。

IMAGE_OPTIONAL_HEADERIMAGE_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是导出函数地址表的基数,通过函数地址表的序数减去基数就是函数地址表的索引,仔细观察整个数组可以发现,函数名称表存在两个数组:AddressOfNamesAddressOfNameOrdinals,而函数地址表只存在一个数组: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;

OriginalFirstThunkFirstThunk实际上都是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结构,其中包含了HintName

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则为0OffsetToData则是资源数据偏移量,如果目录是子目录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

让我们来理一理这个资源目录是如何定位资源数据入口的:

  1. 资源目录中会包含若干个不同类型(Name&ID)的目录入口(ENTRY);
  2. 每一个同类型(Name&ID)的目录入口(ENTRY)会存在一个目录(DIRECTORY)用来描述当前类型的资源信息;
  3. 而这个目录(DIRECTORY)往往会有多个目录入口(ENTRY),用于指定同类型的多个资源信息;
  4. 在单个资源目录入口(ENTRY)中,会存在一个目录(DIRECTORY),用来描述当前条目资源的信息;
  5. 而这个目录(DIRECTORY)中仅仅会包含一个目录入口(ENTRY),用来指定当前条目数据入口的信息;
  6. 最终由这个目录入口(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)/ 2SizeOfBlock指定了当前重定位块的总大小,减去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;

针对Typewinnt.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) 提供直接的PECOFF支持。TLSWindows支持的特殊存储类,其中数据对象不是自动(堆栈)变量,而是运行代码的每个单独线程的本地。因此,每个线程可以为使用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 RAWRAW为文件偏移,又称:File Offset)。

根据IMAGE_SECTION_HEADER结构体,换算公式如下:

RAW - PointerToRawData = RVA - VirtualAddress
RAW = RVA - VirtualAddress + PointerToRawData

注:这里的PointerToRawDataVirtualAddress(RVA形式存储)是IMAGE_SECTION_HEADER结构体中代表在磁盘和内存中,同节区起始位置,因为磁盘和内存是一个映射关系,所以两者是不同的。

下面已 test节区为例:

参考资料

https://docs.microsoft.com/en-us/windows/win32/debug/pe-format

https://docs.microsoft.com/zh-cn/windows/win32/api/winnt/

代码摘录来源为:Microsoft Platform SDK - winnt.h