四平市网站建设_网站建设公司_ASP.NET_seo优化
2025/12/21 21:41:30 网站建设 项目流程

Virtual Machine Protect. 虚拟机保护 ,可以将汇编指令转化为自定义指令集,虚拟指令涉及上百万条汇编指令,极大增强pj难度。

由win版本的和linux,安卓版本的。他们的软件实现方法和厂家都不一样,但是原理相同。

win具体的软件由pmvrotect2.x 3.x 3.5。vmp加密时只是对之前的so进行新增加密节区,不修改节区。

pmvrotect2软件加密的手段有很多最著名的是vmp技术。包含 指令虚拟化,导入表加密,反调试,反dump,指令和数据压缩

vmpdumper可以脱出汇编代码但是不能修复vmp节区和导出表,加密字符串。需要手动修复。导入表(IAT)不能修复

代码压缩 (Packing)

原理:压缩代码段

text

原始PE结构:

├─ .text (500KB) - 可执行代码

├─ .data (100KB) - 数据

└─ .rsrc (200KB) - 资源

↓ 压缩后 ↓

├─ .vmp0 (150KB) - 压缩的代码+解压stub

├─ .vmp1 (80KB) - 压缩的数据

└─ .rsrc (200KB)

运行时:

1. 解压.vmp0到内存

2. 修复重定位

3. 跳转执行

压缩算法:LZMA/自定义

强度:★★

主要用途:减小体积(附带混淆效果)

代码虚拟化 (Virtualization) ⭐最强

原理:将x86指令转换为自定义虚拟机指令

转换示例:

asm

; 原始代码

mov eax, 5

add eax, 3

ret

; ↓ 虚拟化后 ↓

push offset vm_bytecode ; VM字节码

call vm_interpreter ; 调用VM解释器

ret

; vm_bytecode (自定义指令):

; 0x45 0x05 0x00 0x00 0x00 ; VM_MOV eax, 5

; 0x12 0x03 0x00 0x00 0x00 ; VM_ADD eax, 3

; 0xFF ; VM_RET

C代码示例:

C++

// 原始

int add(int a, int b) {

return a + b;

}

// SDK标记

#include "VMProtectSDK.h"

int add(int a, int b) {

VMProtectBeginVirtualization("Add");

return a + b;

VMProtectEnd();

}

强度:★★★★★

性能损耗:5-20倍

代码变异 (Mutation/Obfuscation)

原理:用等价但复杂的指令替换原指令

转换示例:

asm

; 原始

mov eax, 5

; ↓ 变异后 ↓

push 2

push 3

pop ecx

pop edx

lea eax, [ecx+edx] ; 实际还是5

; 或者

xor eax, eax

add eax, 3

inc eax

inc eax ; 仍是5,但复杂化

典型变异技术:

asm

; 1. 指令替换

mov eax, 0 → xor eax, eax

; 2. 花指令插入

nop

jmp $+1

db 0xE8 ; 无效字节

add eax, ebx

; 3. 寄存器替换

mov eax, 5 → mov ebx, 5

mov eax, ebx

; 4. 常量加密

mov eax, 100 → mov eax, 0x12345678

xor eax, 0x123456DC ; = 100

强度:★★★★

性能损耗:1.5-3倍

导入表加密

导入表保护 (Import Protection)

原理:隐藏真实的API调用

转换示例:

C++

; 原始导入表

IMPORT TABLE:

kernel32.dll

- CreateFileA

- ReadFile

; ↓ 保护后 ↓

IMPORT TABLE:

kernel32.dll

- LoadLibraryA ; 只保留最基础的

; 运行时动态获取

char* encrypted = "\x3F\x12\x7A..."; // 加密的 "CreateFileA"

decrypt(encrypted);

pCreateFile = GetProcAddress(LoadLibrary("kernel32"), decrypted);

pCreateFile(...); // 调用

代码对比:

C++

// 原始

MessageBoxA(NULL, "Hello", "Test", 0);

// 保护后(伪代码)

typedef int (WINAPI *pMsgBox)(HWND, LPCSTR, LPCSTR, UINT);

pMsgBox MyMsgBox = vmp_get_api(0x1A3F); // 内部索引

MyMsgBox(NULL, vmp_str(0x2B4C), vmp_str(0x3D5E), 0);

强度:★★★★

绕过难度:中等(可API Hook监控)

call后面是加密的iat表

image

字符串加密 (String Encryption)

原理:加密所有字符串常量

示例:

C++

// 原始

const char* key = "MySecretKey123";

if (strcmp(input, key) == 0) { ... }

// ↓ 加密后 ↓

// 数据段存储

.data

encrypted_str db 0xA3,0x7F,0x2B,0x9C,... ; 加密的字符串

// 代码中

char* key = vmp_decrypt_string(0x004050A0);

if (strcmp(input, key) == 0) {

vmp_free_string(key); // 用完立即清除

}

反调试 (Anti-Debug)

技术清单:

C++

// 1. IsDebuggerPresent

if (IsDebuggerPresent()) {

ExitProcess(0);

}

// 2. PEB检测

bool CheckDebug() {

__asm {

mov eax, fs:[0x30] // PEB

movzx eax, byte ptr [eax+2] // BeingDebugged

test eax, eax

}

}

