概述: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 标识、文件头、可选头、目录结构、节表等。如下图所示:
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_magic
和e_lfanew
,如PE文件结构阐明一图所示:-
e_magic
:对应 MZ标识(固定值) ,也便是0x5A4D
-
e_lfanew
: 指pe的偏移量,对应的PE数据块的基地址
-
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文件使用的时分,咱们能够把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头的第一个成员Signature
是PE
(0X50 0X45 0X00 0X00四字节的签名),后两个成员则分别是标准PE头FileHeader
(_IMAGE_FILE_HEADER
)和OptionalHeader
可选PE头(_IMAGE_OPTIONAL_HEADER
)。
(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 个字段,常用的一般是加*
的几个。这里仍是简单的按照内存和结构体一一对应剖析看下。
内存如下所示:
成员变量 | 巨细 | 值 | 阐明 | 弥补 |
---|---|---|---|---|
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
如下图所示:
节表
下篇文章记载
节表数据
下篇文章记载