PE格式解析

PE格式解析

注意点

本文主要参考:PE可执行文件格式详解 - 知乎

可以直接看winnt.h,里面有PE的定义.这里以32位为主,因为这个知乎文章就是以32位为主,因为这位博主都有翻译就不直接复制winnt.h的结构体了.所以和实际的结构体是有区别的,需要区分.

实际上更建议看这位博主文章,非常好,非常细,我则就是他文章的简化方便我自己理解罢了.

不过我自己用010editor查看了实际的二进制文件,所以也加了些不影响理解的小东西.

这是源码,建议用Mingw/gcc生成,微软的会去符号:

void foo(){}

void fuck(){}

int a = 0xaaaa;

int main() {
	int b = 0xbbbb;
	foo();
	return 0;
}

事前须知

地址

  • 虚拟内存地址(Virtual Address, VA)PE文件中的指令被装入内存后的地址。

  • 相对虚拟内存地址(Reverse Virtual Address, RVA相对虚拟地址是内存地址相对于映射基址的偏移量。

  • 文件偏移地址(File Offset Address, FOA)数据在PE文件中的地址叫文件偏移地址,这是文件在磁盘上存放时相对于文件开头的偏移。

  • 装载基址(Image base)PE装入内存时的基地址。默认情况下,EXE文件在内存中的基地址时0x00400000, DLL文件是0x10000000。这些位置可以通过修改编译选项更改。如果已经被占用,则操作系统会选择一个新的地址,无论选没选择,这个转入后的地址都是VA.

    VA = Image base + RVA

    多余的概念特别是链接相关的可以看程序员的自我修养.

PE总结构

ai生成的

PE 文件结构
├─ DOS 头 (IMAGE_DOS_HEADER)
│  ├─ e_magic: DOS 标识(固定为 0x5A4D,即 "MZ")
│  ├─ e_lfanew: 指向 NT 头的文件偏移量(核心字段,用于定位 PE 真正结构)
│  └─ 其他 DOS 兼容字段(如 e_cblp、e_cp 等,现代系统加载时基本忽略)
│
├─ NT 头 (IMAGE_NT_HEADERS)
│  ├─ Signature: PE 标识(固定为 0x00004550,即 "PE00",32/64 位通用)
│  │
│  ├─ 标准头 (IMAGE_FILE_HEADER)
│  │  ├─ Machine: 目标机器架构(如 0x014C=x86、0x8664=x64)
│  │  ├─ NumberOfSections: 节的数量
│  │  ├─ TimeDateStamp: 文件创建时间戳
│  │  ├─ PointerToSymbolTable: 指向符号表的偏移(调试用,通常为 0)
│  │  ├─ NumberOfSymbols: 符号表中的符号数量(调试用,通常为 0)
│  │  ├─ SizeOfOptionalHeader: 可选头的大小
│  │  └─ Characteristics: 文件属性(如 0x2000=DLL、0x0002=可执行文件)
│  │
│  └─ 可选头 (IMAGE_OPTIONAL_HEADER)
│     ├─ 基础信息(32/64 位通用)
│     │  ├─ Magic: 格式标识(0x10B=PE32、0x20B=PE32+)
│     │  ├─ MajorLinkerVersion/MinorLinkerVersion: 链接器版本
│     │  ├─ SizeOfCode: 代码节总大小
│     │  ├─ SizeOfInitializedData: 已初始化数据节总大小
│     │  ├─ SizeOfUninitializedData: 未初始化数据节总大小
│     │  ├─ AddressOfEntryPoint: 程序执行入口 RVA
│     │  ├─ BaseOfCode: 代码节起始 RVA(PE32+ 无此字段)
│     │  └─ BaseOfData: 数据节起始 RVA(PE32+ 无此字段)
│     │
│     ├─ 内存布局信息
│     │  ├─ ImageBase: 建议加载地址(PE32=DWORD、PE32+=QWORD)
│     │  ├─ SectionAlignment: 内存中节的对齐粒度
│     │  ├─ FileAlignment: 磁盘中节的对齐粒度
│     │  ├─ SizeOfImage: 内存中 PE 镜像总大小
│     │  └─ SizeOfHeaders: 所有头(含节表)的总大小
│     │
│     ├─ 版本与兼容性
│     │  ├─ MajorOperatingSystemVersion/MinorOperatingSystemVersion: 最低系统版本
│     │  ├─ MajorImageVersion/MinorImageVersion: 程序自身版本
│     │  ├─ MajorSubsystemVersion/MinorSubsystemVersion: 所需子系统版本
│     │  └─ Win32VersionValue: 保留字段(通常为 0)
│     │
│     ├─ 运行特性
│     │  ├─ CheckSum: 文件校验和(防篡改)
│     │  ├─ Subsystem: 运行子系统(0x02=GUI、0x03=CUI)
│     │  ├─ DllCharacteristics: DLL 特性(如 ASLR 兼容、SEH 支持)
│     │  └─ LoaderFlags: 调试相关保留字段(通常为 0)
│     │
│     ├─ 栈与堆配置
│     │  ├─ SizeOfStackReserve/SizeOfStackCommit: 线程栈保留/提交大小
│     │  └─ SizeOfHeapReserve/SizeOfHeapCommit: 默认堆保留/提交大小
│     │
│     └─ 数据目录(核心扩展信息)
│        ├─ NumberOfRvaAndSizes: 数据目录条目数(固定为 16)
│        └─ DataDirectory[16]: 16 个数据块描述(每项含 VirtualAddress 和 Size)
│           ├─ 第 0 项:导出表(Export Table)
│           ├─ 第 1 项:导入表(Import Table)
│           ├─ 第 2 项:资源表(Resource Table)
│           ├─ 第 3 项:异常表(Exception Table)
│           └─ 其他:安全表、重定位表、调试表等(共 16 项)
│
├─ 节表 (IMAGE_SECTION_HEADER 数组)
│  ├─ 数量 = IMAGE_FILE_HEADER.NumberOfSections
│  └─ 每个节表项包含:
│     ├─ Name: 节名称(如 ".text"、".data",8 字节)
│     ├─ VirtualSize: 节在内存中的实际大小
│     ├─ VirtualAddress: 节的起始 RVA
│     ├─ SizeOfRawData: 节在磁盘上的大小(按 FileAlignment 对齐)
│     ├─ PointerToRawData: 节在磁盘上的起始偏移
│     ├─ PointerToRelocations: 重定位信息偏移(通常为 0)
│     ├─ PointerToLinenumbers: 行号信息偏移(调试用,通常为 0)
│     ├─ NumberOfRelocations: 重定位项数量(通常为 0)
│     ├─ NumberOfLinenumbers: 行号项数量(通常为 0)
│     └─ Characteristics: 节属性(如 0x60000020=代码节、0xC0000040=数据节)
│
└─ 节数据(实际代码/数据存储区)
   ├─ 数量 = 节表中的节数量
   └─ 常见节类型:
      ├─ .text: 可执行代码(对应代码节)
      ├─ .data: 已初始化数据(全局变量、静态变量)
      ├─ .bss: 未初始化数据(磁盘上不占空间,内存中分配)
      ├─ .rdata: 只读数据(如字符串常量、常量数据)
      ├─ .rsrc: 资源数据(图标、对话框、字符串等)
      └─ .reloc: 重定位信息(ImageBase 被占用时修正地址)

PE

DOS头(可以统称DOS Stub)

IMAGE_DOS_HEADER {
    WORD   e_magic;                // +0000h   -   EXE标志,“MZ”
    WORD   e_cblp;                 // +0002h   -   最后(部分)页中的字节数
    WORD   e_cp;                   // +0004h   -   文件中的全部和部分页数
    WORD   e_crlc;                 // +0006h   -   重定位表中的指针数
    WORD   e_cparhdr;              // +0008h   -   头部尺寸,以段落为单位
    WORD   e_minalloc;             // +000ah   -   所需的最小附加段
    WORD   e_maxalloc;             // +000ch   -   所需的最大附加段
    WORD   e_ss;                   // +000eh   -   初始的SS值(相对偏移量)
    WORD   e_sp;                   // +0010h   -   初始的SP值
    WORD   e_csum;                 // +0012h   -   补码校验值
    WORD   e_ip;                   // +0014h   -   初始的IP值
    WORD   e_cs;                   // +0016h   -   初始的CS值
    WORD   e_lfarlc;               // +0018h   -   重定位表的字节偏移量
    WORD   e_ovno;                 // +001ah   -   覆盖号
    WORD   e_res[4];               // +001ch   -   保留字00
    WORD   e_oemid;                // +0024h   -   OEM标识符
    WORD   e_oeminfo;              // +0026h   -   OEM信息
    WORD   e_res2[10];             // +0028h   -   保留字
    LONG   e_lfanew;               // +003ch   -   PE头相对于文件的偏移地址
  }

其中有用的就是e_magice_lfanew.

DOS头下面是DOS stub,存放data(用来在dos环境运行时会输出的话,可以用来整活),如果用微软工具链生成里面会用个加密数据,存放相应版本号.

PE头IMAGE_NT_HEADERS

IMAGE_NT_HEADERS {
    DWORD Signature;                      // +0000h   -   PE文件标识,“PE\0\0”
    IMAGE_FILE_HEADER FileHeader;                   // +0004h   -   PE标准头
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;         // +0018h   -   PE扩展头,这里32位和64位不一样
}

广义的头,含有标准头和扩展头(或者叫可选头)

标准PE头IMAGE_FILE_HEADER

IMAGE_FILE_HEADER {
    WORD    Machine;                             // +0004h   -   运行平台
    WORD    NumberOfSections;                    // +0006h   -   PE中节的数量
    DWORD   TimeDateStamp;                       // +0008h   -   文件创建日期和时间
    DWORD   PointerToSymbolTable;                // +000ch   -   指向符号表
    DWORD   NumberOfSymbols;                     // +0010h   -   符号表中的符号数量
    WORD    SizeOfOptionalHeader;                // +0014h   -   扩展头结构的长度
    WORD    Characteristics;                     // +0016h   -   文件属性,用来区分dll和pe,还有七七八八
}

扩展PE头IMAGE_OPTIONAL_HEADER

IMAGE_OPTIONAL_HEADER {
    WORD    Magic;                                 // +0018h   -   魔术字107h = ROM Image,10bh = PE32, 20bh = PE32+(就是64位)
    BYTE    MajorLinkerVersion;                    // +001ah   -   链接器主版本号
    BYTE    MinorLinkerVersion;                    // +001bh   -   链接器次版本号
    DWORD   SizeOfCode;                            // +001ch   -   所有含代码的节的总大小
    DWORD   SizeOfInitializedData;                 // +0020h   -   所有含已初始化数据的节的总大小(如.data,.rodata)
    DWORD   SizeOfUninitializedData;               // +0024h   -   所有含未初始化数据的节的大小(如.bss)
    DWORD   AddressOfEntryPoint;                   // +0028h   -   程序执行入口RVA
    DWORD   BaseOfCode;                            // +002ch   -   代码的节的起始RVA
    DWORD   BaseOfData;                            // +0030h   -   数据的节的起始RVA
    DWORD   ImageBase;                             // +0034h   -   程序的建议装载地址
    DWORD   SectionAlignment;                      // +0038h   -   内存中的节的对齐粒度
    DWORD   FileAlignment;                         // +003ch   -   文件中的节的对齐粒度
    WORD    MajorOperatingSystemVersion;           // +0040h   -   操作系统版本号
    WORD    MinorOperatingSystemVersion;           // +0042h   -   
    WORD    MajorImageVersion;                     // +0044h   -   该PE的版本号
    WORD    MinorImageVersion;                     // +0046h   -   
    WORD    MajorSubsystemVersion;                 // +0048h   -   所需子系统的版本号
    WORD    MinorSubsystemVersion;                 // +004ah   -   
    DWORD   Win32VersionValue;                     // +004ch   -   未用
    DWORD   SizeOfImage;                           // +0050h   -   内存中的整个PE映象尺寸
    DWORD   SizeOfHeaders;                         // +0054h   -   所有头+节表的大小
    DWORD   CheckSum;                              // +0058h   -   校验和,不符合加载器会拒绝加载
    WORD    Subsystem;                             // +005ch   -   文件的子系统
    WORD    DllCharacteristics;                    // +005eh   -   DLL文件特性
    DWORD   SizeOfStackReserve;                    // +0060h   -   初始化时的栈大小
    DWORD   SizeOfStackCommit;                     // +0064h   -   初始化时实际提交的栈大小
    DWORD   SizeOfHeapReserve;                     // +0068h   -   初始化时保留的堆大小
    DWORD   SizeOfHeapCommit;                      // +006ch   -   初始化时实际提交的堆大小
    DWORD   LoaderFlags;                           // +0070h   -   与调试有关
    DWORD   NumberOfRvaAndSizes;                   // +0074h   -   下面的数据目录结构的项目数量
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];     // 0078h   -   数据目录,IMAGE_NUMBEROF_DIRECTORY_ENTRIES为16
}