// 3. NtQueryInformationProcess

BOOL isDebug = FALSE;

NtQueryInformationProcess(GetCurrentProcess(),

ProcessDebugPort, &isDebug, sizeof(BOOL), NULL);

// 4. 时间检测

DWORD t1 = GetTickCount();

// 一些代码

DWORD t2 = GetTickCount();

if (t2 - t1 > 1000) { /* 被调试 */ }

// 5. 硬件断点检测

CONTEXT ctx = {0};

ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;

GetThreadContext(GetCurrentThread(), &ctx);

if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {

/* 检测到硬件断点 */

}

// 6. INT 3检测

__try {

__asm int 3

} __except(EXCEPTION_EXECUTE_HANDLER) {

// 正常程序会走这里

}

// 7. 检测调试器窗口

if (FindWindow("OLLYDBG", NULL) ||

FindWindow("WinDbgFrameClass", NULL)) {

ExitProcess(0);

}

反虚拟机 (Anti-VM)

检测技术:

C++

// 1. CPUID检测

void CheckVM() {

int cpuInfo[4];

__cpuid(cpuInfo, 1);

if (cpuInfo[2] & (1 << 31)) {

// Hypervisor bit set

ExitProcess(0);

}

}

// 2. 特征文件检测

if (PathFileExists("C:\\Windows\\System32\\drivers\\vmmouse.sys") ||

PathFileExists("C:\\Windows\\System32\\drivers\\vmhgfs.sys")) {

// VMware检测

}

// 3. 注册表检测

HKEY hKey;

if (RegOpenKey(HKEY_LOCAL_MACHINE,

"SOFTWARE\\VMware, Inc.\\VMware Tools", &hKey) == ERROR_SUCCESS) {

// VMware

}

// 4. MAC地址检测

// VMware前缀: 00:05:69, 00:0C:29, 00:50:56

// VirtualBox前缀: 08:00:27

// 5. 指令时间检测

DWORD t1 = __rdtsc();

// 执行一些指令

DWORD t2 = __rdtsc();

if (t2 - t1 > threshold) { /* VM环境 */ }

强度:★★★

绕过:修改VM配置

内存保护 (Memory Protection)

原理:检测内存完整性

C++

// 1. CRC校验

DWORD original_crc = 0x12345678;

DWORD current_crc = calc_crc32(code_section, size);

if (current_crc != original_crc) {

ExitProcess(0); // 代码被修改

}

// 2. 定期校验

void __stdcall CheckThread(LPVOID param) {

while (true) {

Sleep(1000);

if (!verify_memory()) {

TerminateProcess(GetCurrentProcess(), 0);

}

}

}

// 3. 页面保护

VirtualProtect(code_section, size, PAGE_EXECUTE_READ, &old);

// 任何写入会触发异常

资源加密 (Resource Encryption)

原理:加密PE资源段

C++

// 原始

HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(101), RT_BITMAP);

HGLOBAL hData = LoadResource(NULL, hRes);

void* pData = LockResource(hData);

// ↓ 保护后 ↓

// 资源段全部加密

.rsrc section: [encrypted data]

// 运行时

HRSRC hRes = FindResource(...);

// VMProtect拦截LoadResource

HGLOBAL hData = LoadResource(...); // 内部解密

void* pData = LockResource(hData); // 返回解密数据

强度:★★★

效果:防止资源提取工具直接读取

应用VMProtect后(简化示意)

C++

// 编译后的保护代码(逆向视角)

.text:00401000 ; VMP Entry

.text:00401000 push ebp

.text:00401001 call vmp_init_2FA3C

.text:00401006 jmp vm_dispatcher_1

// 原始的main函数代码已经不存在

// 被转换为:

.vmp0:00501000 db 0x4A, 0x8F, 0x23, ... ; VM字节码

.vmp0:00501100 db 0x7C, 0x12, 0xAB, ...

.vmp1:00502000 ; VM解释器

.vmp1:00502000 vm_handler_0:

.vmp1:00502000 mov al, [esi]

.vmp1:00502002 inc esi

.vmp1:00502003 movzx eax, al

.vmp1:00502006 jmp [vm_table + eax*4]

// 字符串也被加密

.data:00601000 encrypted_str1 db 0xA4,0x7F,0x9C,...

.data:00601020 encrypted_str2 db 0x3E,0x12,0x88,...

// 导入表被隐藏

.idata:00701000 ; 只剩下

.idata:00701000 dd offset LoadLibraryA

.idata:00701004 dd offset GetProcAddress

保护强度对比表

方法 反静态分析 反动态分析 性能影响 推荐场景

Virtualization ★★★★★ ★★★★★ -90% 核心算法

Mutation ★★★★ ★★★ -30% 一般函数

Import Protection ★★★★ ★★ -5% 全局开启

String Encryption ★★★ ★ -5% 敏感字符串

Anti-Debug ★ ★★★★ -1% 全局开启

Anti-VM ★ ★★★ -1% 可选

Memory Protection ★★ ★★★★ -10% 全局开启

Packing ★★ ★ +5% 减小体积

虚拟指令、虚拟栈、虚拟寄存器。

image

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结构最直观)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询