Mobile wallpaper 1Mobile wallpaper 2Mobile wallpaper 3Mobile wallpaper 4Mobile wallpaper 5Mobile wallpaper 6
4942 字
25 分钟
PE file struct

目录

  • 指针和虚拟内存

  • PE文件结构

  • 汇编和函数栈帧

简介#

本篇将介绍PE文件结构中重要的属性字段,说明节区和段和页的区别。介绍PE文件的装载。并介绍LIEF库。

PE文件结构#

NOTE

本节的结构可通过在Visual Studio中包含Windows.h来查阅。 例如:IMAGE_DOS_HEADER、IMAGE_OPTIONAL_HEADER64等。 学习时,建议打开一个十六进制编辑器一边查看结构一遍查看字节码 同时,本节均以64位为例。

总览#

PE文件结构示意图

PE文件结构中最重要的内容如下:

  • DOS文件头:用于区别16位程序和x86/64的程序

  • NT头:

    • 文件头:用于说明基本的PE程序属性

    • 可选头:更多的PE程序属性

      • 数据目录表:具体内容见后文DataDirectory

        • 导出表

        • 导入表/延迟导入

        • 重定位表

        • 资源地址

        • 异常表

        • 证书签名

        • 调试信息

        • 全局指针

        • 线程本地存储

  • 节表:描述节区属性

以下按顺序分别介绍

DOS_Header#

DOS指Disk Operating System。是Windows出现之前的PC端主要操作系统,通过命令行界面进行操作。DOS是16位的操作系统

DOS头的主要作用是兼容以往的系统和Windows系统。在DOS系统中运行32/64位程序会自动打印DOSstub(dos存根)中的“这个程序不能在该环境下运行”的字符串。

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;

e9da6cd1-1d20-4a9a-b302-9ea83eddf8af

DOS头具体结构如上。其中重要的值只有e_magic:4D5A作为文件标识(一般也称魔法数字或魔数magic)和e_lfanew即PE头的文件偏移。

NT_Header#

nt头是文件头和可选头的合称,之所以叫NT,是因为区别于DOS,Windows推出的新一代操作系统系列内核架构:Windows NT。NT是New Technology的缩写。

nt头结构如下:

typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

其中,Signature是魔数5045。后方跟随的是20字节的文件头和240字节的可选头

FILE_Header#

文件头说明PE文件的基本属性

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;

1d3fdfe9-0223-413c-a900-f50627b1f809

Machine说明目标机器CPU架构,如果修改该值,程序将不能在正确的平台上运行,因为无法通过该值的检查。 NumberOfSections说明节表中节区的数量,Loader根据这个来解析节表。 TimeDateStamp指文件生成时间戳。 PointerToSymbolTable指向符号表偏移。如果不打开符号加入最终的可执行文件的选项,这里就是0。最终的符号文件为pdb文件。 NumberOfSymbols符号表中符号的数量。同上。 SizeOfOptionalHeader是可选头大小,通常来说PE32为E0字节,PE32+为F0字节。 Characteristics表示其他的文件属性,按位来设置。

Machine可选字段:#

