PE格式

一、总体格式

+-------------------------------+ \
|     MS-DOS MZ header          |  | -> 30字节
+-------------------------------+  |
| MS-DOS Real-Mode Stub program |  |
+-------------------------------+  |
|     PE Signature              |  | -> PE file header
+-------------------------------+  |
|     IMAGE_FILE_HEADER         |  | -> 20字节
+-------------------------------+  |
|     IMAGE_OPTIONAL_HEADER     |  | 
+-------------------------------+ /
|     section header #1         | 
+-------------------------------+ 
|     section header #2 
+------------------------- 
: 
+------------------------------+ 
|        section #1            | 
+------------------------------+ 
|        section #2 
+-------------------- 
: 附加数据
: 

 二、_IMAGE_DOS_HEADER

typedef struct _IMAGE_DOS_HEADER
{
     WORD e_magic;              // "MZ"
     WORD e_cblp;
     WORD e_cp;
     WORD e_crlc;
     WORD e_cparhdr;
     WORD e_minalloc;
     WORD e_maxalloc;
     WORD e_ss;
     WORD e_sp;
     WORD e_csum;
     WORD e_ip;
     WORD e_cs;
     WORD e_lfarlc;
     WORD e_ovno;
     WORD e_res[4];
     WORD e_oemid;
     WORD e_oeminfo;
     WORD e_res2[10];
     LONG e_lfanew;             // NT 头相对于文件起始处的偏移,“PE”字符位置
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

  三、NT_headers

IMAGE_NT_HEADERS
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;         /* +0000h PE 标识 */
  IMAGE_FILE_HEADER       FileHeader;        /* +0004h PE 标准头 */
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;    /* +0018h PE 可选头  */
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

 四、文件头

IMAGE_FILE_HEADER
typedef struct _IMAGE_FILE_HEADER {
  WORD  Machine;                    /* +0004h 目标机器类型 */
  WORD  NumberOfSections;           /* +0006h PE 中节的数量 */
  DWORD TimeDateStamp;              /* +0008h 时间戳 创建文件的时间*/
  DWORD PointerToSymbolTable;       /* +000ch 指向符号表的指针 */
  DWORD NumberOfSymbols;            /* +0010h 符号表中符号数目 */
  WORD  SizeOfOptionalHeader;       /* +0012h 可选头的大小 *默认0X00E0为32位,0X00F0为64位/
  WORD  Characteristics;            /* +0014h 文件属性标志 */
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

// 文件属性标志
#define IMAGE_FILE_RELOCS_STRIPPED          0x0001    // 表示文件不包含重定位信息,只能在原定的基址加载。如果原定基址不可用,加载器会报出错误
#define IMAGE_FILE_EXECUTABLE_IMAGE         0x0002    // 表示文件可执行,如果该位未设置,意味着存在链接器错误
#define IMAGE_FILE_LINE_NUMS_STRIPPED       0x0004    // 不存在行信息
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED      0x0008    // 不存在符号信息
#define IMAGE_FILE_AGGRESSIVE_WS_TRIM       0x0010    // 已废弃
#define IMAGE_FILE_LARGE_ADDRESS_AWARE      0x0020    // 应用可处理大于 2GB 的地址
#define IMAGE_FILE_BYTES_REVERSED_LO        0x0080    // 小尾存储。已废弃
#define IMAGE_FILE_32BIT_MACHINE            0x0100    // 基于 32-bit 体系结构
#define IMAGE_FILE_DEBUG_STRIPPED           0x0200    // 不存在调试信息
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP  0x0400    // 如果映像文件在可移动介质上,完全加载并复制到内存交换文件中
#define IMAGE_FILE_NET_RUN_FROM_SWAP        0x0800    // 如果映像文件在网络介质上,完全加载并复制到内存交换文件中
#define IMAGE_FILE_SYSTEM                   0x1000    // 映像文件是系统文件
#define IMAGE_FILE_DLL                      0x2000    // 映像文件是动态链接库文件
#define IMAGE_FILE_UP_SYSTEM_ONLY           0x4000    // 文件只能在单处理器机器上运行
#define IMAGE_FILE_BYTES_REVERSED_HI        0x8000    // 大尾存储(已废弃)

 五、可选头 

IMAGE_OPTIONAL_HEADER
typedef struct _IMAGE_OPTIONAL_HEADER {
  WORD                 Magic;                            /* +0018h 魔数 */
  BYTE                 MajorLinkerVersion;               /* +001ah 链接器主要版本号 */
  BYTE                 MinorLinkerVersion;               /* +001bh 链接器次要版本号 */
  DWORD                SizeOfCode;                       /* +001ch 所有含代码的节的总大小 */
  DWORD                SizeOfInitializedData;            /* +0020h 所有含已初始化数据的节的总大小 */
  DWORD                SizeOfUninitializedData;          /* +0024h 所有含未初始化数据的节的总大小 */
  DWORD                AddressOfEntryPoint;              /* +0028h 程序入口点RVA */
  DWORD                BaseOfCode;                       /* +002ch 代码节起始RVA */ 
  DWORD                BaseOfData;                       /* +0030h 数据节起始RVA *在64位中不存在,也BASE合并/
  DWORD                ImageBase;                        /* +0034h 映像文件加载时的首选地址 */
  DWORD                SectionAlignment;                 /* +0038h 内存中节对齐粒度*/
  DWORD                FileAlignment;                    /* +003ch 文件中节对齐粒度 */
  WORD                 MajorOperatingSystemVersion;      /* +0040h 操作系统主要版本号 */
  WORD                 MinorOperatingSystemVersion;      /* +0042h 操作系统次要版本号 */
  WORD                 MajorImageVersion;                /* +0044h 映像文件主要版本号 */
  WORD                 MinorImageVersion;                /* +0046h 映像文件次要版本号 */
  WORD                 MajorSubsystemVersion;            /* +0048h 子系统主要版本号 */
  WORD                 MinorSubsystemVersion;            /* +004ah 子系统次要版本号 */
  DWORD                Win32VersionValue;                /* +004ch 保留。置0 */
  DWORD                SizeOfImage;                      /* +0050h 内存中映像文件的大小 */
  DWORD                SizeOfHeaders;                    /* +0054h 所有头+节表大小 */
  DWORD                CheckSum;                         /* +0058h 映像文件校验和 */
  WORD                 Subsystem;                        /* +005ch 运行映像所需子系统 */
  WORD                 DllCharacteristics;               /* +005eh 映像文件的DLL属性 */
  DWORD                SizeOfStackReserve;               /* +0060h 初始化时的保留的栈大小,64位中占8字节 */
  DWORD                SizeOfStackCommit;                /* +0064h 初始化时实际提交的栈大小64位中占8字节 *  +0068h/
  DWORD                SizeOfHeapReserve;                /* +0068h 初始化时保留的堆大小 64位中占8字节*+0070/
  DWORD                SizeOfHeapCommit;                 /* +006ch 初始化时实际提交的堆大小 *64位中占8字节 +0078/
  DWORD                LoaderFlags;                      /* +0070h 已废弃 */
  DWORD                NumberOfRvaAndSizes;              /* +0074h 数据目录结构的数量 */
  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];     /* +0078h 指向数据目录中第一个 IMAGE_DATA_DIRECTORY 结构体的指针 */
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

 六、目录

IMAGE_DATA_DIRECTORY
typedef struct _IMAGE_DATA_DIRECTORY {
  DWORD VirtualAddress;      /* 数据目录的 RVA */
  DWORD Size;                /* 数据目录的大小 */
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;



// 数据目录
DataDirectory[0] =   EXPORT Directory           // 导入表 RVA 和大小
DataDirectory[1] =   IMPORT Directory           // 导入表 RVA 和大小
DataDirectory[2] =   RESOURCE Directory         // 资源表 RVA 和大小
DataDirectory[3] =   EXCEPTION Directory        // 异常表 RVA 和大小
DataDirectory[4] =   CERTIFICATE Directory      // 证书表 FOA 和大小
DataDirectory[5] =   BASE RELOCATION Directory  // 基址重定位表 RVA 和大小
DataDirectory[6] =   DEBUG Directory            // 调试信息 RVA 和大小
DataDirectory[7] =   ARCH DATA Directory        // 指定架构信息 RVA 和大小
DataDirectory[8] =   GLOBALPTR Directory        // 全局指针寄存器 RVA
DataDirectory[9] =   TLS Directory              // 线程私有存储表 RVA 和大小
DataDirectory[10] =  LOAD CONFIG Directory      // 加载配置表 RVA 和大小
DataDirectory[11] =  BOUND IMPORT Directory     // 绑定导入表 RVA 和大小
DataDirectory[12] =  `IAT` Directory              // 导入地址表 RVA 和大小
DataDirectory[13] =  DELAY IMPORT Directory     // 延迟导入描述符 RVA 和大小
DataDirectory[14] =  CLR Directory              // CLR数据 RVA 和大小
DataDirectory[15] =  Reserverd                  // 保留

  七、节头

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;                 /* 磁盘文件中节区 FOA */
  DWORD PointerToRelocations;             /* 指向重定位表的指针 */
  DWORD PointerToLinenumbers;             /* 指向行号表的指针 */
  WORD  NumberOfRelocations;              /* 重定位入口数量 */
  WORD  NumberOfLinenumbers;              /* 行号数量 */
  DWORD Characteristics;                  /* 节区属性 */
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


// 节区属性
#define IMAGE_SCN_CNT_CODE                0x00000020  // 节区包含代码
#define IMAGE_SCN_CNT_INITIALIZED_DATA    0x00000040  // 节区包含已初始化数据
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA  0x00000080  // 节区包含未初始化数据
#define IMAGE_SCN_ALIGN_1BYTES            0x00100000  // 1-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_2BYTES            0x00200000  // 2-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_4BYTES            0x00300000  // 4-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_8BYTES            0x00400000  // 8-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_16BYTES           0x00500000  // 16-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_32BYTES           0x00600000  // 32-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_64BYTES           0x00700000  // 64-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_128BYTES          0x00800000  // 128-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_256BYTES          0x00900000  // 256-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_512BYTES          0x00A00000  // 512-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_1024BYTES         0x00B00000  // 1024-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_2048BYTES         0x00C00000  // 2048-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_4096BYTES         0x00D00000  // 4096-byte 对齐。仅用于目标文件
#define IMAGE_SCN_ALIGN_8192BYTES         0x00E00000  // 8192-byte 对齐。仅用于目标文件
#define IMAGE_SCN_LNK_NRELOC_OVFL         0x01000000  // 节区包含扩展的重定位项
#define IMAGE_SCN_MEM_DISCARDABLE         0x02000000  // 节区可根据需要丢弃,如 .reloc 在进程开始后被丢弃
#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  // 节区可写八

八:导入表:

#1结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            
        DWORD   OriginalFirstThunk;  //指向上面的IAT,是一个结构数组,里面保存了导入函数的信息(例如Msg的实际地址)       
    };                   //最后会以全0的结构为结束,其中每一项是一个结构,一项8个字节,是指向                                
    DWORD   TimeDateStamp;           //时间,一般不用      

    DWORD   ForwarderChain;           //链表前一个结构,一般不用            
    DWORD   Name;            //上面说的DLL名称的RVA偏移通过偏移可以找到DLL名称
    DWORD   FirstThunk;               // IAT 的RVA偏移.和originalFirstThunk不同
} IMAGE_IMPORT_DESCRIPTOR;

(1),表示导入符号是一个数值,是一个RVA数值(比如我们的Msg可以利用符号导入)
当结构的最高位为1时,表示函数是以序号的方式导入的,这时双字的低位就是函数的序号,当双字最高位为0时,表示函数以函数名方式导入
OriginaFIrstThunk中存储的是一个地址,指向IMAGE_THUNK_DATA(导入名称表)该表中记录了导入函数的名称,也被叫做INT表,该表的结束标志和很多表一样:最后一张表的数据全为0。
FirstThunk中存储的是该文件的IAT(import address table)表的RVA,因为导入表和IAT表都和调用的dll文件中的函数有密切关系,该表的结束标志和很多表一样:最后一张表的数据全为0。
找到IAT表有两种方式:一种是通过数据目录的第13个项目的VirtualAddress找到,另一种方式是通过导入表的FirstTrunk找到
一个程序可能调用了很多dll文件,所以导入表也不只一张,VirtualAddress(数据目录中的第二个元素为导入表) 指向多个一样的导入表结构。

(2)加载过程
加载器读取导入表
加载所需的DLL到内存
获取每个函数的实际地址
用实际地址填充IAT
程序运行时通过IAT调用函数

#OriginalFirstThunk和FirstThunk都指向IMAGE_THUNK_DATA32
typedef struct _IMAGE_THUNK_DATA32 {
union {
PBYTE ForwarderString; // (1) 转发字符串的RAV
PDWORD Function; // (2) 被导入函数的地址
DWORD Ordinal; // (3) 被导入函数的序号
PIMAGE_IMPORT_BY_NAME AddressOfData; // (4) 指向输入名称表
} u1;
} IMAGE_THUNK_DATA32;

九:导出表

#2导出表结构

typedef struct _IMAGE_EXPORT_DIRECTORY { 
    DWORD  Characteristics;         // (1) 保留,恒为0x00000000    
    DWORD  TimeDateStamp;           // (2) 时间戳   
    WORD   MajorVersion;            // (3) 主版本号,一般不赋值   
    WORD   MinorVersion;            // (4) 子版本号,一般不赋值   
    DWORD  Name;                    // (5) 模块名称*   
    DWORD  Base;                    // (6) 索引基数*   
    DWORD  NumberOfFunctions;       // (7) 导出地址表中成员个数*   
    DWORD  NumberOfNames;           // (8) 导出名称表中成员个数*   
    DWORD  AddressOfFunctions;      // (9) 导出地址表(EAT)*   
    DWORD  AddressOfNames;          // (10) 导出名称表(ENT)*   
    DWORD  AddressOfNameOrdinals;   // (11) 指向导出序号数组* 
}IMAGE_EXPORT_DIRECTORY,*PIMAGE_EXPORT_DIRECTORY;
如:

七、重定位表

重定位信息是在编译时,由编译器生成并被保存在可执行文件中的,在程序被执行前,由操作系统根据重定位信息修正代码,个重定位块以一个IMAGE_BASE_RELOCATION结构开头,
后面跟着在本页中使用的所有重定位项,每个重定位项占用16字节,最后一个节点是一个使用0填充的_IMAGE_BASE_RELOCATION标志表的结束。

步骤:由目录定位到重定位表,重定位表决定大小,然后由重定位表根据下列结构进行修正。
表的组织由一页以1K为一块。

typedef struct _IMAGE_BASE_RELOCATION
{   
    DWORD   VirtualAddress;                      // 需重定位数据的起始RVA   
    DWORD   SizeOfBlock;                         // 本结构与TypeOffset总大小 
    WORD    TypeOffset[1];                       // 原则上不属于本结构 
} IMAGE_BASE_RELOCATION; typedef  IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;

#由sizeofblock决定typeoffset个数。
TypeOffset的元素个数 = (SizeOfBlock - 8 )/ 2
TypeOffset的每个元素都是一个自定义类型结构

struct
{
    WORD Offset:12;  // 大小为12Bit的重定位偏移 
    WORD Type  :4;   // 大小为4Bit的重定位信息类型值 
}TypeOffset;         // 这个结构体是A1Pass总结的

每个重定位项的16位数据位中的低12位就是需要重定位的数据在页面中的地址,剩下的高4位也没有被浪费,它们被用来描述当前重定位项的种类。虽然高4位定义了多种重定位项的属性,但实际上在PE文件中只能看到0和3这两种情况。
3代表双字都要重定位。

当读者需要遍历这个表时,首先可以通过NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress获取到重定位表的相对信息,

并通过(PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA))得到重定位表的FOA文件地址,在Reloc->SizeOfBlock变量内获取到重定位块,并循环输出则可实现枚举所有重定位块