数据目录项IMAGE_DATA_DIRECTORY

数据目录数组,包含 16 个 IMAGE_DATA_DIRECTORY 结构,每个结构描述一个关键数据块(如导入表、导出表、资源表等)。

IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;                 // +0000h   -   数据的起始RVA
    DWORD   Size;                           // +0004h   -   数据块的长度
}

16个分别为:

  1. 导出表地址和大小
  2. 导入表地址和大小
  3. 资源表地址和大小
  4. 异常表地址和大小
  5. 属性证书数据地址和大小
  6. 基地址重定位表地址和大小
  7. 调试信息地址和大小
  8. 预留,数据都为0
  9. 指向全局指针寄存器的值
  10. 线程局部存储地址和大小
  11. 加载配置表地址和大小
  12. 绑定导入表地址和大小
  13. 导入函数地址表地址和大小
  14. 延迟导入表地址和大小
  15. CLR运行时头部数据地址和大小
  16. 系统保留

节表头IMAGE_SECTION_HEADER

IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];   // +0000h   -   8个字节节名,如.text,.data这些
    union {
            DWORD   PhysicalAddress;         //物理地址(仅在某些系统中使用,现代 PE 文件中很少用到)
            DWORD   VirtualSize;             //节在内存中的实际大小(未按内存对齐粒度扩展前的真实大小)
    } Misc;                                  // +0008h   -   节区的尺寸
    DWORD   VirtualAddress;                  // +000ch   -   节区的RVA地址
    DWORD   SizeOfRawData;                   // +0010h   -   节在磁盘文件中的大小(按 FileAlignment 对齐后的尺寸)
    DWORD   PointerToRawData;                // +0014h   -   节在磁盘文件中的起始偏移量(从文件开头计算)
    DWORD   PointerToRelocations;            // +0018h   -   在OBJ文件中使用,指向节的重定位信息在文件中的偏移量
    DWORD   PointerToLinenumbers;            // +001ch   -   行号表的位置(供调试用)
    WORD    NumberOfRelocations;             // +0020h   -   在OBJ文件中使用,节的重定位项数量
    WORD    NumberOfLinenumbers;             // +0022h   -   行号表中行号的数量
    DWORD   Characteristics;                 // +0024h   -   节的属性,可读可写可执行,可丢弃
}