宏定义含义
IMAGE_FILE_MACHINE_UNKNOWN0x0000未知或未指定的机器类型
IMAGE_FILE_MACHINE_TARGET_HOST0x0001表示与宿主机交互(而不是 WoW 客户端),主要用于工具
IMAGE_FILE_MACHINE_I3860x014cIntel 386(x86 架构)
IMAGE_FILE_MACHINE_R30000x0162MIPS R3000,小端序(0x160 表示大端序)
IMAGE_FILE_MACHINE_R40000x0166MIPS R4000,小端序
IMAGE_FILE_MACHINE_R100000x0168MIPS R10000,小端序
IMAGE_FILE_MACHINE_WCEMIPSV20x0169MIPS WCE v2,小端序
IMAGE_FILE_MACHINE_ALPHA0x0184DEC Alpha AXP
IMAGE_FILE_MACHINE_SH30x01a2Hitachi SH3,小端序
IMAGE_FILE_MACHINE_SH3DSP0x01a3Hitachi SH3 DSP
IMAGE_FILE_MACHINE_SH3E0x01a4Hitachi SH3E,小端序
IMAGE_FILE_MACHINE_SH40x01a6Hitachi SH4,小端序
IMAGE_FILE_MACHINE_SH50x01a8Hitachi SH5
IMAGE_FILE_MACHINE_ARM0x01c0ARM 小端序
IMAGE_FILE_MACHINE_THUMB0x01c2ARM Thumb/Thumb-2 小端序
IMAGE_FILE_MACHINE_ARMNT0x01c4ARM Thumb-2 小端序(Windows NT 用)
IMAGE_FILE_MACHINE_AM330x01d3Mitsubishi AM33
IMAGE_FILE_MACHINE_POWERPC0x01F0IBM PowerPC,小端序
IMAGE_FILE_MACHINE_POWERPCFP0x01f1IBM PowerPC 浮点支持
IMAGE_FILE_MACHINE_IA640x0200Intel Itanium (IA-64)
IMAGE_FILE_MACHINE_MIPS160x0266MIPS16
IMAGE_FILE_MACHINE_ALPHA640x0284DEC Alpha 64 位
IMAGE_FILE_MACHINE_MIPSFPU0x0366MIPS 带 FPU
IMAGE_FILE_MACHINE_MIPSFPU160x0466MIPS16 带 FPU
IMAGE_FILE_MACHINE_AXP640x0284Alpha64(等同于 IMAGE_FILE_MACHINE_ALPHA64)
IMAGE_FILE_MACHINE_TRICORE0x0520Infineon TriCore
IMAGE_FILE_MACHINE_CEF0x0CEFCEF(保留/特殊用途)
IMAGE_FILE_MACHINE_EBC0x0EBCEFI Byte Code
IMAGE_FILE_MACHINE_AMD640x8664AMD64(x64 架构)
IMAGE_FILE_MACHINE_M32R0x9041Mitsubishi M32R,小端序
IMAGE_FILE_MACHINE_ARM640xAA64ARM64 小端序
IMAGE_FILE_MACHINE_CEE0xC0EECEE(Common Language Runtime,.NET/托管代码)

Characteristics可选字段:#

宏定义含义
IMAGE_FILE_RELOCS_STRIPPED0x0001文件中已去掉重定位信息(如果加载地址不匹配则无法重定位)。
IMAGE_FILE_EXECUTABLE_IMAGE0x0002文件是可执行映像(没有未解析的外部引用)。
IMAGE_FILE_LINE_NUMS_STRIPPED0x0004已去掉行号信息(调试相关)。
IMAGE_FILE_LOCAL_SYMS_STRIPPED0x0008已去掉本地符号信息。
IMAGE_FILE_AGGRESIVE_WS_TRIM0x0010系统可激进地修剪工作集(很少使用)。
IMAGE_FILE_LARGE_ADDRESS_AWARE0x0020应用程序能处理大于 2GB 的地址空间(常见于 64 位或 /LARGEADDRESSAWARE 编译)。
IMAGE_FILE_BYTES_REVERSED_LO0x0080低位字节顺序反转(历史遗留)。
IMAGE_FILE_32BIT_MACHINE0x0100文件适用于 32 位机器。
IMAGE_FILE_DEBUG_STRIPPED0x0200调试信息已移到单独的 .DBG 文件。
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP0x0400如果映像在可移动介质上,需复制到交换文件再运行。
IMAGE_FILE_NET_RUN_FROM_SWAP0x0800如果映像在网络上,需复制到交换文件再运行。
IMAGE_FILE_SYSTEM0x1000文件是 SYS。
IMAGE_FILE_DLL0x2000文件是 DLL。
IMAGE_FILE_UP_SYSTEM_ONLY0x4000文件只能在单处理器机器上运行。
IMAGE_FILE_BYTES_REVERSED_HI0x8000高位字节顺序反转(历史遗留)。

