PE-Base-1

初识 PE 文件

注:本篇文章为转载,我报错为笔记时没有保存原文章链接。

0x00 前言

PE (Portable Executable),即可移植的执行体。
Linux 平台:ELF(Executable and Linking Format)文件结构。
一般在Windows平台下,所有的可执行文件诸如:exe、dll、sys、ocx、com等均适用PE文件结构。这些使用PE文件结构也被称为PE文件。

0x01 PE 结构

PE 结构是由若干个复杂的结构体组合而成的,不是单单的一个结构体那么简单,它的结构就像文件系统的结构是由多个结构体组成的。

PE 结构包含的结构体有 DOS 头、PE 标识、文件头、可选头、目录结构、节表等。

Windows 下如何判断文件是否是 PE 文件?

  1. 通过导入文件到 c32asm (IDA、OD 当然也可以) 等工具,观察 MZ 头。

  1. 通过 lordpe 等工具。

从 数据管理的角度来看,可以把 PE 文件大致分为两部分,DOS 头、PE 头和节表属于 PE 文件的数据管理结构或数据组织结构部分,而节表数据才是 PE 文件真正的数据部分,其中包含着代码、数据、资源等内容。

从PE结构图中可以看出,PE 结构主要分为 4 大部分(DOS头、PE头、节表、节表数据),其中每个部分又进行了细分,存在若干个小的部分。

DOS头

无论是 32 位或 64 位可执行文件,其文件的头部必定是 IMAGE_DOS_HEADER

IMAGE_DOS_HEADER 数据结构定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

而DOS头又分两部分:

MZ 文件头 和 Dos Stub

MZ文件头: IMAGE_DOS_HEADER 结构体,其大小占 64 个字节,并且该结构中的最后一个 LONG 类型 e_lfanew 成员指向 PE 文件头的位置为中的PE文件头标志的地址。

这里有两个比较有用的成员信息:

1、e_magic,用于判断PE文件的标识。如果不是MZ即不是十六进制值:0x5A4D。计算机存储顺序是低位在前高位在后,所以存储为:0x4D5A。
2、e_lfanew,这里是指pe的偏移量,用于找到pe头的位置。

如下阴影区域:

DOS stub:dos 存根,在 IMAGE_DOS_HEADER 和 IMAGE_NT_HEADERS 之间存在一DOS存根,这其实是一段汇编代码:

PE 文件是运行在 32 位或 64 位操作系统下的。

其功能是当该EXE运行在16位环境下,输出一段文字:”This program cannot be run in DOS mode”,然后并退出该进程。

在 pe 文件利用的时候,我们可以把 payload 写入到当前区域,诸如存放我们的 shellcode,在读取时,获取 dos 头字节数,减去 MZ 头字节数,即为 dos 存根字节大小。然后拿去操作加载 shellcode 等。

PE头

在 MS-DOS 头下 main,就是 PE 头,PE 头是 PE 相关结构 NT 映像头(IMAGE_NT_HEADER)的简称,其中包含许多 PE 装载器用到的重要字段。

IMAGE_NT_HEADER 数据结构定义:

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

参具体含义:

1
Signature

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

1
FileHeader

指定文件头的 IMAGE_FILE_HEADER 结构。

IMAGE_FILE_HEADER 结构

这个字段也是包含几个字段结构,它包含了 PE 文件的一些基本信息,最重要的是其中一个域指出了 IMAGE_OPTIONAL_HEADER 的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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;

在 PE 文件头的后面,1234567 个框分别对应 _IMAGE_FILE_HEADER 结构的七个参数位置以及各自的值。我们需要判断运行平台,就可以通过第一个参数位置的值来判断。
上述 e_lfanew 中,可以在下图中看到,e_lfanew 的值为 0080,这里可以看到 PE 头就在 0080h。
常见标识如下,比如这里的 014c,就是在 Intel I386 机器上运行。

机器 标识
Intel I386 14ch
MIPS R3000 162h
Alpha AXP 184h
Power PC 1F0h
MIPS R4000 184h