节(或者说块,和上面的数据目录项有重合):

  • .text
  • .data
  • .rdata
  • idata:导入表
  • edata:导出表
  • rsrc:资源,比如图标菜单等
  • .bss:现在少用了,往往是data段扩大到可以存放未初始化变量
  • .crt
  • .tls
  • .reloc:重定位表
  • .sdata
  • .srdata
  • .pdata:异常表
  • .debug:obj用的
  • .drectve:obj用的
  • .didat:非Release下能找到

在编译的时候可以用编译器指令修改节的情况,比如用#pragma指令

各类表

重定位表

[数据目录表项] → 指向重定位表起始地址
  ↓
[重定位块1]
  VirtualAddress: 0x1000(对应页:默认基址 + 0x1000)
  SizeOfBlock: 0x10(包含 (0x10-8)/2 = 4 个项)
  [重定位项1]:0x3200 → 类型0x3,偏移0x200 → 需调整地址:默认基址+0x1000+0x200
  [重定位项2]:0x3300 → 类型0x3,偏移0x300 → 需调整地址:默认基址+0x1000+0x300
  [重定位项3]:0x3400 → ...
  [重定位项4]:0x0000 → 无效项(块结束)
[重定位块2]
  VirtualAddress: 0x2000(对应页:默认基址 + 0x2000)
  SizeOfBlock: 0x08(包含0个有效项,仅结构本身)
  ...