OPTIONAL_Header#

PE文件由COFF格式演化而来,而COFF格式是用来描述obj文件的(编译过程中间文件或lib静态链接库文件)。COFF格式不能直接运行,不需要运行时信息。PE文件格式为了保持和COFF格式的兼容,将运行时的额外信息放在可选头中。

结构如下:

typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG 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;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;

340dc220-ea81-4d75-8fb4-6d53645142e7

Magic:标识PE文件类型(32/64/rom)。

MajorLinkerVersion / MinorLinkerVersion:表示连接器主版本号和次版本号。

SizeOfCode:表示代码节总大小(比如.text)。

SizeOfInitializedData/SizeOfUninitializedData:分别表示已初始化(比如.data)和未初始化(比如.bss)数据节大小。

AddressOfEntryPoint:程序入口点RVA(相对于映像基地址的偏移)。加载器初始化完成后,从这里开始执行代码。

BaseOfCode:代码节起始RVA。

ImageBase:映像基地址。

SectionAlignment/FileAlignment:分别表示内存和文件中中节的对齐粒度,一般分别为0x1000和0x200 。

MajorOperatingSystemVersion / MinorOperatingSystemVersion:期望的操作系统版本号。

MajorImageVersion / MinorImageVersion:映像版本号,由开发者自定义。

MajorSubsystemVersion / MinorSubsystemVersion:子系统版本(子系统见Subsystem字段)。

Win32VersionValue:保留值。

SizeOfImage:映像总大小,按SectionAlignment对齐。

SizeOfHeaders:文件中头部(DOS,NT,Sections)的大小,按FileAlignment对齐。

CheckSum:系统校验和。

Subsystem:子系统类型:GUI,CUI,CE GUI,EFI。

DllCharacteristics:DLL特性标志位,具体内容见后表。

SizeOfStackReserve / SizeOfStackCommit:栈保留大小(默认栈的虚拟内存大小)和栈提交大小(默认栈的物理内存大小)。

SizeOfHeapReserve / SizeOfHeapCommit:堆保留大小和堆提交大小。

LoaderFlags:保留。

NumberOfRvaAndSizes:数据目录数组的长度,一般为16。

DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]:数据目录数组,此处内容较为重要,具体内容见后。

Magic可选字段#

用途
IMAGE_NT_OPTIONAL_HDR32_MAGIC0x10B表示 PE32(32位可执行文件)
IMAGE_NT_OPTIONAL_HDR64_MAGIC0x20B表示 PE32+(64位可执行文件)
IMAGE_ROM_OPTIONAL_HDR_MAGIC0x107表示 ROM 映像(嵌入式/特殊用途)

Subsystem可选字段#

用途
IMAGE_SUBSYSTEM_UNKNOWN0未知子系统
IMAGE_SUBSYSTEM_NATIVE1原生映像,不依赖子系统(如内核驱动)
IMAGE_SUBSYSTEM_WINDOWS_GUI2Windows GUI 程序(图形界面)
IMAGE_SUBSYSTEM_WINDOWS_CUI3Windows 控制台程序(命令行)
IMAGE_SUBSYSTEM_OS2_CUI5OS/2 控制台子系统(历史遗留)
IMAGE_SUBSYSTEM_POSIX_CUI7POSIX 控制台子系统(历史遗留)
IMAGE_SUBSYSTEM_NATIVE_WINDOWS8原生 Win9x 驱动
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI9Windows CE GUI 程序
IMAGE_SUBSYSTEM_EFI_APPLICATION10EFI 应用程序
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER11EFI 启动服务驱动
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER12EFI 运行时驱动
IMAGE_SUBSYSTEM_EFI_ROM13EFI ROM 映像
IMAGE_SUBSYSTEM_XBOX14Xbox 程序
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION16Windows Boot 应用程序
IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG17Xbox Code Catalog 映像

