Featured image of post PE文件结构

PE文件结构

  • DOS头

    • e_magic:最开头的两个字节4D5A是DOS签名,不可改
    • e_lfanew:最后四个字节,小端序,所以80指向PE头的偏移,也就是开始位置

DOS头bin

  • NT头

    1
    2
    3
    4
    5
    
    typedef struct _IMAGE_NT_HEADERS {
     DWORD Signature;  // 50 45 PE标识
     IMAGE_FILE_HEADER FileHeader; // 标准PE头
     IMAGE_OPTIONAL_HEADER32 OptionalHeader; ``// 扩展PE头
    } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
    
    • Signature(4字节)

      PE标识不可改 50 45

    • 标准PE头 IMAGE_FILE_HEADER(20字节)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      typedef struct _IMAGE_FILE_HEADER {
       WORD Machine; // 可以运行在什么样的CPU上
       WORD NumberOfSections; // 表示节的数量
       DWORD TimeDateStamp; // 编译器填写的时间戳
       DWORD PointerToSymbolTable; // 调试相关
       DWORD NumberOfSymbols; // 调试相关
       WORD SizeOfOptionalHeader; // 扩展PE头的大小,32位E0,64位F0
       WORD Characteristics; // 文件属性,每个二进制位代表不同信息
      } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
      

      文件属性

      文件属性

    • 扩展PE头 IMAGE_OPTIONAL_HEADER

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      
      //32位为例
      typedef struct _IMAGE_OPTIONAL_HEADER32 {
          WORD    Magic;                  // 标志:PE32 是 0x10B
          BYTE    MajorLinkerVersion;     // 链接器的主版本号
          BYTE    MinorLinkerVersion;     // 链接器的次版本号
          DWORD   SizeOfCode;             // 代码段的大小(没用)
          DWORD   SizeOfInitializedData;  // 初始化数据段的大小(没用)
          DWORD   SizeOfUninitializedData;// 未初始化数据段的大小(没用)
          DWORD   AddressOfEntryPoint;    // 程序入口点的 RVA
          DWORD   BaseOfCode;             // 代码段的起始 RVA(没用)
          DWORD   BaseOfData;             // 数据段的起始 RVA(没用)
          DWORD   ImageBase;              // 内存镜像基址
          DWORD   SectionAlignment;       // 内存对齐
          DWORD   FileAlignment;          // 文件对齐
          WORD    MajorOperatingSystemVersion; // OS 主版本号
          WORD    MinorOperatingSystemVersion; // OS 次版本号
          DWORD   SizeOfImage;            // 镜像的总大小
          DWORD   SizeOfHeaders;          // 头+节表按照文件对齐后的总大小
          DWORD   CheckSum;               // 校验和
          WORD    Subsystem;              // 子系统(如 GUI、CUI)
          WORD    DllCharacteristics;    // DLL 属性
          DWORD   SizeOfStackReserve;     // 堆栈保留大小
          DWORD   SizeOfStackCommit;      // 堆栈提交大小
          DWORD   SizeOfHeapReserve;      // 堆保留大小
          DWORD   SizeOfHeapCommit;       // 堆提交大小
          DWORD   LoaderFlags;            // 装载器标志(通常为 0)
          DWORD   NumberOfRvaAndSizes;    // 数据目录项数
          IMAGE_DATA_DIRECTORY DataDirectory[16]; // 表,结构体数组
      } IMAGE_OPTIONAL_HEADER32;
      
      • 重要字段

        WORD Magic; 32位程序:10B;64位程序:20B

        DWORD AddressOfEntryPoint; 程序入口 相对于加载到内存后起始地址的偏移,需要与ImageBase配合使用

          		**DWORD   ImageBase; **   内存镜像基址
        
          		**WORD    DllCharacteristics;**  里可以选择是否为动态基址 
        

​ **DWORD SectionAlignment; ** 内存对齐

DWORD FileAlignment; 文件对齐

DWORD SizeOfImage; 文件在内存中的大小,按SectionAlignment对齐后的大小

DWORD SizeOfHeaders; DOS头+NT头+标准PE头+可选PE头+区段头,按FileAlignment对齐后的大小

​ **DWORD CheckSum;**表示校验和,是用来判断文件是否被修改的,它的计算方法就是文件的两个字节与两个字节相加,最终的值(不考虑溢出情况)就是校验和。

DWORD NumberOfRvaAndSizes; 数据目录项数

