PE文件结构

2022/01/13

Kkem Chen

  • PE( Portable Execute)文件是Windows下可执行文件的总称,常见的有 DLL,EXE,OCX,SYS 等。它是微软在 UNIX 平台的 COFF(通用对象文件格式)基础上制作而成。
  • 最初设计用来提高程序在不同操作系统上的移植性,但实际上这种文件格式仅用在 Windows 系列操作系统下PE文件是指 32 位可执行文件,也称为PE32。64位的可执行文件称为 PE+ 或 PE32+,是PE(PE32)的一种扩展形式(请注意不是PE64)

DOS头(分Header和DOS存根)

  • Header结构(00000000 - 0000003F,共64字节)
  • DOS存根(00000040 - 000000BF,共128字节)
    DOS存根是一段简单的DOS程序,主要用来输出类似“This program cannot be run in DOS mode.”的提示语句。即使没有DOS存根,程序也能正常执行。

重要参数为 e_magic 和 e_lfanew 。(即第一个和最后一个)


NT头(PE最重要的头)

起始位置由DOS头中e_lfanew决定,表示DOS头之后的NT头相对其实地址的偏移

  • IMAGE_NT_HEADERS32

4个字节(4*bit=32bit)

  • IMAGE_FILE_HEADER

其中有4个重要的成员,若设置不正确,将会导致文件无法正常运行。