DllCharacteristics可选字段#

用途
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA0x0020支持高熵 64 位地址空间(ASLR 更强)
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE0x0040映像可重定位(支持 ASLR)
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY0x0080强制代码完整性检查
IMAGE_DLLCHARACTERISTICS_NX_COMPAT0x0100支持 NX(DEP,禁止执行数据页)
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION0x0200不使用应用程序隔离(Manifest 隔离)
IMAGE_DLLCHARACTERISTICS_NO_SEH0x0400不使用 SEH(结构化异常处理)
IMAGE_DLLCHARACTERISTICS_NO_BIND0x0800不允许绑定导入表
IMAGE_DLLCHARACTERISTICS_APPCONTAINER0x1000要在 AppContainer 沙箱中运行
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER0x2000WDM 驱动程序
IMAGE_DLLCHARACTERISTICS_GUARD_CF0x4000支持 Control Flow Guard(CFG)安全特性
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE0x8000终端服务器兼容

Data Directory 索引#

索引用途
IMAGE_DIRECTORY_ENTRY_EXPORT0导出表(函数/变量导出信息)
IMAGE_DIRECTORY_ENTRY_IMPORT1导入表(DLL 引用信息)
IMAGE_DIRECTORY_ENTRY_RESOURCE2资源表(图标、字符串、对话框等)
IMAGE_DIRECTORY_ENTRY_EXCEPTION3异常表(x64 异常处理信息)
IMAGE_DIRECTORY_ENTRY_SECURITY4安全目录(数字签名,文件偏移寻址)
IMAGE_DIRECTORY_ENTRY_BASERELOC5重定位表(地址修正信息)
IMAGE_DIRECTORY_ENTRY_DEBUG6调试目录(PDB 路径、调试信息)
IMAGE_DIRECTORY_ENTRY_ARCHITECTURE7架构专用数据(保留/弃用)
IMAGE_DIRECTORY_ENTRY_GLOBALPTR8全局指针(IA-64 使用)
IMAGE_DIRECTORY_ENTRY_TLS9TLS 表(线程本地存储回调)
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG10加载配置表(安全策略、SEH 表等)
IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT11绑定导入表(优化加载速度)
IMAGE_DIRECTORY_ENTRY_IAT12导入地址表(运行时函数指针表)
IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT13延迟导入表(首次调用时才加载 DLL)
IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR14COM/.NET 目录(CLR 头,托管代码支持)

Section Header#

section header是节表中的项,其大小由FILE_Header的NumberOfSections指定。

其结构如下:

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
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;

b48045f8-4ea8-4f9e-908d-3dc0be881432

Name:是一个最多8字节的节区名称,一般由.开头,后方填充0。

Misc:如果是目标文件,则为PhysicalAddress,若是PE文件,则是VirtualSize,表示节在内存中需要的大小。

VirtualAddress:表示节在内存中的RVA。

SizeOfRawData:节在文件中占用的字节数。

PointerToRawData:节在文件中的FOV(文件偏移)。

PointerToRelocations / NumberOfRelocations:obj文件中用来描述重定位信息,在PE文件中为0。

PointerToLinenumbers / NumberOfLinenumbers:PE文件中一般为0,具体调试信息在pdb文件中。

Characteristics节属性

Characteristics可选字段#