IMAGE_DATA_DIRECTORY DataDirectory (数据⽬录) 指向数据⽬录中第⼀个 IMAGE_DATA_DIRECTORY 结构的指针。每个条⽬包含地址(RVA)和⼤⼩

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY{
	DWORD VirtualAddresss;   //内存中的偏移
    DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

数据目录

  • RVA和FOA转换 RVA-对应区段RVA=FOA-对应区段的FOA

  • 节表

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    typedef struct _IMAGE_SECTION_HEADER {
        BYTE    Name[8];               // 节名称(最多 8 字节,可能没有结束符)
        union {
            DWORD   PhysicalAddress;   // 实际已弃用,常为 0
            DWORD   VirtualSize;       // 节的实际大小(内存中的大小)
        } Misc;
        DWORD   VirtualAddress;        // 节在内存中的 RVA(相对虚拟地址)
        DWORD   SizeOfRawData;         // 节在文件中的大小(以 FileAlignment 对齐)
        DWORD   PointerToRawData;      // 节在文件中的偏移(以 FileAlignment 对齐)
        DWORD   PointerToRelocations; // 重定位表的文件偏移(通常为 0,已弃用)
        DWORD   PointerToLinenumbers; // 调试信息的文件偏移(通常为 0)
        WORD    NumberOfRelocations;  // 重定位条目数量(通常为 0)
        WORD    NumberOfLinenumbers;  // 调试行号条目数量(通常为 0)
        DWORD   Characteristics;      // 节的属性标志(权限、类型等)
    } IMAGE_SECTION_HEADER;
    

    DWORD VirtualAddress; 节在内存中的偏移位置

    DWORD PointerToRawData; 节在文件中的偏移

  • 空白添加代码

  • 扩大节

    • 分配一块新的空间,大小为S

    • 将最后一个节的SizeOfRawData和VirtualSize改为N

      N=max(SizeOfRawData,VirtualSize)+S

    • 修改SizeOfImage

    • 修改节的属性使它可执行

  • 新增节的步骤

    • 判断是否有足够的空间,可以添加一个节表
    • 在节表中新增一个成员
    • 修改PE头中节的数量
    • 修改SizeOfimage的大小
    • 在原有数据的最后,新增一个节的数据(内存对齐的整数倍)
    • 修正新增节表的属性
  • 导出表,先拿到可选PE头,看最后一个字段数据目录表的第0项,存放导出表的偏移

    1
    2
    3
    4
    
    typedef struct _IMAGE_DATA_DIRECTORY{    
        DWORD VirtualAddresss;   //内存中的偏移   
        DWORD Size;
    }IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
    
    • 导出表结构体

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      typedef struct _IMAGE_EXPORT_DIRECTORY
      {
          DWORD   Characteristics;       // 保留,恒为0x00000000(没有)
          DWORD   TimeDateStamp;         // 文件的产生时间戳(没有)
          WORD    MajorVersion;          // 主版本号(没有)
          WORD    MinorVersion;          // 次版本号(没有)
          DWORD   Name;                  // 指向导出表文件名的RVA
          DWORD   Base;                  // 导出函数的起始序号
          DWORD   NumberOfFunctions;     // 导出函数总数(最大序号-最小序号+1)
          DWORD   NumberOfNames;         // 以名称导
          出函数的总数
          DWORD   AddressOfFunctions;    // 导出函数地址表的RVA
          DWORD   AddressOfNames;        // 函数名称地址表的RVA
          DWORD   AddressOfNameOrdinals; // 函数名序号表的RVA
      } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
      

      AddressOfFunctions,AddressOfNames,AddressOfNameOrdinals

      函数序号表-函数名称表-函数地址表

      已知名称,可从AddressOfNames获得相应索引,再通过该索引在AddressOfNameOrdinals找到函数偏移地址在AddressOfFunctions表中的索引。

  • 导入表

    • 前置知识

      • 调用dll文件的原理

        程序在调用dll文件函数时,并不是把dll文件函数的代码编译到当前文件中,而是把dll文件对应的函数地址保存到了当前文件中

        在文件中,对应的函数地址部分存储的时函数名称

      • 一个进程空间中exe dll文件如何被加载到内存

      • exe文件调用的动调链接库在内存中和硬盘中有什么不同

    • 扩展PE头中数据目录存储的是第一个导入表的偏移,导入表有很多张,20个字节是一个,以20个0为结束

    • 导入表结构体

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      typedef struct _IMAGE_IMPORT_DESCRIPTOR {
      	union {
      		DWORD Characteristics;
      		DWORD OriginalFirstThunk; // INT地址
      	};
      DWORD TimeDateStamp; // 时间戳
      DWORD ForwarderChain; // 转发链
      DWORD Name; // DLL名称RVA
      DWORD FirstThunk; // IAT地址
      } IMAGE_IMPORT_DESCRIPTOR;
      

      DWORD OriginalFirstThunk INT导入名称表地址

      表中成员

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      typedef struct _IMAGE_THUNK_DATA32 {
          union {
              DWORD ForwarderString;      // PBYTE 
              DWORD Function;             // PDWORD
              DWORD Ordinal;
              DWORD AddressOfData;        //RVA 指向_IMAGE_IMPORT_BY_NAME 
          } u1;
      } IMAGE_THUNK_DATA32;
      typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
      

      INT

      1
      2
      3
      4
      
      typedef struct _IMAGE_IMPORT_BY_NAME {
          WORD    Hint;       //可能为0,编译器决定,如果不为0,是函数在导出表中的索引
          BYTE    Name[1];    //函数名称,以0结尾,由于不知道到底多长,所以干脆只给出第一个字符,找到0结束
      } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
      

      DWORD FirstThunk IAT导入地址表地址

      加载前是和INT一样,加载后存的是函数地址

  • 重定位表 修正因为动调加载导致的地址错误

    1
    2
    3
    4
    5
    
    typedef struct _IMAGE_BASE_RELOCATION
    {   
        DWORD   VirtualAddress;                      // 需重定位数据的起始RVA   
        DWORD   SizeOfBlock;                         // 本结构与TypeOffset总大小 
    } IMAGE_BASE_RELOCATION; typedef  IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;
    

重定位表

​ 高4位判断是否需要修改