1)NumberOfSection,标识区块的数目,关于区块后面会详细讲。
2)TimeDateStamp
这个字段没啥好说的,指的就是PE文件创建的事件,这个时间是指从1970年1月1日到创建该文件的所有的秒数。
3)PointerToSymbolTable。这个字段用的比较少,略
4)NumberOfSymbol。这个字段也用得很少,略
5)SizeOfOptionalHeader:紧跟着IMAGE_FILE_HEADER后面的数据大小,这也是一个数据结构,它叫做IMAGE_OPTIONAL_HEADER,其大小依赖于是64位还是32位文件。32位文件值通常是00EOh,对于64位值通常为00F0h。
6)Characteristics:文件属性,普通EXE文件这个字段值为010fh,DLL文件这个字段一般是0210h。

IMAGE_OPTIONAL_HEADER结构

1
OptionalHeader

指定可选文件头的 IMAGE_OPTIONAL_HEADER 结构。
这个结构是 IMAGE_FILE_HEADER 结构的补充。这两个结构合起来才能对整个 PE 文件头进行描述。左边的 16 位字符表示相对于文件头的偏移量。

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
30
31
32
33
34
35
36
37
38
39
40
typedef struct _IMAGE_OPTIONAL_HEADER 
{
//
// Standard fields.
//
+18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh)
+1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号
+1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号
+1Ch DWORD SizeOfCode; // 所有含代码的节的总大小
+20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小
+24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小
+28h DWORD AddressOfEntryPoint; // 程序执行入口RVA
+2Ch DWORD BaseOfCode; // 代码的区块的起始RVA
+30h DWORD BaseOfData; // 数据的区块的起始RVA
//
// NT additional fields. 以下是属于NT结构增加的领域。
//
+34h DWORD ImageBase; // *********程序的首选装载地址
+38h DWORD SectionAlignment; // *********内存中的区块的对齐大小
+3Ch DWORD FileAlignment; // *********文件中的区块的对齐大小
+40h WORD MajorOperatingSystemVersion; // 要求操作系统最低版本号的主版本号
+42h WORD MinorOperatingSystemVersion; // 要求操作系统最低版本号的副版本号
+44h WORD MajorImageVersion; // 可运行于操作系统的主版本号
+46h WORD MinorImageVersion; // 可运行于操作系统的次版本号
+48h WORD MajorSubsystemVersion; // 要求最低子系统版本的主版本号
+4Ah WORD MinorSubsystemVersion; // 要求最低子系统版本的次版本号
+4Ch DWORD Win32VersionValue; // 莫须有字段,不被病毒利用的话一般为0
+50h DWORD SizeOfImage; // 映像装入内存后的总尺寸
+54h DWORD SizeOfHeaders; // 所有头 + 区块表的尺寸大小
+58h DWORD CheckSum; // 映像的校检和
+5Ch WORD Subsystem; // 可执行文件期望的子系统
+5Eh WORD DllCharacteristics; // DllMain()函数何时被调用,默认为 0
+60h DWORD SizeOfStackReserve; // 初始化时的栈大小
+64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小
+68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小
+6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小
+70h DWORD LoaderFlags; // 与调试有关,默认为 0
+74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来 // 一直是16
+78h DWORD DataDirctory[16]; // ********* 数据目录表
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

这里总共 31 个字段。通常用的就是加 * 字段。

1
2
3
4
5
6
7
8
9
字段6:AddressOfEntryPoint 表 程序入口RVA,即OEP:
``EOP:程序入口点,壳相关概念
``OEP:原本的程序入口点(实际为偏移,+模块基址=实际入口点)
``EP: 被加工后的入口点
字段9:ImageBase 表 模块加载基地址,exe默认0x400000,dll默认0x10000000
``建议装载地址:exe映射加载到内存中的首地址= PE 0处,即实例句柄hInstance
``一般而言,exe文件可遵从装载地址建议,但dll文件无法满足
尾字段:DataDirectory 表 数据目录表,用来定义多种不通用处的数据块。
``存储了PE中各个表的位置,详情参考IMAGE_DIRECTORY_ENTRY...系列宏