typedef struct _IMAGE_BASE_RELOCATION {
    DWORD VirtualAddress;  // 该块对应的内存页的起始虚拟地址(基于默认基址)
    DWORD SizeOfBlock;     // 整个块的大小(包括本结构和后面的重定位项)
    // 后面紧跟着多个重定位项(IMAGE_RELOCATION),每个都是这个结构.
} IMAGE_BASE_RELOCATION;

导出表

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;  //一般为0,没啥用
    DWORD   TimeDateStamp;  //导出表生成的时间
    WORD    MajorVersion;  //版本,也是0没啥用
    WORD    MinorVersion;  //也是没啥用的版本信息一般为0
    DWORD   Name;  //当前导出表的模块名字
    DWORD   Base;  //序号表中序号的基数
    DWORD   NumberOfFunctions;  //导出函数数量
    DWORD   NumberOfNames;  //按名字导出函数的数量
    DWORD   AddressOfFunctions;     // 地址表,指向函数RVA
    DWORD   AddressOfNames;         // 名称表,指向函数名称
    DWORD   AddressOfNameOrdinals;  // 序号表,指向AddressOfNameOrdinals的序号
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
graph LR subgraph "第 1 步:根据函数名查找其数组索引" direction LR A[AddressOfNames] --> B["名称指针数组<br/>(RVA to Name String)"]; B -- "索引 [0]" --> B1["'Func1'"]; B -- "索引 [1]" --> B2["'Func2' (查找目标)"]; B -- "索引 [2]" --> B3["'Func3'"]; end subgraph "第 2 步:使用该索引在序号表中找到对应的序号" direction LR C[AddressOfNameOrdinals] --> D["名称序号数组<br/>(Ordinal Number)"]; D -- "索引 [0]" --> D1["值 = 0"]; D -- "索引 [1]" --> D2["值 = 2"]; D -- "索引 [2]" --> D3["值 = 3"]; end subgraph "第 3 步:使用序号作为地址表的索引,定位函数地址" direction LR E[AddressOfFunctions] --> F["函数地址数组<br/>(RVA to Function)"]; F -- "索引 [0]" --> F1["'Func1' 的入口地址"]; F -- "索引 [1]" --> F_other["..."]; F -- "索引 [2]" --> F2["'Func2' 的入口地址"]; F -- "索引 [3]" --> F3["'Func3' 的入口地址"]; end %% 流程连线 B2 -- "找到'Func2'<br/>获得<b>索引 [1]</b>" --> D; D2 -- "根据<b>索引 [1]</b><br/>获得<b>序号值 [2]</b>" --> F; F -- "使用<b>序号 [2]</b>作为索引" --> F2; F2 --> G((成功定位 'Func2' 地址)); %% 样式 style B2 fill:#FFDAB9,stroke:#333,stroke-width:2px; style D2 fill:#FFDAB9,stroke:#333,stroke-width:2px; style F2 fill:#98FB98,stroke:#333,stroke-width:2px;

