资料来源:《逆向工程核心原理》和PE文件结构格式详解(完整版)【逆向编程】 (youtube.com)

一、PE文件基础

1.可执行文件

Windows:PE

Linux: elf

2.PE文件特征

PE文件指纹

1735729100495.png

3.PE结构

1735729120355.png

DOS头

  • DOS MZ头 IMAGE_DOS_HEADER(64字节)

1735729155568.png

e_magic:4D5A是DOS签名,不可改

e_lfanew:78指向PE头开始位置,要改要一起改。

上面两个是PE指纹,操作系统用来识别是否是PE文件,其他地方可以随便改,因为IMAGE_DOS_HEADER是给16位平台看的,而我们现在的环境大部分是32位或者64位。

  • MS_DOS Stu,DOS存根,用来给链接器插入数据,随便改

1735729196266.png

NT头 IMAGE_NT_HEADERS

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; ``// PE标识
IMAGE_FILE_HEADER FileHeader; ``// 标准PE头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; ``// 扩展PE头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

PE标识 Signature 4字节

不可改,操作系统启动程序的时候识别这个标识。

1735729196266.png

标准PE头 IMAGE_FILE_HEADER(20字节)

1
2
3
4
5
6
7
8
9
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 可以运行在什么样的CPU上
WORD NumberOfSections; // 表示节的数量
DWORD TimeDateStamp; // 编译器填写的时间戳
DWORD PointerToSymbolTable; // 调试相关
DWORD NumberOfSymbols; // 调试相关
WORD SizeOfOptionalHeader; // 扩展PE头的大小
WORD Characteristics; // 文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

1735729302371.png

1735729314190.png

1735729331600.png

1
2
3
4
5
6
64 86 ->8664 代表在x64上运行
0F 00 有0x0F个节区
84 D7 68 65 编译器写的时间戳,和文件无关,随便改
调试不管
F0 00 扩展PE头大小,可改
22 00->0022-> 0000 0000 0010 0010 第2位,第6位有值 对应数据位1,5 分别代表文件可执行,应用程序可以处理大于2GB的地址(代表64位)

扩展PE头 IMAGE_OPTIONAL_HEADER

扩展PE头结构&不同编译器上的差异

32位上是224字节(E0)(可扩展)

64位是F0

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
//32位为例
typedef struct _IMAGE_OPTIONAL_HEADER32 {
WORD Magic; // 标志:PE32 是 0x10B
BYTE MajorLinkerVersion; // 链接器的主版本号
BYTE MinorLinkerVersion; // 链接器的次版本号
DWORD SizeOfCode; // 代码段的大小(没用)
DWORD SizeOfInitializedData; // 初始化数据段的大小(没用)
DWORD SizeOfUninitializedData;// 未初始化数据段的大小(没用)
DWORD AddressOfEntryPoint; // 程序入口点的 RVA
DWORD BaseOfCode; // 代码段的起始 RVA(没用)
DWORD BaseOfData; // 数据段的起始 RVA(没用)
DWORD ImageBase; // 内存镜像基址
DWORD SectionAlignment; // 内存对齐
DWORD FileAlignment; // 文件对齐
WORD MajorOperatingSystemVersion; // OS 主版本号
WORD MinorOperatingSystemVersion; // OS 次版本号
DWORD SizeOfImage; // 镜像的总大小
DWORD SizeOfHeaders; // 头+节表按照文件对齐后的总大小
DWORD CheckSum; // 校验和
WORD Subsystem; // 子系统(如 GUI、CUI)
WORD DllCharacteristics; // DLL 属性
DWORD SizeOfStackReserve; // 堆栈保留大小
DWORD SizeOfStackCommit; // 堆栈提交大小
DWORD SizeOfHeapReserve; // 堆保留大小
DWORD SizeOfHeapCommit; // 堆提交大小
DWORD LoaderFlags; // 装载器标志(通常为 0)
DWORD NumberOfRvaAndSizes; // 数据目录项数
IMAGE_DATA_DIRECTORY DataDirectory[16]; // 表,结构体数组
} IMAGE_OPTIONAL_HEADER32;