这里可以知道我们确定的 PE 文件头在 0080h 处,通过偏移计算,我们可以得到 IMAGE_OPTIONAL_HEADER 结构的的首个字段在 80h+18h=98h 的地方。这里直接通过 c32asm 跳转到对应位置。如图所示:

然后比较重要的就是最后一个成员,即数据目录表,大小为 16,每个元素都是一个 IMAGE_DATA_DIRECTORY 结构体,这里看到是一个数组类型。

它的定义如下:

1
2
3
4
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //所指向的数据结构的虚拟地址
DWORD Size; //数据结构的大小
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

在winnt.h中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define IMAGE_DIRECTORY_ENTRY_EXPORT   //0 导出表
#define IMAGE_DIRECTORY_ENTRY_IMPORT //1 导入表
#define IMAGE_DIRECTORY_ENTRY_RESOURCE //2 资源目录
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION //3 异常目录
#define IMAGE_DIRECTORY_ENTRY_SECURITY //4 安全目录
#define IMAGE_DIRECTORY_ENTRY_BASERELOC //5 重定位基本表
#define IMAGE_DIRECTORY_ENTRY_DEBUG //6 调试目录
#define IMAGE_DIRECTORY_ENTRY_COPYRIGHT //7 描术字串
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR //8 机器值
#define IMAGE_DIRECTORY_ENTRY_TLS //9 TLS目录
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG //10 载入配值目录
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT //11 绑定输入表
#define IMAGE_DIRECTORY_ENTRY_IAT //12 导入地址表
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT //13 延迟载入描述
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR //14 COM信息

在这个数据目录结构体中只有两个成员VirtualAddress和Size,这两个成员的含义比较简单,VirtualAddress指定了数据块的相对虚拟地址(RVA)。Size则指定了该数据块的大小,有时并不是该类型数据的总大小,可能只是该类型数据一个数据项的大小。这两个成员(主要是VirtualAddress)成为了定位各种表的关键,所以一定要知道每个数组元素所指向的数据块类型,以下表格就是它的对应关系:

下面是 DataDirctory[16] 即数据目录表的各个成员

索 引 索引值在 Windows.inc 中的预定义值 对应的数据块 偏移量
0 IMAGE_DIRECTORY_ENTRY_EXPORT 导出表 78h
1 IMAGE_DIRECTORY_ENTRY_IMPORT 导入表 80h
2 IMAGE_DIRECTORY_ENTRY_RESOURCE 资源 88h
3 IMAGE_DIRECTORY_ENTRY_EXCEPTION 异常(具体资料不详) 90h
4 IMAGE_DIRECTORY_ENTRY_SECURITY 安全(具体资料不详) 98h
5 IMAGE_DIRECTORY_ENTRY_BASERELOC 重定位表 A0h
6 IMAGE_DIRECTORY_ENTRY_DEBUG 调试信息 A8h
7 IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 版权信息 B0h
8 IMAGE_DIRECTORY_ENTRY_GLOBALPTR 具体资料不详 B8h
9 IMAGE_DIRECTORY_ENTRY_TLS Thread Local Storage C0h
10 IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 具体资料不详 C8h
11 IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 具体资料不详 D0h
12 IMAGE_DIRECTORY_ENTRY_IAT 导入函数地址表 D8h
13 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 具体资料不详 E0h
14 IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 具体资料不详 E8h
15 未使用保留
Contents
  1. 1. 初识 PE 文件
    1. 1.1. 0x00 前言
    2. 1.2. 0x01 PE 结构
    3. 1.3. Windows 下如何判断文件是否是 PE 文件?
    4. 1.4. DOS头
    5. 1.5. PE头
    6. 1.6. IMAGE_FILE_HEADER 结构
    7. 1.7. IMAGE_OPTIONAL_HEADER结构
|