根据程序的自我修养中的说明,以前是采取序号来找RVA的,用来节省空间.后续发现如果dll删除了一个函数或者加一个函数,序号就要变,就不如直接找符号.

但是为了向后兼容,序号表得到保留,事实上,名称表是可选的.

导入表

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 为0
        DWORD   OriginalFirstThunk;         // 指向 导入名称表指针 INT,实际上就是未动态链接前的IAT
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;                  // 时间日期戳,与绑定技术有关,现在不常用,无需了解
    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;                           // dll的名字
    DWORD   FirstThunk;                     // 指向 导入地址数组(Import Address Table IAT),链接后存放外部函数地址
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
graph TD subgraph "程序在内存中的状态" subgraph "Windows 加载器 (Loader) 的工作" Loader["1.加载 user32.dll<br/>2.查找 MessageBoxA 地址 (例如 0x77D507EA)<br/>3.<b>用真实地址填充 IAT</b>"] end subgraph "内存中的 .exe" Code["代码: call [IAT 中 MessageBoxA 的条目]"] --> IAT_Entry_Mem subgraph "导入地址表 (IAT) - <br>已被修改" IAT_Table_Mem[IAT] IAT_Entry_Mem["<b>真实地址: 0x77D507EA</b>"] IAT_Table_Mem --> IAT_Entry_Mem end subgraph "导入名称表 (INT) - <br>保持不变" INT_Table_Mem[INT] INT_Entry_Mem["RVA 指向 'MessageBoxA' 字符串"] INT_Table_Mem --> INT_Entry_Mem end end subgraph "内存中的 user32.dll" FuncAddr["MessageBoxA 函数<br>@ 0x77D507EA"] end IAT_Entry_Mem -- "指向" --> FuncAddr style IAT_Entry_Mem fill:#98FB98,stroke:#333 style Loader fill:#lightblue,stroke:#333 end

也有输入绑定的技术,就是在确保dll加载在相应基址上,在确保dll导出表符号位置不变的情况下,不过这个不重要,了解一下即可

资源表

它采取类似磁盘目录结构的方式保存,通常有3层.