字段名称 32 位 PE(PE32) 64 位 PE(PE32+) 描述
Magic 0x10B 0x20B 标识 PE 文件是 32 位(PE32)还是 64 位(PE32+)。
AddressOfEntryPoint 存在 存在 程序入口点的 RVA(相对虚拟地址)。
BaseOfCode 存在 存在 代码段的起始 RVA。
BaseOfData 存在 不存在 数据段的起始 RVA,仅在 PE32 中存在
ImageBase 32 位地址(默认 0x00400000) 64 位地址(默认 0x0000000140000000) 可执行文件加载到内存中的首地址。
SizeOfStackReserve 32 位值 64 位值 为线程的堆栈预留的大小。
SizeOfHeapReserve 32 位值 64 位值 为堆分配的保留大小。

1735729389033.png

  • Magic

2个字节,文件的标志

32 位:10B

64 位:20B

  • AddressOfEntryPoint

4个字节,程序的入口点地址,即执行开始的位置。

1735729398902.png

  • ImageBase

4个字节,程序加载的基地址。

1735729409663.png

AddressOfEntryPoint:042CE910

imagebase:00000010

程序执行入口:(EIP)042CE910+00000010=042CE920

  • SectionAlignment

节区的内存对齐大小,节区在内存中的最小大小。

  • FileAlignment

节区的文件对齐大小,节区在磁盘文件中的最小单位。

  • SizeOfImage

表示在内存中整个PE文件映射的大小(包括所有节区和头信息),可比实际的值大。内存对齐以后是SectionAlignment或者FileAlignment的整数倍。

  • SizeOfHeaders

PE 文件头的大小。是FileAlignment的整数倍

  • CheckSum

校验和,系统用来检测文件是否被修改

  • Subsystem

程序的子系统类型(例如,Windows GUI 或控制台应用程序),用来表示PE文件的特性。

节表

IMAGE_SECTION_HEADER (40字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[8]; // 节名称(最多 8 字节,可能没有结束符)
union {
DWORD PhysicalAddress; // 实际已弃用,常为 0
DWORD VirtualSize; // 节的实际大小(内存中的大小)
} Misc;
DWORD VirtualAddress; // 节在内存中的 RVA(相对虚拟地址)
DWORD SizeOfRawData; // 节在文件中的大小(以 FileAlignment 对齐)
DWORD PointerToRawData; // 节在文件中的偏移(以 FileAlignment 对齐)
DWORD PointerToRelocations; // 重定位表的文件偏移(通常为 0,已弃用)
DWORD PointerToLinenumbers; // 调试信息的文件偏移(通常为 0)
WORD NumberOfRelocations; // 重定位条目数量(通常为 0)
WORD NumberOfLinenumbers; // 调试行号条目数量(通常为 0)
DWORD Characteristics; // 节的属性标志(权限、类型等)
} IMAGE_SECTION_HEADER;

红色框出来的是扩展PE头,下面就是节表

1735729466460.png

1735729479345.png

  • Name

8字节,当前节的名字,可以随意更改。

image

当前这个节未对齐时的大小,即实际大小。

实际大小有可能会比Size of Raw Data大,因为未初始化的全局变量在文件中是不占空间的,但是在内存里是有位置的。

Q:在内存中展开时以什么为基准呢?

A:谁大按谁,如果Vitual Size>Size of Raw Data,则按照Vitual Size展开,反之则按照Size of Raw Data。

  • VirtualAddress(RVA)

在内存中的偏移地址,加上ImageBase则是内存中的真实地址。

  • Raw Size(Size of Raw Data)

文件对齐后的大小

  • Raw Address(File Pointer to Raw Data)

当前节在文件中起始位置

  • Reloc Address

节的重定位表(如果有的话)在文件中的偏移地址。

  • Line NumbersRelocation NumberLine Number Numbers

与调试信息和重定位表相关。

  • Characteristics

节区属性

image

PE文件的两种状态

文件对齐和内存对齐的差异:

image

4、RVA和FOA的转换

VA:虚拟内存的绝对地址。

