PE文件格式

PE文件格式

总体

PE即Portable Executable,是Windows OS下使用的可执行文件格式。PE文件是指32位的可执行文件,亦称为PE32。64位的可执行文件称为PE+或PE32+,是PE文件的一种扩展形式。

img

相关概念

  • 虚拟内存地址(Virtual Address, VA)PE文件中的指令被装入内存后的地址。
  • 相对虚拟内存地址(Reverse Virtual Address, RVA相对虚拟地址是内存地址相对于映射基址的偏移量。
  • 文件偏移地址(File Offset Address, FOA)数据在PE文件中的地址叫文件偏移地址,这是文件在磁盘上存放时相对于文件开头的偏移。
  • 装在基址(Image base)PE装入内存时的基地址。默认情况下,EXE文件在内存中的基地址时0x00400000, DLL文件是0x10000000。这些位置可以通过修改编译选项更改。
  • 虚拟内存地址、映射基址、相对虚拟内存地址的关系:
VA = Image Base + RVA
  • 文件偏移是相对于文件开始处0字节的偏移,相对虚拟地址则是相对于装载基址0x00400000处的偏移。(1)PE文件中的数据按照磁盘数据标准存放,以0x200字节为基本单位进行组织,PE数据节的大小永远是0x200的整数倍。(2)当代码装入内存后,将按照内存数据标准存放,并以0x1000字节为基本单位进行组织,内存中的节总是0x1000的整数倍。
  • 内存中数据节相对于装载基址的偏移量和文件中数据节的偏移量的差异称为节偏移。
文件偏移地址 = 虚拟内存地址(VA) - 装载基址(Image Base) - 节偏移 
             = RVA - 节偏移

注:在可执行文件中,有相当多的地方需要指定内存的地址。例如:引用全局变量时,需要指定它的地址。PE文件尽管有一个首选的载入地址(基地址),但是他们可以载入到进程空间的任意地方,所以不能依赖与PE的载入点。由于这个原因,必须有一个方法来指定一个地址而不是依赖于PE载入点。

结构

D0S部分

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;

e_magic:一个WORD类型,值是一个常数0x4D5A,用文本编辑器查看该值位 MZ,可执行文件必须都是 MZ 开头。

e_lfanew:为32位可执行文件扩展的域,用来表示DOS头之后的PE头相对文件起始地址的偏移。直接指向PE文件头开头,是一个文件偏移地址,PE部分开头固定为‘’PE00‘’ 0x00004550。PE文件采用小端,低位放在低地址。大端是高位放在低地址。

18个 WORD 类型,一个 LONG 类型,WORD为2字节,LONG为4字节

18*2+4+24*2 = 64

DOS头的下面是DOS Stub。整个DOS Stub是一个字节块,其内容随着链接时使用的链接器不同而不同,PE中并没有与之对应的相关结构。常见里面有This program cannot run in dos mode

PE文件头

前四字节为PE文件头标志,可根据 DOS头的 e_lfanew 得到。(一个64位,一个32位)只有一个出现

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

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

DWORD Signature;4字节,小端存储, 00004550 ,代表PE文件头标志 PE00

IMAGE_FILE_HEADER FileHeader;20字节,代表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;

WORD Machine;2字节,该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。

WORD Machine;2字节,该文件的运行平台,是x86、x64还是I64等等,可以是下面值里的某一个。

#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2
#define IMAGE_FILE_MACHINE_AM33              0x01d3
#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon
#define IMAGE_FILE_MACHINE_CEF               0x0CEF
#define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE               0xC0EE

WORD NumberOfSections;2字节,该PE文件中有多少个节,也就是节表中的项数。即有多少区段表。对应.data .text等节

DWORD TimeDateStamp;4字节,PE文件的创建时间,一般有连接器填写。
比如下图3FCCF133转换成十进制就是1070395699,也就是2003-12-03 04:08:19。注意此时间不会随着更改程序某些字节的变化而变化。

DWORD PointerToSymbolTable;4 字节,COFF文件符号表在文件中的偏移,现在基本没用了,值位0。

DWORD NumberOfSymbols;4 字节,符号表的数量。如果有COFF 符号表,它代表其中的符号数目,COFF符号是一个大小固定的结构,如果想找到COFF 符号表的结束位置,则需要这个变量。没有COFF,值为0.

WORD SizeOfOptionalHeader;2字节,紧随其后的PE可选头的大小。下图为 00E0 即为32字节。

WORD Characteristics;2字节,可执行文件的属性,,有选择的通过几个值可以运算得到。( 这些标志的有效值是定义于 winnt.h 内的 IMAGE_FILE_** 的值,具体含义见下表。普通的EXE文件这个字段的值一般是 0100h,DLL文件这个字段的值一般是 210Eh。)多种属性可以通过 “或运算” 使得同时拥有!2字节,对应16个符号位。

0100h,DLL文件这个字段的值一般是 210Eh。)多种属性可以通过 “或运算” 使得同时拥有!

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
    #define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).
    #define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
    #define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
    #define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
    #define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
    #define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
    #define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
    #define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
    #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
    #define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
    #define IMAGE_FILE_SYSTEM                    0x1000  // System File.
    #define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
    #define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
    #define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

可以看出,PE文件头定义了PE文件的一些基本信息和属性,这些属性会在PE加载器加载时用到,如果加载器发现PE文件头中定义的一些属性不满足当前的运行环境,将会终止加载该PE。

PE文件表头,标准 PE 头,20 字节。

