概述:PE文件结构基础认识之DOS头和PE头(以 rpcrt4.dll 为例剖析)

0x01 前语

PE(Portable Executable),即可移植的履行体。

  • Linux渠道:ELF(Executable and Linking Format)文件结构。
  • Windows渠道下,一切的可履行文件。比如:exe、dll、sys、ocx、com等均适用PE文件结构,这些使用PE文件结构也被称为PE文件。

0x02 PE结构

PE 结构是由若干个杂乱的结构体组合而成的,不是单单的一个结构体那么简单,它的结构就像文件体系的结构是由多个结构体组成的。PE结构 包含的结构体有 DOS 头、PE 标识、文件头、可选头、目录结构、节表等。如下图所示:

【PE】PE文件结构(一)PE结构及头部

DOS头

DOS头中声明用的寄存器(咱们能够看到e_ss、e_sp、e_ip、e_cs仍是16位的寄存器),所以在32位/64为体系中用到的只有两个成员了(第一个和最终一个)。无论是32位仍是64位PE文件,其文件的头部必定是DOS头。巨细为 40H(64字节)。相关结构如下所示:

// WORD 为2字节
// DWORD 为4字节typedef struct _IMAGE_DOS_HEADER {   // DOS .EXE header(※Magic DOS signature MZ(4Dh 5Ah):MZ标记)
  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;

DOS 头又分为两部分,MZ头部DOS存根

MZ头部

  • MZ文件头:IMAGE_DOS_HEADER 结构体,其巨细占64个字节,而且该结构中的最终一个LONG类型e_lfanew成员指向PE文件头的位置为中的PE文件头标志的地址。 DOS头中有两个重要的成员,分别是 e_magice_lfanew,如PE文件结构阐明一图所示:

    • e_magic:对应 MZ标识(固定值) ,也便是 0x5A4D
    • e_lfanew: 指pe的偏移量,对应的PE数据块的基地址

    MZ头部(p9-juejin.byteimg.com/tos-cn-i-k3…)

DOS存根(DOS stub)

DOS头(IMAGE_DOS_HEADER)和PE头(IMAGE_NT_HEADERS)中心的空余位置是一些废物值以及编译器填充的一些 The is program cannot be run in DOS mode.This program must be run under Win32 等信息。这些信息便是 DOS存根。DOS存根实践上是一段汇编代码。用于在DOS环境下发动PE文件时输出一些上述文本。

【PE】PE文件结构(一)PE结构及头部

在pe文件使用的时分,咱们能够把payload写入到当时区域,比如存放咱们的shellcode,在读取时,获取dos头字节数,减去MZ头字节数,即为dos存根字节巨细。然后拿去操作加载shellcode等。

PE头

在MS-DOS头下main,便是PE头,PE头是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,其间包含许多PE装载器用到的重要字段。PE头又分为标准PE头和可选PE头,其同为NT结构的成员:

IMAGE_NT_HEADER数据结构界说:

//NT头
//pNTHeader = dosHeader   dosHeader->e_lfanew;
struct _IMAGE_NT_HEADERS{
  0x00 DWORD Signature;  //PE文件标识:ASCII的"PE"
  0x04 _IMAGE_FILE_HEADER FileHeader;
  0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;
};

依据DOS头的e_lfanew成员咱们就能够找到NT头,NT头的第一个成员SignaturePE(0X50 0X45 0X00 0X00四字节的签名),后两个成员则分别是标准PE头FileHeader_IMAGE_FILE_HEADER)和OptionalHeader可选PE头(_IMAGE_OPTIONAL_HEADER)。

PE头(p3-juejin.byteimg.com/tos-cn-i-k3…)

PE标识 Signature

将文件标识为 PE 映像的 4 字节签名。字节为PE。这个字段是PE文件的标志字段,一般设置成 00004550h,其ASCII码为 PE00,这个字段是PE文件头的开始,前面的DOS_HEADER结构中的字段e_lfanew字段便是指向这里。