RVA:相对虚拟地址,从ImageBase开始的相对地址。

FOA:文件偏移地址

Q:想改边一个全局变量的初始值,应该怎么做?

A:先区分全局变量有无初始值。如果有初始值,全局变量储存在文件中,如果没有初始值,在文件里就没有位置,在内存展开时才会分配位置。

<1>、判断RVA是否在头部,在的话直接返回

FOA=RVA

<2>、判断RVA在哪一个节

RVA>=节.VA

RVA<=节.VA+当前节内存对其后大小

差值=RVA-节.VA

<4>、FOA=节.PointerToRawData+差值

看一下书上的例子,实例下面导入表的计算也有提到

image

image

算完RAW记得查看是否和内存中在同一节区!!!如上图Q3

5、手撕PE文件

(1)在空白区添加代码

(2)扩大节

(3)删除节

(4)新添节

(5)合并节

6、导出表&导入表

前置知识

首先明白,一个可执行程序是有多个pe文件组成的。

导入表(IMP):PE文件引用了哪些文件

  • 导入地址表IAT:储存导入函数在内存里的实际应用。
  • 导入名称表INT:每个dll导入描述符,储存函数名或者序号,用于加载解析函数地址。
组件 内容(磁盘) 内容(内存) 作用
导入表(IMP) 所有导入DLL的描述信息 不变 管理所有导入的DLL和函数引用
导入名称表(INT) 函数名称/序号的RVA 不变(或不存在于内存) 提供加载时解析函数地址的线索
导入地址表(IAT) 初始为函数名称/序号的RVA 实际函数地址 运行时跳转到目标函数的地址表
1
2
3
4
5
6
7
8
Import Table (IMP)
├─ IMAGE_IMPORT_DESCRIPTOR 1 (DLL1)
│ ├─ OriginalFirstThunk → INT (函数名称/序号)
│ └─ FirstThunk → IAT (初始同INT,加载后为地址)
├─ IMAGE_IMPORT_DESCRIPTOR 2 (DLL2)
│ ├─ OriginalFirstThunk → INT
│ └─ FirstThunk → IAT
└─ ...

导出表(EAT):当前的PE文件储存了哪些函数给其他文件用。

Q:导出表在哪?

A:再扩展PE头最后一个成员

image

Dll

动态链接库

加载DLL的两种方式

  • 显式链接:程序使用DLL时候加载,使用完释放内存。
  • 隐式链接:程序开始时一同加载DLL,程序终止时释放内存。

导出表

image

image

image

先找到导出表位置

image

image

导入表

确定依赖的函数

导入表位置

image

image

导入表结构

image

image

Name

字符串指针,指向导入函数所属的库文件名字。

image

image

RVA要转成FOA,参考下面的实际计算

image

因为指向的是assic码的字符串,所以到第一个00结束

OringinalFirstThunk-INT

导入名称表image

FirstThunk-IAT

导入地址表

实际计算

image

Export Directory RVA:93 5D 82 09->0x09825D93(imagebase:0x00000010)查了一下再rdata段->FOA:0x09825D83

Export Directory Size:00033669

image

看了010半天不对,dumpbin /headers看了一下,然后又开了个exe,发现这个爆红的意思是typora.exe没有导入表导出表。。。(也有可能有加壳?die看了一下没有,但是这个地址太大了不正常)

1
dumpbin /headers "D:\Typora\Typora\Typora.exe"

换个文件来

image

Import Directory RVA:0x00003824,在.rdata段,rdata段的RVA是0x00003000,所以相对地址就是0x00000824,rdata段的raw address是0x00001A00,所以FOA是0x00002224,大小是C8字节

Import Directory RVA:0x00003824

.rdata段的 RVA:0x00003000

.rdata段的 Raw Address:0x00001A00

.rdata段的 Raw Size:C8 字节(即 200 字节)

1
2
3
相对地址 = Import Directory RVA - .rdata段的 RVA = 0x00003824 - 0x00003000 = 0x00000824

FOA = .rdata段的 Raw Address + 相对地址 =0x00001A00 + 0x00000824 = 0x00002224

image