八、参考链接

https://www.cnblogs.com/cyx-b/p/13485664.html
https://ctf-wiki.org/executable/pe/pe-excutable/
https://www.cnblogs.com/zpchcbd/p/18995565
https://learn.microsoft.com/zh-cn/windows/win32/debug/pe-format

九、关闭ASLR

ASLR全称Address Space Layout Randomization,又称地址空间配置随机化、地址空间布局随机化
用CFF打开文件将PE中的十六进制第一个40 81数据改为00 81

 十、TLS线程目录表

TLS表位于PE文件Optional Header中的数据目录(Data Directory)数组中,是第10个条目(索引为9)。
TLS结构32位:

typedef struct _IMAGE_TLS_DIRECTORY32 {
    DWORD   StartAddressOfRawData;     // TLS原始数据起始地址(RVA)
    DWORD   EndAddressOfRawData;       // TLS原始数据结束地址(RVA)
    DWORD   AddressOfIndex;            // 指向TLS索引值的地址(RVA)
    DWORD   AddressOfCallBacks;        // TLS回调函数数组的地址(RVA)
    DWORD   SizeOfZeroFill;            // 初始化为0的数据大小
    DWORD   Characteristics;           // 特性标志位(保留,通常为0)
} IMAGE_TLS_DIRECTORY32;