文件头 FileHeader

文件头结构体为 _IMAGE_FILE_HEADER,见IMAGE_FILE_HEADER (winnt.h) – Win32 apps | Microsoft Learn。结构体成员变量如下所示:

typedef struct _IMAGE_FILE_HEADER {
  WORD Machine;           //运转渠道
  WORD NumberOfSections;  //文件的区块数目
  DWORD TimeDateStamp;    //文件创立的用时刻戳标识的日期
  DWORD PointerToSymbolTable;     //指向符号表(用于调试)
  DWORD NumberOfSymbols;          //符号表中符号的个数
  WORD SizeOfOptionalHeader;      //IMAGE_OPTIONAL_HEADER32结构巨细
  WORD Characteristics;           //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

参照上述结构体,剖析一下 rpcrt4.dll 的 FileHeader 成员。

PE头存储内容如下所示:

Offset    0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
​
000000E0               50 45 00 00 4C 01 06 00      PE  L  
000000F0  77 7B 41 86 00 00 00 00  00 00 00 00 E0 00 02 21  w{A?    ? !

现在有数据也知道结构体,能够依据存储的内容知道 rpcrt4.dll 的一些信息了(地址是由低位向高位增长的,这里直接复制粘贴了,读者知道这回事就行)

成员变量 Machine NumberOfSections TimeDateStamp PointerToSymbolTable NumberOfSymbols SizeOfOptionalHeader Characteristics
巨细 WORD WORD DWORD DWORD DWORD WORD WORD
4C 01 06 00 77 7B 41 86 00 00 00 00 00 00 00 00 E0 00 02 21
阐明 0x014C 表示当时体系结构类型为 x86 当时PE文件的节数,当时所示有 6 个 当时PE文件的时刻戳 符号表的偏移量(以字节为单位),如果没有 COFF 符号表,则为 0 符号表中的符号数,当时为 0 可选文件头巨细,当时所示有 224 个 图画的特征 0x2102
弥补 机器标识 标识区块的数目,关于区块后边会详细讲 指的便是PE文件创立的事件,这个时刻是指从1970年1月1日到创立该文件的一切的秒数 紧跟着IMAGE_FILE_HEADER后边的数据巨细,这也是一个数据结构,它叫做IMAGE_OPTIONAL_HEADER,其巨细依靠于是64位仍是32位文件。32位文件值一般是00EOh,对于64位值一般为00F0h 一般EXE文件这个字段值为010fh,DLL文件这个字段一般是0210h

最终一个参数图画特征 Characteristics 的值 0x2102 的阐明

  • 0x2000 该图画是一个DLL文件,虽然是可履行文件,但不能直接运转
  • 0x0100 计算机支持32位
  • 0x0002 该文件是可履行文件,(没有未解析的外部引用)

可选头 OptionalHeader

可选PE头紧接着标准PE头,其巨细在标准PE头中给出:巨细为 E0H 即224字节。_IMAGE_OPTIONAL_HEADER结构如下所示:

WinNT.h 中的实践 结构IMAGE_OPTIONAL_HEADER32命名IMAGE_OPTIONAL_HEADER 界说为 IMAGE_OPTIONAL_HEADER32。 可是,如果界说了 _WIN64 ,则 IMAGE_OPTIONAL_HEADER 界说为 IMAGE_OPTIONAL_HEADER64

typedef struct  _IMAGE_OPTIONAL_HEADER
{
    //
    // Standard    fields. 
    //
    /* 0x00*/     WORD   Magic;            // 标志字, ROM 映像(0107h),一般可履行文件(010Bh)
    /* 0x02*/     BYTE   MajorLinkerVersion;     // 链接程序的主版本号
    /* 0x03*/     BYTE   MinorLinkerVersion;     // 链接程序的次版本号
    /* 0x04*/     DWORD  SizeOfCode;         // 一切含代码的节的总巨细
    /* 0x08*/     DWORD  SizeOfInitializedData;    // 一切含已初始化数据的节的总巨细
    /* 0x0c*/     DWORD  SizeOfUninitializedData;   // 一切含未初始化数据的节的巨细
    /* 0x10*/     DWORD  AddressOfEntryPoint;     // 程序履行入口RVA
    /* 0x14*/     DWORD  BaseOfCode;         // 代码的区块的起始RVA
    /* 0x18*/     DWORD  BaseOfData;         // 数据的区块的起始RVA//
    // NT additional fields.  以下是归于NT结构增加的领域。
    //
    /* 0x1c*/     DWORD  ImageBase;          // *程序的首选装载地址
    /* 0x20*/     DWORD  SectionAlignment;      // *内存中的区块的对齐巨细
    /* 0x24*/     DWORD  FileAlignment;        // *文件中的区块的对齐巨细
    /* 0x28*/     WORD   MajorOperatingSystemVersion; // 要求操作体系最低版本号的主版本号
    /* 0x2a*/     WORD   MinorOperatingSystemVersion; // 要求操作体系最低版本号的副版本号
    /* 0x2c*/     WORD   MajorImageVersion;      // 可运转于操作体系的主版本号
    /* 0x2e*/     WORD   MinorImageVersion;      // 可运转于操作体系的次版本号
    /* 0x30*/     WORD   MajorSubsystemVersion;    // 要求最低子体系版本的主版本号
    /* 0x32*/     WORD   MinorSubsystemVersion;    // 要求最低子体系版本的次版本号
    /* 0x34*/     DWORD  Win32VersionValue;      // 莫须有字段,不被病毒使用的话一般为0
    /* 0x38*/     DWORD  SizeOfImage;         // 映像装入内存后的总尺度
    /* 0x3c*/     DWORD  SizeOfHeaders;        // 一切头 区块表的尺度巨细
    /* 0x40*/     DWORD  CheckSum;          // 映像的校检和
    /* 0x44*/     WORD   Subsystem;          // 可履行文件希望的子体系
    /* 0x46*/     WORD   DllCharacteristics;     // DllMain()函数何时被调用,默以为0
    /* 0x48*/     DWORD  SizeOfStackReserve;     // 初始化时的栈巨细
    /* 0x4c*/     DWORD  SizeOfStackCommit;      // 初始化时实践提交的栈巨细
    /* 0x50*/     DWORD  SizeOfHeapReserve;      // 初始化时保留的堆巨细
    /* 0x54*/     DWORD  SizeOfHeapCommit;      // 初始化时实践提交的堆巨细
    /* 0x58*/     DWORD  LoaderFlags;         // 与调试有关,默以为0 
    /* 0x5c*/     DWORD  NumberOfRvaAndSizes;     // 下边数据目录的项数,这个字段自Windows NT发布以来,一直是16
    /* 0x60*/     IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // *数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
​

上述共计 31 个字段,常用的一般是加*的几个。这里仍是简单的按照内存和结构体一一对应剖析看下。

内存如下所示:

【PE】PE文件结构(一)PE结构及头部

成员变量 巨细 阐明 弥补
Magic WORD 0B 01 映像文件的状况,0x01 标识当时是可履行映像
MajorLinkerVersion BYTE 0E 链接器的首要版本号,当时是 14
MinorLinkerVersion BYTE 14 链接器的次要版本号,当时是 20
SizeOfCode DWORD 00 C2 0A 00 如果有多个代码节,则为代码节的巨细(以字节为单位)或一切此类节的总和。
SizeOfInitializedData DWORD 00 EA 00 00 初始化的数据节的巨细。代码节的巨细或一切此类节的总和
SizeOfUninitializedData DWORD 00 00 00 00 未初始化数据节的巨细
AddressOfEntryPoint DWORD 50 D3 03 00 指向入口点函数的指针
BaseOfCode DWORD 00 10 00 00 指向代码部分的最初的指针
BaseOfData DWORD 00 E0 0A 00 指向数据部分最初(相对于图画基数)的指针。
ImageBase DWORD 00 00 F8 4E 图画加载到内存中的第一个字节的首选地址。 此值是 64K 字节的倍数。 DLL 的默认值为 0x10000000。 应用程序的默认值为 0x00400000,0x00010000 Windows CE除外。
SectionAlignment DWORD 00 10 00 00 内存中加载的节的对齐方式(以字节为单位)。 此值有必要大于或等于 FileAlignment 成员。 默认值是体系的页巨细。
FileAlignment DWORD 00 02 00 00 图画文件中各部分的原始数据的对齐方式(以字节为单位)。 该值应为介于 512 和 64K 之间的幂 ((含) )。 默认值为 512。 如果 SectionAlignment 成员小于体系页面巨细,则此成员有必要与 SectionAlignment 相同。
MajorOperatingSystemVersion WORD 0A 00 所需操作体系的主版本号。 0x0A 为 10
MinorOperatingSystemVersion WORD 00 00 所需操作体系的次要版本号。
MajorImageVersion WORD 0A 00 映像的主版本号。
MinorImageVersion WORD 00 00 映像的次要版本号。
MajorSubsystemVersion WORD 0A 00 子体系的主版本号。
MinorSubsystemVersion WORD 00 00 子体系的次要版本号。
Win32VersionValue DWORD 00 00 00 00 此成员是保留的,有必要为 0。
SizeOfImage DWORD 00 F0 0B 00 图画的巨细(以字节为单位),包含一切标头。 有必要是 SectionAlignment 的倍数。
SizeOfHeaders DWORD 00 04 00 00 以下项的组合巨细,舍入为 FileAlignment 成员中指定的值的倍数。
CheckSum DWORD 3B 06 0C 00 映像文件校验和。 以下文件在加载时进行验证:一切驱动程序、在发动时加载的任何 DLL,以及加载到要害体系进程中的任何 DLL。
Subsystem WORD 03 00 运转此映像所需的子体系。 界说了以下值。 0x03 标识 windows 字符膜片式用户界面(CUI)体系
DllCharacteristics WORD 40 41 图画的 DLL 特征。 界说了以下值。
SizeOfStackReserve DWORD 00 00 04 00
SizeOfStackCommit DWORD 00 10 00 00
SizeOfHeapReserve DWORD 00 00 10 00
SizeOfHeapCommit DWORD 00 10 00 00
LoaderFlags DWORD 00 00 00 00
NumberOfRvaAndSizes DWORD 10 00 00 00
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] IMAGE_DATA_DIRECTORY 16个指针地址,巨细为 80H 这个是一个指针类型,指向 IMAGE_DATA_DIRECTORY 结构的指针。会依据传入的索引号回来不同指向的结构体

最终一个成员变量结构体如下所示,详见IMAGE_DATA_DIRECTORY (winnt.h) – Win32 apps | Microsoft Learn

struct _IMAGE_DATA_DIRECTORY{
  DWORD  VirtualAddress;
  DWORD  Size;
};
//占用16*8 = 128Byte = 80H = E0H(可选PE头默认巨细) - 60H(前面一切成员固定占用巨细)

关于最终一个成员变量,可枚举的 IMAGE_NUMBEROF_DIRECTORY_ENTRIES 索引总共有15个,外加一个保留指针。也便是说,在 DataDirectory 位置存放了 16 个指针。能够参阅之前的PE头对照一下,剩余的PE内存刚好存放了 16 个指针,参阅 PE 块的总巨细,剩余的部分巨细也刚好为 80h 。

16 * 8 = 128 = 80H

如下图所示:

【PE】PE文件结构(一)PE结构及头部

节表

下篇文章记载

节表数据

下篇文章记载