typedef struct _IMAGE_FILE_HEADER { 
       WORD    Machine;              
       // 每个CPU拥有唯一的Machine码 -> 4C 01 -> PE -> 兼容32位Intel X86芯片'

       WORD    NumberOfSections;     
       // 指文件中存在的节段(又称节区)数量,也就是节表中的项数 -> 00 04 -> 4
       // 该值一定要大于0,且当定义的节段数与实际不符时,将发生运行错误。'

       DWORD   TimeDateStamp;         
       // PE文件的创建时间,一般有连接器填写 -> 38 D1 29 1E
       DWORD   PointerToSymbolTable;  
       // COFF文件符号表在文件中的偏移 -> 00 00 00 00
       DWORD   NumberOfSymbols;       
       // 符号表的数量 -> 00 00 00 00

       WORD    SizeOfOptionalHeader; 
       // 指出IMAGE_OPTIONAL_HEADER32结构体的长度。->  00 E0 -> 224字节
       // PE32+格式文件中使用的是IMAGE_OPTIONAL_HEADER64结构体,
       // 这两个结构体尺寸是不相同的,所以需要在SizeOfOptionalHeader中指明大小。

       WORD    Characteristics;      
       // 标识文件的属性,二进制中每一位代表不同属性 -> 0F 01
                   
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
  • IMAGE_OPTIONAL_HEADER

其中有9个重要参数,设置错误会导致文件无法运行

typedef struct _IMAGE_OPTIONAL_HEADER { 
        WORD    Magic;                     '// 魔数 32位为0x10B,64位为0x20B,ROM镜像为0x107'
        BYTE    MajorLinkerVersion;         // 链接器的主版本号 -> 05
        BYTE    MinorLinkerVersion;         // 链接器的次版本号 -> 0C
        DWORD   SizeOfCode;                 // 代码节大小,一般放在“.text”节里,必须是FileAlignment的整数倍 -> 40 00 04 00
        DWORD   SizeOfInitializedData;      // 已初始化数大小,一般放在“.data”节里,必须是FileAlignment的整数倍 -> 40 00 0A 00
        DWORD   SizeOfUninitializedData;    // 未初始化数大小,一般放在“.bss”节里,必须是FileAlignment的整数倍 -> 00 00 00 00
        DWORD   AddressOfEntryPoint;       '// 指出程序最先执行的代码起始地址(RVA) -> 00 00 10 00'
        DWORD   BaseOfCode;                 // 代码基址,当镜像被加载进内存时代码节的开头RVA。必须是SectionAlignment的整数倍 -> 40 00 10 00
 
        DWORD   BaseOfData;                 // 数据基址,当镜像被加载进内存时数据节的开头RVA。必须是SectionAlignment的整数倍 -> 40 00 20 00
                                            // 在64位文件中此处被并入紧随其后的ImageBase中。
 
        DWORD   ImageBase;                 '// 当加载进内存时,镜像的第1个字节的首选地址。
                                           // WindowEXE默认ImageBase值为00400000,DLL文件的ImageBase值为10000000,也可以指定其他值。
                                            // 执行PE文件时,PE装载器先创建进程,再将文件载入内存,
                                            // 然后把EIP寄存器的值设置为ImageBase+AddressOfEntryPoint'

                                           '// PE文件的Body部分被划分成若干节段,这些节段储存着不同类别的数据。'
        DWORD   SectionAlignment;          '// SectionAlignment指定了节段在内存中的最小单位, -> 00 00 10 00'
        DWORD   FileAlignment;             '// FileAlignment指定了节段在磁盘文件中的最小单位,-> 00 00 02 00
                                            // SectionAlignment必须大于或者等于FileAlignment'
 
        WORD    MajorOperatingSystemVersion;// 主系统的主版本号 -> 00 04
        WORD    MinorOperatingSystemVersion;// 主系统的次版本号 -> 00 00
       WORD    MajorImageVersion;          // 镜像的主版本号 -> 00 00
       WORD    MinorImageVersion;          // 镜像的次版本号 -> 00 00
       WORD    MajorSubsystemVersion;      // 子系统的主版本号 -> 00 04
       WORD    MinorSubsystemVersion;      // 子系统的次版本号 -> 00 00
       DWORD   Win32VersionValue;          // 保留,必须为0 -> 00 00 00 00

       DWORD   SizeOfImage;               '// 当镜像被加载进内存时的大小,包括所有的文件头。向上舍入为SectionAlignment的倍数。
                                           // 一般文件大小与加载到内存中的大小是不同的。 -> 00 00 50 00'

       DWORD   SizeOfHeaders;             '// 所有头的总大小,向上舍入为FileAlignment的倍数。
                                           // 可以以此值作为PE文件第一节的文件偏移量。-> 00 00 04 00'

       DWORD   CheckSum;                   // 镜像文件的校验和 -> 00 00 B4 99

       WORD    Subsystem;                 '// 运行此镜像所需的子系统 -> 00 02 -> 窗口应用程序
                                           // 用来区分系统驱动文件(*.sys)与普通可执行文件(*.exe,*.dll),
                                            // 参考:https://blog.csdn.net/qiming_zhang/article/details/7309909#3.2.3'
 
        WORD    DllCharacteristics;         // DLL标识 -> 00 00
        DWORD   SizeOfStackReserve;         // 最大栈大小。CPU的堆栈。默认是1MB。-> 00 10 00 00
        DWORD   SizeOfStackCommit;          // 初始提交的堆栈大小。默认是4KB -> 00 00 10 00
        DWORD   SizeOfHeapReserve;          // 最大堆大小。编译器分配的。默认是1MB ->00 10 00 00
        DWORD   SizeOfHeapCommit;           // 初始提交的局部堆空间大小。默认是4K ->00 00 10 00
        DWORD   LoaderFlags;                // 保留,必须为0 -> 00 00 00 00
 
        DWORD   NumberOfRvaAndSizes;       '// 指定DataDirectory的数组个数,由于以前发行的Windows NT的原因,它只能为16。 -> 00 00 00 10'
        IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; '// 数据目录数组。详见下文。' 
    } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
 
typedef struct _IMAGE_DATA_DIRECTORY {  
    DWORD   VirtualAddress;  
    DWORD   Size;  
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
  • IMAGE_SECTION_HEADER

参考链接:
BiliBiliPE文件结构全局大揭秘
PE文件结构详解 –(完整版)



comments powered by Disqus