typedef struct _IMAGE_RESOURCE_DIRECTORY {
    DWORD   Characteristics; //通常为0
    DWORD   TimeDateStamp;  //资源创建时间
    WORD    MajorVersion;   //通常为0
    WORD    MinorVersion;
    WORD    NumberOfNamedEntries;//使用名字的资源条目个数
    WORD    NumberOfIdEntries;//使用ID数字资源条目的个数
//  IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

//下一层
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
    union {
        struct {
            DWORD NameOffset:31;
            DWORD NameIsString:1;
        } DUMMYSTRUCTNAME;
        DWORD   Name; 
        WORD    Id;   
    } DUMMYUNIONNAME;
    //如果最高位为 0,则剩下的31位表示一个整数ID(例如,图标的ID)。
    //如果最高位为 1,则剩下的31位是一个偏移量,指向一个IMAGE_RESOURCE_DIR_STRING_U结构,该结构包含资源的Unicode名称。
    //第一层定义资源类型,第二层定义资源名称,第三层定义代码页编号
    union {
        DWORD   OffsetToData; //资源数据偏移地址或者子目录偏移地址
        struct {
            DWORD   OffsetToDirectory:31;
            DWORD   DataIsDirectory:1;
        } DUMMYSTRUCTNAME2;
    } DUMMYUNIONNAME2;
    //如果最高位为 1,则剩下的31位是一个偏移量,指向下一级的IMAGE_RESOURCE_DIRECTORY(即子目录)。
    //如果最高位为 0,则剩下的31位是一个偏移量,指向一个IMAGE_RESOURCE_DATA_ENTRY结构(即最终的资源数据信息)。
    //这是从资源区块开始计算偏移量,不是根目录.
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

//最终的资源结构
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
    DWORD   OffsetToData; // 资源的RVA (相对虚拟地址)
    DWORD   Size;         // 资源数据的大小
    DWORD   CodePage;     // 代码页,通常用于文本资源
    DWORD   Reserved;     // 保留字段
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
资源类型(第一层) 类型ID值 资源类型 类型ID值
光标 (Cursor) 01h 字体 (Font) 08h
位图 (Bitmap) 02h 加速键 (Accelerators) 09h
图标 (Icon) 03h 未格式化资源 (Unformatted) 0Ah
菜单 (Menu) 04h 消息表 (MessageTable) 0Bh
对话框 (Dialog) 05h 光标组 (Group Cursor) 0Ch
字符串 (String) 06h 图标组 (Group Icon) 0Eh
字体目录 (Font Directory) 07h 版本信息 (Version Information) 10h
graph TD subgraph "第1层: 资源类型 (Resource Type)" A["<b>IMAGE_RESOURCE_DIRECTORY</b><br>资源根目录"] --> B["<b>IMAGE_RESOURCE_DIRECTORY_ENTRY</b><br>例如: 图标/ICON, 位图/BITMAP 等"]; end B -- "OffsetToData (指向子目录) " --> C["<b>IMAGE_RESOURCE_DIRECTORY</b><br>第2层目录"]; subgraph "第2层: 资源名/ID (Name/ID)" C --> D_ID{"<b>按ID索引</b><br>IMAGE_RESOURCE_DIRECTORY_ENTRY"}; C --> D_Name{"<b>按名称索引</b><br>IMAGE_RESOURCE_DIRECTORY_ENTRY"}; end subgraph " " D_Name -- "Name (偏移量)" --> E["<b>IMAGE_RESOURCE_DIR_STRING_U</b><br>Length<br>NameString (UNICODE字符串)"]; end D_ID -- "OffsetToData (指向子目录)" --> F["<b>IMAGE_RESOURCE_DIRECTORY</b><br>第3层目录"]; D_Name -- "OffsetToData (指向子目录)" --> F; subgraph "第3层: 语言 (Language)" F --> G{"<b>按语言索引</b><br>IMAGE_RESOURCE_DIRECTORY_ENTRY"}; end G -- "OffsetToData (指向数据)" --> H["<b>IMAGE_RESOURCE_DATA_ENTRY</b><br>资源数据入口"]; subgraph "最终资源数据" H -- "OffsetToData (RVA)" --> I(("<b>资源数据块</b><br>Size")); end %% 样式定义 classDef dir fill:#ffe2e2,stroke:#c53030,stroke-width:2px; classDef entry fill:#ebf8ff,stroke:#3182ce,stroke-width:2px; classDef dataEntry fill:#f7fafc,stroke:#718096,stroke-width:2px,color:#2d3748; classDef data fill:#f0fff4,stroke:#38a169,stroke-width:2px,color:#2f855a; classDef string fill:#fffff0,stroke:#d69e2e,stroke-width:1px,color:#975a16; class A,C,F dir; class B,D_ID,D_Name,G entry; class H dataEntry; class I data; class E string;

TLS表

我放在专门讲TLS的地方了

posted @ 2025-08-30 14:05  T0fV404  阅读(12)  评论(0)    收藏  举报