VMP是一种软件加固方法
Virtual Machine Protect. 虚拟机保护 ,可以将汇编指令转化为自定义指令集,虚拟指令涉及上百万条汇编指令,极大增强pj难度。
由win版本的和linux,安卓版本的。他们的软件实现方法和厂家都不一样,但是原理相同。
win具体的软件由pmvrotect2.x 3.x 3.5。vmp加密时只是对之前的so进行新增加密节区,不修改节区。
vmpdumper可以脱出汇编代码但是不能修复vmp节区和导出表,加密字符串。需要手动修复。
虚拟指令、虚拟栈、虚拟寄存器。
msdn。开发文档
<windows程序设计>
<windows核心编程>
<win32汇编语言程序设计>
PE 文件结构图例
text
+-----------------------------------+ <-- 文件开始 (Offset 0)
| IMAGE_DOS_HEADER |
|-----------------------------------|
| - e_magic: "MZ" (0x5A4D) | <- DOS 可执行文件签名
| - e_lfanew: 指向 NT 头部的偏移量 | <- 这是通往 PE 头的关键指针!
+-----------------------------------+
| DOS Stub Program | <- 一小段 DOS 程序,通常显示 "This program cannot be run in DOS mode."
+-----------------------------------+ <-- e_lfanew 指向的位置
| IMAGE_NT_HEADERS |
| +-----------------------------+ |
| | Signature | |
| | - "PE\0\0" (0x00004550) | | <- PE 文件签名
| +-----------------------------+ |
| | IMAGE_FILE_HEADER | |
| |-----------------------------| |
| | - Machine: (e.g., 0x8664) | | <- 目标 CPU 架构 (如 x64)
| | - NumberOfSections: | | <- 后面跟着的节区数量
| | - TimeDateStamp: | | <- 链接时间戳
| | - SizeOfOptionalHeader: | | <- 下一个头的大小
| | - Characteristics: | | <- 文件属性 (如 DLL, Executable)
| +-----------------------------+ |
| | IMAGE_OPTIONAL_HEADER | | <- 对于操作系统加载器至关重要
| |-----------------------------| |
| | - Magic: (e.g., 0x20B) | | <- 标识 PE32(0x10B) 或 PE32+(0x20B)
| | - AddressOfEntryPoint: | | <- 程序执行入口 RVA
| | - ImageBase: | | <- 映像的首选加载地址
| | - SectionAlignment: | | <- 内存中节区的对齐方式
| | - FileAlignment: | | <- 文件中节区的对齐方式
| | - SizeOfImage: | | <- 内存中整个映像的大小
| | - SizeOfHeaders: | | <- 所有头部的总大小
| | - Subsystem: (e.g., GUI/CUI) | | <- 要求子系统 (如 Windows GUI)
| | - NumberOfRvaAndSizes: | | <- 数据目录项的数量
| |-----------------------------| |
| | IMAGE_DATA_DIRECTORY | | <- 一个结构体数组
| | [0] Export Directory | |
| | [1] Import Directory | | <- 指向导入函数信息
| | [2] Resource Directory | | <- 指向资源 (图标、字符串等)
| | [3] Exception Directory | |
| | ... (共 16 个) ... | |
| +-----------------------------+ |
+-----------------------------------+
| IMAGE_SECTION_HEADER[0] | <- 第一个节区头 (如 .text)
| - Name: ".text\0\0\0" | <- 节区名称
| - VirtualAddress: | <- 内存中的 RVA
| - SizeOfRawData: | <- 在文件中的大小
| - PointerToRawData: | <- 在文件中的偏移
| - Characteristics: | <- 节区属性 (如 可执行、可读)
+-----------------------------------+
| IMAGE_SECTION_HEADER[1] | <- 第二个节区头 (如 .data)
| - Name: ".data\0\0\0" |
| - ... |
+-----------------------------------+
| ... | <- 其他节区头 (共 NumberOfSections 个)
+-----------------------------------+ <-- SizeOfHeaders 标记的头部结束位置
| 节区数据开始 |
| |
| .text 节区原始数据 | <- 文件偏移由 PointerToRawData 指定
| (存放代码) |
| |
| .data 节区原始数据 | <- 文件偏移由 PointerToRawData 指定
| (存放全局变量) |
| |
| .rsrc 节区原始数据 |
| (存放资源) |
| |
| ... |
+-----------------------------------+ <-- 文件结束
各组成部分的简明解释
-
IMAGE_DOS_HEADER (DOS 头)
-
目的:为了保持与古老 DOS 系统的兼容性。
-
关键成员:
e_lfanew字段,它包含了指向真正的 PE 头 (IMAGE_NT_HEADERS) 的文件偏移量。
-
-
IMAGE_NT_HEADERS (NT 头)
-
目的:PE 文件的正式入口和核心描述符。
-
包含三部分:
-
Signature:一个 "PE\0\0" 的签名,标识这是一个 PE 文件。
-
IMAGE_FILE_HEADER (文件头):描述了文件的全局属性,如目标机器类型、节区数量、创建时间等。
-
IMAGE_OPTIONAL_HEADER (可选头):虽然叫"可选",但对于可执行文件是必需的。它包含了程序加载和运行所需的关键信息。
-
-
-
IMAGE_OPTIONAL_HEADER (可选头)
-
目的:为操作系统加载器提供如何准备和执行程序的信息。
-
关键成员:入口点地址、映像基址、内存/文件对齐值、子系统等。
-
它末尾的 IMAGE_DATA_DIRECTORY (数据目录) 是一个非常重要的表格,它指出了其他重要数据结构(如导入表、导出表、资源表)在文件中的位置和大小。
-
-
IMAGE_DATA_DIRECTORY (数据目录)
-
目的:作为指向其他重要数据的"目录"或"索引"。
-
结构:一个由16个相同结构组成的数组。每个结构包含一个 RVA(相对虚拟地址) 和 Size。
-
例如:第二个条目(索引1)是 Import Directory,加载器通过它找到所有需要从其他DLL导入的函数列表。
-
-
IMAGE_SECTION_HEADER (节区头)
-
目的:描述文件中的各个"节区"。节区是实际存储代码、数据、资源等内容的部分。
-
数量:由
IMAGE_FILE_HEADER中的NumberOfSections指定。 -
关键成员:
-
Name:节区名称(如.text,.data,.rdata)。 -
VirtualAddress:该节区加载到内存后的 RVA。 -
PointerToRawData:该节区在磁盘文件中的原始数据偏移。 -
Characteristics:节区属性(如可读、可写、可执行)。
-
-
总结与流程
操作系统加载一个 PE 文件的简化流程如下:
-
读取
IMAGE_DOS_HEADER,找到e_lfanew。 -
跳到
e_lfanew位置,验证 "PE" 签名,读取IMAGE_NT_HEADERS。 -
从
IMAGE_FILE_HEADER知道有多少个节区。 -
从
IMAGE_OPTIONAL_HEADER获取关键信息(如入口点、映像大小、数据目录)。 -
遍历
IMAGE_SECTION_HEADER数组,了解每个节区在文件和内存中的映射关系。 -
根据节区头的信息,将文件的各个节区(代码、数据等)映射到内存的相应位置。
-
通过
IMAGE_DATA_DIRECTORY找到导入表,解析并填充所有需要的外部函数地址。 -
最后,跳转到
AddressOfEntryPoint指向的地址,程序开始执行。
这个结构确保了 PE 文件既能在磁盘上高效存储,又能在内存中正确加载和执行。
PE文件头结构图解 + 白话文秒懂
📊 完整结构总览
text
┌─────────────────────────────────────────────────────────┐
│ DOS Header (64字节) │ ← 开头的"MZ"标记
│ "这是个老式DOS程序" 的伪装外壳 │
├─────────────────────────────────────────────────────────┤
│ DOS Stub (可变) │ │ "This program cannot be run in DOS mode" │ ← 在DOS下运行会看到的提示 ├─────────────────────────────────────────────────────────┤ │ PE Signature (4字节) │ │ "PE\0\0" │ ← 真正的PE文件标记 ├─────────────────────────────────────────────────────────┤ │ File Header (20字节) │ │ 记录机器类型、节数量、时间戳等基本信息 │ ├─────────────────────────────────────────────────────────┤ │ Optional Header (224/240字节) │ │ 记录程序入口点、内存布局、导入导出等关键信息 │ ├─────────────────────────────────────────────────────────┤ │ Section Table (每节40字节) │ │ .text .data .rdata .rsrc 等节的"目录" │ ├─────────────────────────────────────────────────────────┤ │ │ │ Section 1 (.text) │ ← 代码区 │ 你写的代码在这里 │ │ │ ├─────────────────────────────────────────────────────────┤ │ Section 2 (.data) │ ← 数据区 │ 全局变量在这里 │ ├─────────────────────────────────────────────────────────┤ │ Section 3 (.rsrc) │ ← 资源区 │ 图标、对话框、字符串在这里 │ └─────────────────────────────────────────────────────────┘
🔍 详细结构拆解
1️⃣ DOS Header (IMAGE_DOS_HEADER)
text
偏移 大小 字段名 白话文解释
+0x00 2字节 e_magic "MZ" 标记(0x5A4D)------ 所有PE文件必须以这两个字母开头
+0x02 58字节 [其他DOS字段] 基本没用,为了兼容古董DOS系统
+0x3C 4字节 e_lfanew **超重要!** 指向真正的PE头在哪里
白话:
这是个"假门面",为了让Windows程序能在老DOS系统上显示错误提示,而不是直接崩溃。
最重要的是最后那个
e_lfanew,它告诉系统:"真正的PE头在文件偏移XXX处"。
2️⃣ PE Signature (4字节)
text
+0x00 4字节 Signature "PE\0\0" (0x50450000)
白话:
就像盖了个"认证章",证明"我是正宗的Windows程序"。
3️⃣ File Header (IMAGE_FILE_HEADER - 20字节)
text
偏移 大小 字段名 白话文解释
+0x00 2字节 Machine CPU类型(0x14C=x86, 0x8664=x64)
+0x02 2字节 NumberOfSections 这个程序有几个"节"(通常3-6个)
+0x04 4字节 TimeDateStamp 程序编译的时间戳
+0x08 4字节 PointerToSymbolTable 调试符号表位置(发布版通常是0) +0x0C 4字节 NumberOfSymbols 符号数量 +0x10 2字节 SizeOfOptionalHeader 下一个头的大小(32位=224, 64位=240) +0x12 2字节 Characteristics 文件属性标志
白话:
这是"身份证"部分:
- 告诉系统这是32位还是64位程序
- 有几个代码/数据分区
- 什么时候编译的
- 是个EXE还是DLL(Characteristics字段)
Characteristics 常见标志:
text
0x0002 IMAGE_FILE_EXECUTABLE_IMAGE 可执行文件(不是obj)
0x0100 IMAGE_FILE_32BIT_MACHINE 32位程序
0x2000 IMAGE_FILE_DLL 这是个DLL文件
4️⃣ Optional Header (IMAGE_OPTIONAL_HEADER - 最重要!)
标准字段部分
text
偏移 大小 字段名 白话文解释
+0x00 2字节 Magic 0x10B(32位) / 0x20B(64位)
+0x02 1字节 MajorLinkerVersion 编译器版本
+0x03 1字节 MinorLinkerVersion
+0x04 4字节 SizeOfCode 代码段总大小 +0x08 4字节 SizeOfInitializedData 已初始化数据大小 +0x0C 4字节 SizeOfUninitializedData未初始化数据大小 +0x10 4字节 AddressOfEntryPoint **入口点!程序从这里开始执行** +0x14 4字节 BaseOfCode 代码段起始地址 +0x18 4字节 BaseOfData 数据段起始地址(仅32位)
Windows专用字段部分
text
偏移 大小 字段名 白话文解释
+0x1C 4/8字节 ImageBase 程序希望加载到内存的哪个地址
+0x20 4字节 SectionAlignment 节在内存中的对齐单位(通常0x1000=4KB)
+0x24 4字节 FileAlignment 节在文件中的对齐单位(通常0x200=512字节)
+0x28 8字节 [操作系统版本号] +0x30 8字节 [程序版本号] +0x38 8字节 [子系统版本号] +0x40 4字节 Win32VersionValue 保留(总是0) +0x44 4字节 SizeOfImage 程序加载到内存后的总大小 +0x48 4字节 SizeOfHeaders 所有头的总大小 +0x4C 4字节 CheckSum 校验和(驱动必须正确,普通程序可为0) +0x50 2字节 Subsystem 子系统类型 +0x52 2字节 DllCharacteristics DLL特性标志 +0x54 16字节 [栈/堆大小设置] +0x64 4字节 NumberOfRvaAndSizes 数据目录数量(通常是16)
Subsystem 子系统:
text
1 = Native(驱动程序)
2 = GUI(窗口程序)
3 = CUI(控制台程序,黑框框)
DllCharacteristics 重要标志:
text
0x0040 DYNAMIC_BASE 支持ASLR(地址随机化)
0x0100 NX_COMPAT 支持DEP(数据执行保护)
0x0400 NO_SEH 不使用SEH异常处理
0x8000 TERMINAL_SERVER_AWARE 终端服务器感知
5️⃣ 数据目录表 (Data Directory - 16个条目)
text
索引 名称 作用
[0] Export Table 导出表(DLL导出的函数列表)
[1] Import Table 导入表(程序要用哪些DLL的哪些函数)
[2] Resource Table 资源表(图标、字符串、对话框)
[3] Exception Table 异常处理表 [4] Certificate Table 数字签名 [5] Base Relocation Table 重定位表(修正地址用) [6] Debug 调试信息 [7] Architecture 架构特定数据 [8] Global Ptr 全局指针寄存器 [9] TLS Table 线程局部存储 [10] Load Config Table 加载配置 [11] Bound Import 绑定导入 [12] IAT 导入地址表(最常被破解者关注!) [13] Delay Import Descriptor 延迟导入 [14] CLR Runtime Header .NET程序专用 [15] Reserved 保留
每个条目结构:
text
+0x00 4字节 VirtualAddress 数据在内存中的RVA
+0x04 4字节 Size 数据的大小
6️⃣ 节表 (Section Table - 每节40字节)
text
偏移 大小 字段名 白话文解释
+0x00 8字节 Name 节名称(如".text"、".data")
+0x08 4字节 VirtualSize 在内存中的实际大小
+0x0C 4字节 VirtualAddress 在内存中的起始地址(RVA)
+0x10 4字节 SizeOfRawData 在文件中的大小 +0x14 4字节 PointerToRawData 在文件中的偏移 +0x18 12字节 [重定位/行号信息] 通常为0 +0x24 4字节 Characteristics 节的属性(可读/可写/可执行)
常见节名称:
text
.text 代码段(你的程序逻辑) 可读+可执行
.data 已初始化数据(全局变量) 可读+可写
.rdata 只读数据(常量字符串) 只读
.bss 未初始化数据 可读+可写
.rsrc 资源(图标、菜单、字符串) 只读 .reloc 重定位信息 只读 .idata 导入表(需要的DLL函数) 可读+可写 .edata 导出表(DLL导出的函数) 只读
Characteristics 节属性:
text
0x00000020 CODE 包含代码
0x00000040 INITIALIZED_DATA 包含已初始化数据
0x00000080 UNINITIALIZED_DATA 包含未初始化数据
0x20000000 EXECUTE 可执行
0x40000000 READ 可读 0x80000000 WRITE 可写
🎯 关键概念白话解释
RVA (Relative Virtual Address) - 相对虚拟地址
text
假设程序被加载到内存地址 0x00400000
某个函数的RVA是 0x1000
那么这个函数的实际内存地址 = 0x00400000 + 0x1000 = 0x00401000
文件偏移 vs 内存地址
text
文件偏移:在硬盘上的.exe文件中的位置
内存地址:程序运行时在内存中的位置
需要通过节表来转换!
对齐 (Alignment)
text
FileAlignment = 0x200 (512字节)
→ 文件中每个节的起始位置必须是512的倍数
SectionAlignment = 0x1000 (4096字节)
→ 内存中每个节的起始位置必须是4KB的倍数
🛠️ 实战:用十六进制编辑器看PE头
text
偏移 十六进制 解释
00000000: 4D 5A 90 00 03 00 00 00... MZ = DOS头开始
0000003C: E0 00 00 00 e_lfanew = 0xE0
000000E0: 50 45 00 00 PE签名
000000E4: 4C 01 06 00 Machine=0x014C(x86), Sections=6 000000F8: 0B 01 Magic=0x10B(32位) 00000100: 00 10 00 00 AddressOfEntryPoint=0x1000
📝 总结:PE头三句话秒懂
- DOS头:"我伪装成DOS程序,但真正的内容在后面"
- PE头:"我是Windows程序,32位/64位,从地址XXX开始执行"
- 节表:"我的代码在.text节,数据在.data节,资源在.rsrc节"
🔥 最关心的地方
text
✅ AddressOfEntryPoint → 程序从哪里开始跑
✅ Import Table (IAT) → 调用了哪些关键API(MessageBox? RegCreateKey?)
✅ .text 节 → 注册验证代码藏在这里
✅ Characteristics → 能不能改这个节的数据
✅ Resource Table → 修改图标、字符串、对话框
希望这个图解能帮你快速理解PE结构!如果要深入学习,推荐工具:
- CFF Explorer (查看PE结构最直观)
- PE-bear
- 010 Editor (配合PE模板)
- windowspe 权威指南
了解所有编译器和反汇编特征
了解vmp入口,区段,反汇编特征
看特征的方法主要有两个,一个是入口代码的在节区中的位置,入口代码中包含的指令和调用的外部接口
vc6特征和易语言
64-ia-32-architectures-software-developer-manualintel开发手册
<加密与解密>
揭秘><C++反汇编与逆向分析技术
Stud PE
exeinfoExeinfo PE是一款专业的程序查壳工具
LordPE
PE Tools 1.9