宏定义含义
IMAGE_SCN_TYPE_REG0x00000000保留字段
IMAGE_SCN_TYPE_DSECT0x00000001保留字段
IMAGE_SCN_TYPE_NOLOAD0x00000002保留字段,不加载该节
IMAGE_SCN_TYPE_GROUP0x00000004保留字段
IMAGE_SCN_TYPE_NO_PAD0x00000008保留字段
IMAGE_SCN_TYPE_COPY0x00000010保留字段
IMAGE_SCN_CNT_CODE0x00000020节包含代码(通常是 .text
IMAGE_SCN_CNT_INITIALIZED_DATA0x00000040节包含已初始化数据(如 .data
IMAGE_SCN_CNT_UNINITIALIZED_DATA0x00000080节包含未初始化数据(如 .bss
IMAGE_SCN_LNK_OTHER0x00000100保留字段
IMAGE_SCN_LNK_INFO0x00000200节包含注释或其他信息
IMAGE_SCN_LNK_REMOVE0x00000800节不会进入最终映像
IMAGE_SCN_LNK_COMDAT0x00001000节是 COMDAT(公共数据,链接器合并重复符号)
IMAGE_SCN_NO_DEFER_SPEC_EXC0x00004000重置 TLB 中的推测异常处理位
IMAGE_SCN_GPREL / IMAGE_SCN_MEM_FARDATA0x00008000节内容可通过 GP(全局指针)访问,常见于 MIPS/IA-64
IMAGE_SCN_MEM_PURGEABLE / IMAGE_SCN_MEM_16BIT0x00020000保留字段
IMAGE_SCN_MEM_LOCKED0x00040000保留字段
IMAGE_SCN_MEM_PRELOAD0x00080000保留字段
IMAGE_SCN_ALIGN_1BYTES0x001000001 字节对齐
IMAGE_SCN_ALIGN_2BYTES0x002000002 字节对齐
IMAGE_SCN_ALIGN_4BYTES0x003000004 字节对齐
IMAGE_SCN_ALIGN_8BYTES0x004000008 字节对齐
IMAGE_SCN_ALIGN_16BYTES0x0050000016 字节对齐(默认)
IMAGE_SCN_ALIGN_32BYTES0x0060000032 字节对齐
IMAGE_SCN_ALIGN_64BYTES0x0070000064 字节对齐
IMAGE_SCN_ALIGN_128BYTES0x00800000128 字节对齐
IMAGE_SCN_ALIGN_256BYTES0x00900000256 字节对齐
IMAGE_SCN_ALIGN_512BYTES0x00A00000512 字节对齐
IMAGE_SCN_ALIGN_1024BYTES0x00B000001024 字节对齐
IMAGE_SCN_ALIGN_2048BYTES0x00C000002048 字节对齐
IMAGE_SCN_ALIGN_4096BYTES0x00D000004096 字节对齐
IMAGE_SCN_ALIGN_8192BYTES0x00E000008192 字节对齐
IMAGE_SCN_ALIGN_MASK0x00F00000对齐掩码
IMAGE_SCN_LNK_NRELOC_OVFL0x01000000节包含扩展重定位
IMAGE_SCN_MEM_DISCARDABLE0x02000000节可被丢弃(如资源节加载后可释放)
IMAGE_SCN_MEM_NOT_CACHED0x04000000节不可缓存
IMAGE_SCN_MEM_NOT_PAGED0x08000000节不可分页
IMAGE_SCN_MEM_SHARED0x10000000节可共享
IMAGE_SCN_MEM_EXECUTE0x20000000节可执行
IMAGE_SCN_MEM_READ0x40000000节可读
IMAGE_SCN_MEM_WRITE0x80000000节可写
IMAGE_SCN_SCALE_INDEX0x00000001TLS 范围标志

DataDirectory#

data directory是一个数组,存放了许多重要内容。此处较为重要的为导入表,导出表,重定位表。它们是PE加载器加载PE文件到内存时几乎必须的内容。其中,导入表指明了本PE文件使用了哪些其他PE文件的函数,导出表指明了其他PE文件可以使用本PE文件的哪些函数。重定位表指明了在不能加载到默认映像基地址时,该如何修正PE文件中的绝对偏移。

DIRECTORY_ENTRY_IMPORT#

导入表记录了使用的外部函数。其每一项叫导入描述符IMPORT_DESCRIPTOR,每一个描述符都对应一个dll的所有函数。

描述符结构如下:

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (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;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

59babe3c-a60e-463a-b5d0-bdaba4a1058d

OriginalFirstThunk:如果是0,则说明导入表结束。否则指向该描述符描述的dll的INT表(用以区分每个函数是通过名称还是通过序号导入的)

TimeDateStamp:0

ForwarderChain:转发引用标志,如果有,则为非零,表示INT表和IAT表第x个函数为第一个向前导出函数

Name:指向DLL名称的RVA

FirstThunk:指向IAT表(导入地址表),内容是每个函数地址

INT表和IAT表的结构相同,但是意义不同,其结构如下:

typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString; // PBYTE
ULONGLONG Function; // PDWORD
ULONGLONG Ordinal;
ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA64;
typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;

ForwarderString:表示将该函数实现转发给另一个DLL。

Function:表示函数真实地址。

Ordinal:如果最高位是1,之后的位存放导出序号

AddressOfData:如果最高位为0,则是指向文件中保存函数名称的结构的RVA

AddressOfData结构如下:

typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

其中Hint为导出表序号,Name为名称。由于名称长度不固定,因此AddressOfData是RVA指针。

虽然它们的结构都是一个ULONGLONG,但是作为INT表,用来记录想要查询的函数的位置,只会是Ordinal和AddressOfData,而IAT表原值不重要,启动后,始终会覆盖为Function,即真实函数地址。具体的加载流程,即通过导入描述符设置真实地址的逻辑如下:

  • 通过可选头得到导入描述符地址。

  • 查看该导入描述符的Name字段得到dll名称

    • 进入OriginalFirstThunk字段分析IMAGE_THUNK_DATA64结构数组

      • 如果第一位为1,获取剩下位作为序号,寻找该DLL的导出表对应序号的函数地址,将该值填入FirstThunk字段中对应位置上

      • 如果不为1,获取剩下位作为RVA寻找函数名称结构,先找该DLL导出表的根据Hint作为序号的函数,如果没找到,通过二分查找用名称找,最后将地址填入FirstThunk对应位置上

      • 每次执行完这个操作,OriginalFirstThunk和FirstThunk指针同时+1,表示进行下一个函数的搜索,直到它们的结构为0

  • 结束一个导入描述符后,指针+1,对下一个导入描述符做同样的操作,直到接结构全0

注意:运行时,所有使用DLL函数的地方,实际跳转均为IAT表对应位置,当IAT表填充为正确地址后,实际跳转才能被正常解析

DIRECTORY_ENTRY_EXPORT#

导出表用于其他PE文件导入本PE文件的函数。和导入表不同,导入表是导入描述符的数组,而导出表是一个单独的结构。

其结构如下:

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

Characteristics:保留,全0

TimeDateStamp:时间戳

MajorVersion/MinorVersion:版本号

Name:指向该DLL名称的RVA

Base:起始序列号

NumberOfFunctions:AddressOfFunctions中元素总数

NumberOfNames:AddressOfNames中元素总数

AddressOfFunctions:EAT,函数地址RVA表

AddressOfNames:ENT,函数名称表

AddressOfNameOrdinals:EOT,序号索引表,以序号作为index,值为EAT的索引。

当其他PE文件想要寻找该PE文件的某个函数地址时:

  • 通过文件头获取导出表地址

    • 若是通过名称查询,找到AddressOfNames中该函数名称的序号,将该序号传入AddressOfNameOrdinals,取对应函数地址表的index,在AddressOfFunctions的对应index位获取地址
    • 若是通过序号查询,让序号减去Base,得到AddressOfFunctions偏移,然后在对应位获取地址
  • 此处获取的地址是RVA,需要加上ImageBase才是真实地址

DIRECTORY_ENTRY_BASERELOC#

重定位表是为了防止不能在默认位置加载镜像而导致绝对地址报错的结构。它是块链,每个重定位块负责每个4KB中所有需要重定位的内容。

重定位块结构如下:

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

VirtualAddress:该块负责的页面的起始RVA

SizeOfBlock:本块的总大小。下一块的地址是该块起始地址加SizeOfBlock。该块负责的重定位项(每个需重定位的地址)是(SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2

TypeOffset:是重定位项数组,每位都是一个WORD类型的值

typedef struct _IMAGE_RELOC_ENTRY {//猜的结构,VS里面没找到
WORD Type : 4;
WORD Offset : 12;
} IMAGE_RELOC_ENTRY, *PIMAGE_RELOC_ENTRY;

其中Type是对应等待修正的地址的类型,可用值如下:

宏定义名称数值适用架构修复逻辑与说明
ABSOLUTE0所有忽略。不做任何操作,通常用于块末尾的填充对齐。
HIGH1部分 (如 MIPS)高16位修复。将基址差值(Delta)的高16位加到目标位置。
LOW2部分 (如 MIPS)低16位修复。将基址差值的低16位加到目标位置。
HIGHLOW3x86 (32位)32位全修复。32位程序最核心的类型,修复整个 32 位绝对地址。
HIGHADJ4特殊 (如 RISC)带进位的高位修复。修复高16位,但需考虑低16位的进位。占用两个条目空间。
MIPS_JMPADDR5MIPS跳转地址修复。针对 MIPS 架构的 JJAL 指令进行地址修正。
ARM_MOV325ARMMOV32 修复。修复 ARM 模式下由 MOV 指令对加载的 32 位常量。
THUMB_MOV327ARM ThumbThumb MOV32 修复。针对 Thumb 指令集编码的 32 位立即数修复。
IA64_IMM649IA64 (安腾)64位立即数修复。修复 IA64 架构指令束中的 64 位值。
DIR6410x64 (64位)64位全修复。64位程序(Win11/10主流)的核心类型,修复 8 字节绝对地址。

一般来说都是DIR64。

offset就是相对于本块VirtualAddress的偏移。指明了对应的需要重定位的地址。

重定位流程大致如下:

  • 从文件头中找到重定位表首地址

  • 取VirtualAddress

    • 进入TypeOffset表项,对其中每一项,计算ImageBase+VirtualAddress+Offset作为目标地址。并查看Type,一般为DIR64,说明目标位置绝对地址长度为8字节。

    • 将真实基址减去PE头中的基址作为差值,加到目标地址上作为重定位地址。

    • 继续循环,直到循环次数为(SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2说明该块修复完毕。

  • 将当前重定位块首地址+SizeOfBlock,转到下一个重定位块,循环,直到结构全0

PE文件的基本加载流程#

通过上面的结构可以看出,部分地址是FOV,部分地址是RVA。FOV是某个结构在文件中的偏移,RVA是某个结构在内存中基于ImageBase的偏移。PE文件的加载,就是将文件内容“映射”到虚拟内存,然后初始化重要结构,比如IAT,重定位表。

其中,映射的目的在于:文件一般以扇区对齐(FileAlignment),内存则是按页对齐,所有的属性都是页的属性,为了防止代码段,数据段放到一个页中引起属性冲突导致的安全性下降,必须要通过映射将节区分离。

段和节区在PE文件中是类似的东西。可以说几乎一样,节区是用于给编译器和链接器链接的。而段是用来给PE加载器执行时设置权限的。而页是实际的权限载体,加载器根据段的权限设置页的权限。

大致来说PE加载器执行程序的流程如下:(以下为简化步骤,只有极少代码可以仅通过该操作实现执行)

  • 映射节区

  • 修复IAT

  • 执行重定位

  • 跳转到入口点

可以尝试手动写一个PE加载器来实现这个流程,体会感觉。

LIEF库的使用#

LIEF是支持多种语言的PE文件解析库,可以很方便地加载/修改PE文件内容。

具体API见:PE — LIEF Documentation

PE file struct
https://pri87.vip/posts/pe-file-struct/
作者
pRism
发布于
2025-12-15
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时

封面
Sample Song
Sample Artist
封面
Sample Song
Sample Artist
0:00 / 0:00