应用安全 --- win安全 之 VMP初体验

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 节区原始数据          |
|        (存放资源)                  |
|                                   |
|             ...                   |
+-----------------------------------+  <-- 文件结束

各组成部分的简明解释

  1. IMAGE_DOS_HEADER (DOS 头)

    • 目的:为了保持与古老 DOS 系统的兼容性。

    • 关键成员:e_lfanew 字段,它包含了指向真正的 PE 头 (IMAGE_NT_HEADERS) 的文件偏移量。

  2. IMAGE_NT_HEADERS (NT 头)

    • 目的:PE 文件的正式入口和核心描述符。

    • 包含三部分:

      • Signature:一个 "PE\0\0" 的签名,标识这是一个 PE 文件。

      • IMAGE_FILE_HEADER (文件头):描述了文件的全局属性,如目标机器类型、节区数量、创建时间等。

      • IMAGE_OPTIONAL_HEADER (可选头):虽然叫"可选",但对于可执行文件是必需的。它包含了程序加载和运行所需的关键信息。

  3. IMAGE_OPTIONAL_HEADER (可选头)

    • 目的:为操作系统加载器提供如何准备和执行程序的信息。

    • 关键成员:入口点地址、映像基址、内存/文件对齐值、子系统等。

    • 它末尾的 IMAGE_DATA_DIRECTORY (数据目录) 是一个非常重要的表格,它指出了其他重要数据结构(如导入表、导出表、资源表)在文件中的位置和大小。

  4. IMAGE_DATA_DIRECTORY (数据目录)

    • 目的:作为指向其他重要数据的"目录"或"索引"。

    • 结构:一个由16个相同结构组成的数组。每个结构包含一个 RVA(相对虚拟地址) 和 Size。

    • 例如:第二个条目(索引1)是 Import Directory,加载器通过它找到所有需要从其他DLL导入的函数列表。

  5. IMAGE_SECTION_HEADER (节区头)

    • 目的:描述文件中的各个"节区"。节区是实际存储代码、数据、资源等内容的部分。

    • 数量:由 IMAGE_FILE_HEADER 中的 NumberOfSections 指定。

    • 关键成员:

      • Name:节区名称(如 .text, .data, .rdata)。

      • VirtualAddress:该节区加载到内存后的 RVA。

      • PointerToRawData:该节区在磁盘文件中的原始数据偏移。

      • Characteristics:节区属性(如可读、可写、可执行)。

总结与流程

操作系统加载一个 PE 文件的简化流程如下:

  1. 读取 IMAGE_DOS_HEADER,找到 e_lfanew

  2. 跳到 e_lfanew 位置,验证 "PE" 签名,读取 IMAGE_NT_HEADERS

  3. IMAGE_FILE_HEADER 知道有多少个节区。

  4. IMAGE_OPTIONAL_HEADER 获取关键信息(如入口点、映像大小、数据目录)。

  5. 遍历 IMAGE_SECTION_HEADER 数组,了解每个节区在文件和内存中的映射关系。

  6. 根据节区头的信息,将文件的各个节区(代码、数据等)映射到内存的相应位置。

  7. 通过 IMAGE_DATA_DIRECTORY 找到导入表,解析并填充所有需要的外部函数地址。

  8. 最后,跳转到 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头三句话秒懂

  1. DOS头:"我伪装成DOS程序,但真正的内容在后面"
  2. PE头:"我是Windows程序,32位/64位,从地址XXX开始执行"
  3. 节表:"我的代码在.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