TLS结构64位:
typedef struct _IMAGE_TLS_DIRECTORY64 {
    ULONGLONG   StartAddressOfRawData; // TLS原始数据起始地址(RVA)
    ULONGLONG   EndAddressOfRawData;   // TLS原始数据结束地址(RVA)
    ULONGLONG   AddressOfIndex;        // 指向TLS索引值的地址(RVA)
    ULONGLONG   AddressOfCallBacks;    // TLS回调函数数组的地址(RVA)
    DWORD       SizeOfZeroFill;        // 初始化为0的数据大小
    DWORD       Characteristics;       // 特性标志位(保留,通常为0)
} IMAGE_TLS_DIRECTORY64;

回调函数原型:

void NTAPI TlsCallback(PVOID DllHandle, DWORD Reason, PVOID Reserved)
{
    switch(Reason)
    {
        case DLL_PROCESS_ATTACH:
            // 进程初始化时调用
            break;
        case DLL_THREAD_ATTACH:
            // 线程创建时调用
            break;
        case DLL_THREAD_DETACH:
            // 线程退出时调用
            break;
        case DLL_PROCESS_DETACH:
            // 进程终止时调用
            break;
    }
}
##线程创建时
当创建新线程时,系统会分配一块内存用于TLS存储
将PE文件中指定的TLS模板数据复制到这块内存
用0填充SizeOfZeroFill指定大小的额外空间
调用所有TLS回调函数(DLL_THREAD_ATTACH原因)

###TLS数据访问
每个线程都有自己的TLS数据副本
通过TLS索引和TlsGetValue/TlsSetValue函数访问TLS数据
在Windows内部,TLS数据存储在线程环境块(TEB)中
参考链接:
https://cloud.tencent.com/developer/article/1015269
手写PE参考链接:
https://www.cnblogs.com/by-clark/p/9129135.html

 

posted @ 2026-01-06 16:34  hopeccie  阅读(68)  评论(0)    收藏  举报