PE文件表头可选部分,PE扩展PE头

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;      //  标志字, ROM 映像(0107h),普通可执行文件(010Bh)
    BYTE    MajorLinkerVersion;    // 链接程序的主版本号
    BYTE    MinorLinkerVersion;    // 链接程序的次版本号
    DWORD   SizeOfCode;           // 代码段的长度,如果有多个代码段,则是代码段长度的总和。
    DWORD   SizeOfInitializedData;     // 所有含已初始化数据的节的总大小
    DWORD   SizeOfUninitializedData;   // 所有含未初始化数据的节的大小
    DWORD   AddressOfEntryPoint;       // 程序执行入口 RVA
    DWORD   BaseOfCode;                // 代码的区块的起始RVA
    DWORD   BaseOfData;                // 数据的区块的起始RVA

    //
    // NT additional fields.
    //

    DWORD   ImageBase;                 // 程序的首选装载地址,数据机制
    DWORD   SectionAlignment;          // 内存中的区块的对齐大小,块对齐
    DWORD   FileAlignment;             // 文件中的区块的对齐大小,文件块对齐
    WORD    MajorOperatingSystemVersion;    // 要求操作系统最低版本号的主版本号
    WORD    MinorOperatingSystemVersion;    // 要求操作系统最低版本号的副版本号
    WORD    MajorImageVersion;      // 可运行于操作系统的主版本号
    WORD    MinorImageVersion;      // 可运行于操作系统的次版本号
    WORD    MajorSubsystemVersion;   // 要求最低子系统版本的主版本号
    WORD    MinorSubsystemVersion;    // 要求最低子系统版本的次版本号
    DWORD   Win32VersionValue;         // 莫须有字段,不被病毒利用的话一般为0
    DWORD   SizeOfImage;             // 映像装入内存后的总尺寸
    DWORD   SizeOfHeaders;            // 所有头 + 区块表的尺寸大小
    DWORD   CheckSum;                  // 映像的校检和
    WORD    Subsystem;                 // 可执行文件期望的子系统
    WORD    DllCharacteristics;        // DllMain()函数何时被调用,默认为 0
    DWORD   SizeOfStackReserve;        // 初始化时的栈大小
    DWORD   SizeOfStackCommit;         // 初始化时实际提交的栈大小
    DWORD   SizeOfHeapReserve;          // 初始化时保留的堆大小
    DWORD   SizeOfHeapCommit;          // 初始化时实际提交的堆大小
    DWORD   LoaderFlags;               // 与调试有关,默认为 0
    DWORD   NumberOfRvaAndSizes;    //下边数据目录的项数,这个字段自Windows NT 发布以来一直是16
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;   // 数据目录表

WORD Magic;, 表示可选头的类型。

#define IMAGE_NT_OPTIONAL_HDR32_MAGIC      0x10b  // 32位PE可选头
    #define IMAGE_NT_OPTIONAL_HDR64_MAGIC      0x20b  // 64位PE可选头
    #define IMAGE_ROM_OPTIONAL_HDR_MAGIC       0x107

IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
这个字段可以说是最重要的字段之一,它由16个相同的IMAGE_DATA_DIRECTORY结构组成,虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的节中的,但是这些处于各个节中的数据按照用途可以被分为导出表、导入表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定义多种不同用途的数据块的IMAGE_DATA_DIRECTORY结构的定义很简单,它仅仅指出了某种数据块的位置和长度。

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

VirtualAddress:是一个RVA。 Size:是一个大小。
16个数据目录,数据目录列表的含义如下:

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
    #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
    #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
    #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
    #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
    #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
    #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
    //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
    #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
    #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
    #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
    #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
    #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
    #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
    #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
    #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

在PE文件中寻找特定的数据时就是从这些IMAGE_DATA_DIRECTORY结构开始的,比如要存取资源,那么必须从第3个IMAGE_DATA_DIRECTORY结构(索引为2)中得到资源数据块的大小和位置;同理,如果要查看PE文件导入了哪些DLL文件的哪些API函数,那就必须首先从第2个IMAGE_DATA_DIRECTORY结构得到导入表的位置和大小。

32位PE文件头 为 224 字节,可扩展, 一行 16 字节,14 行

节表

http://image.bubuko.com/info/201409/20180921125127615796.jpg

PE文件中所有节的属性都被定义在节表中,节表由一系列的IMAGE_SECTION_HEADER结构排列而成,该类型的数组

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];     // 8个字节的节区名称
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;      //节区的尺寸
    } Misc;
    DWORD   VirtualAddress;      // 节区的 RVA 地址
    DWORD   SizeOfRawData;        // 在文件中对齐后的尺寸
    DWORD   PointerToRawData;      // 在文件中的偏移量
    DWORD   PointerToRelocations;  // 在OBJ文件中使用,重定位的偏移
    DWORD   PointerToLinenumbers;    // 行号表的偏移(供调试使用地)
    WORD    NumberOfRelocations;    // 在OBJ文件中使用,重定位项数目
    WORD    NumberOfLinenumbers;    // 行号表中行号的数目
    DWORD   Characteristics;        // 节属性如可读,可写,可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

顺序排列的一系列节表数量和节的数量相应数据。

分别都为 40 字节。

节数据

img

参考

PE可执行文件格式详解 - 知乎 (zhihu.com)

[PE文件结构入门到入坟 - FreeBuf网络安全行业门户

posted @ 2021-08-31 17:02  ddddd1234654732  阅读(450)  评论(0)    收藏  举报
Live2D