應(yīng)用安全 --- win安全 之 VMP初體驗(yàn)
VMP是一種軟件加固方法
Virtual Machine Protect. 虛擬機(jī)保護(hù) ,可以將匯編指令轉(zhuǎn)化為自定義指令集,虛擬指令涉及上百萬(wàn)條匯編指令,極大增強(qiáng)pj難度。
由win版本的和linux,安卓版本的。他們的軟件實(shí)現(xiàn)方法和廠(chǎng)家都不一樣,但是原理相同。
win具體的軟件由pmvrotect2.x 3.x 3.5。vmp加密時(shí)只是對(duì)之前的so進(jìn)行新增加密節(jié)區(qū),不修改節(jié)區(qū)。
pmvrotect2軟件加密的手段有很多最著名的是vmp技術(shù)。包含 指令虛擬化,導(dǎo)入表加密,反調(diào)試,反dump,指令和數(shù)據(jù)壓縮
vmpdumper可以脫出匯編代碼但是不能修復(fù)vmp節(jié)區(qū)和導(dǎo)出表,加密字符串。需要手動(dòng)修復(fù)。導(dǎo)入表(IAT)不能修復(fù)
代碼壓縮 (Packing)
原理:壓縮代碼段
原始PE結(jié)構(gòu):
├─ .text (500KB) - 可執(zhí)行代碼
├─ .data (100KB) - 數(shù)據(jù)
└─ .rsrc (200KB) - 資源
↓ 壓縮后 ↓
├─ .vmp0 (150KB) - 壓縮的代碼+解壓stub
├─ .vmp1 (80KB) - 壓縮的數(shù)據(jù)
└─ .rsrc (200KB)
運(yùn)行時(shí):
1. 解壓.vmp0到內(nèi)存
2. 修復(fù)重定位
3. 跳轉(zhuǎn)執(zhí)行
壓縮算法:LZMA/自定義
強(qiáng)度:★★
主要用途:減小體積(附帶混淆效果)
代碼虛擬化 (Virtualization) ?最強(qiáng)
原理:將x86指令轉(zhuǎn)換為自定義虛擬機(jī)指令
轉(zhuǎn)換示例:
; 原始代碼
mov eax, 5
add eax, 3
ret
; ↓ 虛擬化后 ↓
push offset vm_bytecode ; VM字節(jié)碼
call vm_interpreter ; 調(diào)用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代碼示例:
// 原始
int add(int a, int b) {
return a + b;
}
// SDK標(biāo)記
#include "VMProtectSDK.h"
int add(int a, int b) {
VMProtectBeginVirtualization("Add");
return a + b;
VMProtectEnd();
}
強(qiáng)度:★★★★★
性能損耗:5-20倍
代碼變異 (Mutation/Obfuscation)
原理:用等價(jià)但復(fù)雜的指令替換原指令
轉(zhuǎn)換示例:
; 原始
mov eax, 5
; ↓ 變異后 ↓
push 2
push 3
pop ecx
pop edx
lea eax, [ecx+edx] ; 實(shí)際還是5
; 或者
xor eax, eax
add eax, 3
inc eax
inc eax ; 仍是5,但復(fù)雜化
典型變異技術(shù):
; 1. 指令替換
mov eax, 0 → xor eax, eax
; 2. 花指令插入
nop
jmp $+1
db 0xE8 ; 無(wú)效字節(jié)
add eax, ebx
; 3. 寄存器替換
mov eax, 5 → mov ebx, 5
mov eax, ebx
; 4. 常量加密
mov eax, 100 → mov eax, 0x12345678
xor eax, 0x123456DC ; = 100
強(qiáng)度:★★★★
性能損耗:1.5-3倍
導(dǎo)入表加密
導(dǎo)入表保護(hù) (Import Protection)
原理:隱藏真實(shí)的API調(diào)用
轉(zhuǎn)換示例:
; 原始導(dǎo)入表
IMPORT TABLE:
kernel32.dll
- CreateFileA
- ReadFile
; ↓ 保護(hù)后 ↓
IMPORT TABLE:
kernel32.dll
- LoadLibraryA ; 只保留最基礎(chǔ)的
; 運(yùn)行時(shí)動(dòng)態(tài)獲取
char* encrypted = "\x3F\x12\x7A..."; // 加密的 "CreateFileA"
decrypt(encrypted);
pCreateFile = GetProcAddress(LoadLibrary("kernel32"), decrypted);
pCreateFile(...); // 調(diào)用
代碼對(duì)比:
// 原始
MessageBoxA(NULL, "Hello", "Test", 0);
// 保護(hù)后(偽代碼)
typedef int (WINAPI *pMsgBox)(HWND, LPCSTR, LPCSTR, UINT);
pMsgBox MyMsgBox = vmp_get_api(0x1A3F); // 內(nèi)部索引
MyMsgBox(NULL, vmp_str(0x2B4C), vmp_str(0x3D5E), 0);
強(qiáng)度:★★★★
繞過(guò)難度:中等(可API Hook監(jiān)控)
call后面是加密的iat表

字符串加密 (String Encryption)
原理:加密所有字符串常量
示例:
// 原始
const char* key = "MySecretKey123";
if (strcmp(input, key) == 0) { ... }
// ↓ 加密后 ↓
// 數(shù)據(jù)段存儲(chǔ)
.data
encrypted_str db 0xA3,0x7F,0x2B,0x9C,... ; 加密的字符串
// 代碼中
char* key = vmp_decrypt_string(0x004050A0);
if (strcmp(input, key) == 0) {
vmp_free_string(key); // 用完立即清除
}
反調(diào)試 (Anti-Debug)
技術(shù)清單:
// 1. IsDebuggerPresent if (IsDebuggerPresent()) { ExitProcess(0); } // 2. PEB檢測(cè) 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. 時(shí)間檢測(cè) DWORD t1 = GetTickCount(); // 一些代碼 DWORD t2 = GetTickCount(); if (t2 - t1 > 1000) { /* 被調(diào)試 */ } // 5. 硬件斷點(diǎn)檢測(cè) CONTEXT ctx = {0}; ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; GetThreadContext(GetCurrentThread(), &ctx); if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) { /* 檢測(cè)到硬件斷點(diǎn) */ } // 6. INT 3檢測(cè) __try { __asm int 3 } __except(EXCEPTION_EXECUTE_HANDLER) { // 正常程序會(huì)走這里 } // 7. 檢測(cè)調(diào)試器窗口 if (FindWindow("OLLYDBG", NULL) || FindWindow("WinDbgFrameClass", NULL)) { ExitProcess(0); }
反虛擬機(jī) (Anti-VM)
檢測(cè)技術(shù):
// 1. CPUID檢測(cè)
void CheckVM() {
int cpuInfo[4];
__cpuid(cpuInfo, 1);
if (cpuInfo[2] & (1 << 31)) {
// Hypervisor bit set
ExitProcess(0);
}
}
// 2. 特征文件檢測(cè)
if (PathFileExists("C:\\Windows\\System32\\drivers\\vmmouse.sys") ||
PathFileExists("C:\\Windows\\System32\\drivers\\vmhgfs.sys")) {
// VMware檢測(cè)
}
// 3. 注冊(cè)表檢測(cè)
HKEY hKey;
if (RegOpenKey(HKEY_LOCAL_MACHINE,
"SOFTWARE\\VMware, Inc.\\VMware Tools", &hKey) == ERROR_SUCCESS) {
// VMware
}
// 4. MAC地址檢測(cè)
// VMware前綴: 00:05:69, 00:0C:29, 00:50:56
// VirtualBox前綴: 08:00:27
// 5. 指令時(shí)間檢測(cè)
DWORD t1 = __rdtsc();
// 執(zhí)行一些指令
DWORD t2 = __rdtsc();
if (t2 - t1 > threshold) { /* VM環(huán)境 */ }
強(qiáng)度:★★★
繞過(guò):修改VM配置
內(nèi)存保護(hù) (Memory Protection)
原理:檢測(cè)內(nèi)存完整性
// 1. CRC校驗(yàn) DWORD original_crc = 0x12345678; DWORD current_crc = calc_crc32(code_section, size); if (current_crc != original_crc) { ExitProcess(0); // 代碼被修改 } // 2. 定期校驗(yàn) void __stdcall CheckThread(LPVOID param) { while (true) { Sleep(1000); if (!verify_memory()) { TerminateProcess(GetCurrentProcess(), 0); } } } // 3. 頁(yè)面保護(hù) VirtualProtect(code_section, size, PAGE_EXECUTE_READ, &old); // 任何寫(xiě)入會(huì)觸發(fā)異常
資源加密 (Resource Encryption)
原理:加密PE資源段
// 原始
HRSRC hRes = FindResource(NULL, MAKEINTRESOURCE(101), RT_BITMAP);
HGLOBAL hData = LoadResource(NULL, hRes);
void* pData = LockResource(hData);
// ↓ 保護(hù)后 ↓
// 資源段全部加密
.rsrc section: [encrypted data]
// 運(yùn)行時(shí)
HRSRC hRes = FindResource(...);
// VMProtect攔截LoadResource
HGLOBAL hData = LoadResource(...); // 內(nèi)部解密
void* pData = LockResource(hData); // 返回解密數(shù)據(jù)
強(qiáng)度:★★★
效果:防止資源提取工具直接讀取
應(yīng)用VMProtect后(簡(jiǎn)化示意)
// 編譯后的保護(hù)代碼(逆向視角)
.text:00401000 ; VMP Entry
.text:00401000 push ebp
.text:00401001 call vmp_init_2FA3C
.text:00401006 jmp vm_dispatcher_1
// 原始的main函數(shù)代碼已經(jīng)不存在
// 被轉(zhuǎn)換為:
.vmp0:00501000 db 0x4A, 0x8F, 0x23, ... ; VM字節(jié)碼
.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,...
// 導(dǎo)入表被隱藏
.idata:00701000 ; 只剩下
.idata:00701000 dd offset LoadLibraryA
.idata:00701004 dd offset GetProcAddress
保護(hù)強(qiáng)度對(duì)比表
| 方法 | 反靜態(tài)分析 | 反動(dòng)態(tài)分析 | 性能影響 | 推薦場(chǎng)景 |
|---|---|---|---|---|
| Virtualization | ★★★★★ | ★★★★★ | -90% | 核心算法 |
| Mutation | ★★★★ | ★★★ | -30% | 一般函數(shù) |
| Import Protection | ★★★★ | ★★ | -5% | 全局開(kāi)啟 |
| String Encryption | ★★★ | ★ | -5% | 敏感字符串 |
| Anti-Debug | ★ | ★★★★ | -1% | 全局開(kāi)啟 |
| Anti-VM | ★ | ★★★ | -1% | 可選 |
| Memory Protection | ★★ | ★★★★ | -10% | 全局開(kāi)啟 |
| Packing | ★★ | ★ | +5% | 減小體積 |
虛擬指令、虛擬棧、虛擬寄存器。

msdn。開(kāi)發(fā)文檔
<windows程序設(shè)計(jì)>
<windows核心編程>
<win32匯編語(yǔ)言程序設(shè)計(jì)>
PE 文件結(jié)構(gòu)圖例
+-----------------------------------+ <-- 文件開(kāi)始 (Offset 0) | IMAGE_DOS_HEADER | |-----------------------------------| | - e_magic: "MZ" (0x5A4D) | <- DOS 可執(zhí)行文件簽名 | - e_lfanew: 指向 NT 頭部的偏移量 | <- 這是通往 PE 頭的關(guān)鍵指針! +-----------------------------------+ | 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) | | <- 目標(biāo) CPU 架構(gòu) (如 x64) | | - NumberOfSections: | | <- 后面跟著的節(jié)區(qū)數(shù)量 | | - TimeDateStamp: | | <- 鏈接時(shí)間戳 | | - SizeOfOptionalHeader: | | <- 下一個(gè)頭的大小 | | - Characteristics: | | <- 文件屬性 (如 DLL, Executable) | +-----------------------------+ | | | IMAGE_OPTIONAL_HEADER | | <- 對(duì)于操作系統(tǒng)加載器至關(guān)重要 | |-----------------------------| | | | - Magic: (e.g., 0x20B) | | <- 標(biāo)識(shí) PE32(0x10B) 或 PE32+(0x20B) | | - AddressOfEntryPoint: | | <- 程序執(zhí)行入口 RVA | | - ImageBase: | | <- 映像的首選加載地址 | | - SectionAlignment: | | <- 內(nèi)存中節(jié)區(qū)的對(duì)齊方式 | | - FileAlignment: | | <- 文件中節(jié)區(qū)的對(duì)齊方式 | | - SizeOfImage: | | <- 內(nèi)存中整個(gè)映像的大小 | | - SizeOfHeaders: | | <- 所有頭部的總大小 | | - Subsystem: (e.g., GUI/CUI) | | <- 要求子系統(tǒng) (如 Windows GUI) | | - NumberOfRvaAndSizes: | | <- 數(shù)據(jù)目錄項(xiàng)的數(shù)量 | |-----------------------------| | | | IMAGE_DATA_DIRECTORY | | <- 一個(gè)結(jié)構(gòu)體數(shù)組 | | [0] Export Directory | | | | [1] Import Directory | | <- 指向?qū)牒瘮?shù)信息 | | [2] Resource Directory | | <- 指向資源 (圖標(biāo)、字符串等) | | [3] Exception Directory | | | | ... (共 16 個(gè)) ... | | | +-----------------------------+ | +-----------------------------------+ | IMAGE_SECTION_HEADER[0] | <- 第一個(gè)節(jié)區(qū)頭 (如 .text) | - Name: ".text\0\0\0" | <- 節(jié)區(qū)名稱(chēng) | - VirtualAddress: | <- 內(nèi)存中的 RVA | - SizeOfRawData: | <- 在文件中的大小 | - PointerToRawData: | <- 在文件中的偏移 | - Characteristics: | <- 節(jié)區(qū)屬性 (如 可執(zhí)行、可讀) +-----------------------------------+ | IMAGE_SECTION_HEADER[1] | <- 第二個(gè)節(jié)區(qū)頭 (如 .data) | - Name: ".data\0\0\0" | | - ... | +-----------------------------------+ | ... | <- 其他節(jié)區(qū)頭 (共 NumberOfSections 個(gè)) +-----------------------------------+ <-- SizeOfHeaders 標(biāo)記的頭部結(jié)束位置 | 節(jié)區(qū)數(shù)據(jù)開(kāi)始 | | | | .text 節(jié)區(qū)原始數(shù)據(jù) | <- 文件偏移由 PointerToRawData 指定 | (存放代碼) | | | | .data 節(jié)區(qū)原始數(shù)據(jù) | <- 文件偏移由 PointerToRawData 指定 | (存放全局變量) | | | | .rsrc 節(jié)區(qū)原始數(shù)據(jù) | | (存放資源) | | | | ... | +-----------------------------------+ <-- 文件結(jié)束
各組成部分的簡(jiǎn)明解釋
-
IMAGE_DOS_HEADER (DOS 頭)
-
目的:為了保持與古老 DOS 系統(tǒng)的兼容性。
-
關(guān)鍵成員:
e_lfanew字段,它包含了指向真正的 PE 頭 (IMAGE_NT_HEADERS) 的文件偏移量。
-
-
IMAGE_NT_HEADERS (NT 頭)
-
目的:PE 文件的正式入口和核心描述符。
-
包含三部分:
-
Signature:一個(gè) "PE\0\0" 的簽名,標(biāo)識(shí)這是一個(gè) PE 文件。
-
IMAGE_FILE_HEADER (文件頭):描述了文件的全局屬性,如目標(biāo)機(jī)器類(lèi)型、節(jié)區(qū)數(shù)量、創(chuàng)建時(shí)間等。
-
IMAGE_OPTIONAL_HEADER (可選頭):雖然叫"可選",但對(duì)于可執(zhí)行文件是必需的。它包含了程序加載和運(yùn)行所需的關(guān)鍵信息。
-
-
-
IMAGE_OPTIONAL_HEADER (可選頭)
-
目的:為操作系統(tǒng)加載器提供如何準(zhǔn)備和執(zhí)行程序的信息。
-
關(guān)鍵成員:入口點(diǎn)地址、映像基址、內(nèi)存/文件對(duì)齊值、子系統(tǒng)等。
-
它末尾的 IMAGE_DATA_DIRECTORY (數(shù)據(jù)目錄) 是一個(gè)非常重要的表格,它指出了其他重要數(shù)據(jù)結(jié)構(gòu)(如導(dǎo)入表、導(dǎo)出表、資源表)在文件中的位置和大小。
-
-
IMAGE_DATA_DIRECTORY (數(shù)據(jù)目錄)
-
目的:作為指向其他重要數(shù)據(jù)的“目錄”或“索引”。
-
結(jié)構(gòu):一個(gè)由16個(gè)相同結(jié)構(gòu)組成的數(shù)組。每個(gè)結(jié)構(gòu)包含一個(gè) RVA(相對(duì)虛擬地址) 和 Size。
-
例如:第二個(gè)條目(索引1)是 Import Directory,加載器通過(guò)它找到所有需要從其他DLL導(dǎo)入的函數(shù)列表。
-
-
IMAGE_SECTION_HEADER (節(jié)區(qū)頭)
-
目的:描述文件中的各個(gè)“節(jié)區(qū)”。節(jié)區(qū)是實(shí)際存儲(chǔ)代碼、數(shù)據(jù)、資源等內(nèi)容的部分。
-
數(shù)量:由
IMAGE_FILE_HEADER中的NumberOfSections指定。 -
關(guān)鍵成員:
-
Name:節(jié)區(qū)名稱(chēng)(如.text,.data,.rdata)。 -
VirtualAddress:該節(jié)區(qū)加載到內(nèi)存后的 RVA。 -
PointerToRawData:該節(jié)區(qū)在磁盤(pán)文件中的原始數(shù)據(jù)偏移。 -
Characteristics:節(jié)區(qū)屬性(如可讀、可寫(xiě)、可執(zhí)行)。
-
-
總結(jié)與流程
操作系統(tǒng)加載一個(gè) PE 文件的簡(jiǎn)化流程如下:
-
讀取
IMAGE_DOS_HEADER,找到e_lfanew。 -
跳到
e_lfanew位置,驗(yàn)證 "PE" 簽名,讀取IMAGE_NT_HEADERS。 -
從
IMAGE_FILE_HEADER知道有多少個(gè)節(jié)區(qū)。 -
從
IMAGE_OPTIONAL_HEADER獲取關(guān)鍵信息(如入口點(diǎn)、映像大小、數(shù)據(jù)目錄)。 -
遍歷
IMAGE_SECTION_HEADER數(shù)組,了解每個(gè)節(jié)區(qū)在文件和內(nèi)存中的映射關(guān)系。 -
根據(jù)節(jié)區(qū)頭的信息,將文件的各個(gè)節(jié)區(qū)(代碼、數(shù)據(jù)等)映射到內(nèi)存的相應(yīng)位置。
-
通過(guò)
IMAGE_DATA_DIRECTORY找到導(dǎo)入表,解析并填充所有需要的外部函數(shù)地址。 -
最后,跳轉(zhuǎn)到
AddressOfEntryPoint指向的地址,程序開(kāi)始執(zhí)行。
這個(gè)結(jié)構(gòu)確保了 PE 文件既能在磁盤(pán)上高效存儲(chǔ),又能在內(nèi)存中正確加載和執(zhí)行。
PE文件頭結(jié)構(gòu)圖解 + 白話(huà)文秒懂
?? 完整結(jié)構(gòu)總覽
┌─────────────────────────────────────────────────────────┐
│ DOS Header (64字節(jié)) │ ← 開(kāi)頭的"MZ"標(biāo)記
│ "這是個(gè)老式DOS程序" 的偽裝外殼 │
├─────────────────────────────────────────────────────────┤
│ DOS Stub (可變) │
│ "This program cannot be run in DOS mode" │ ← 在DOS下運(yùn)行會(huì)看到的提示
├─────────────────────────────────────────────────────────┤
│ PE Signature (4字節(jié)) │
│ "PE\0\0" │ ← 真正的PE文件標(biāo)記
├─────────────────────────────────────────────────────────┤
│ File Header (20字節(jié)) │
│ 記錄機(jī)器類(lèi)型、節(jié)數(shù)量、時(shí)間戳等基本信息 │
├─────────────────────────────────────────────────────────┤
│ Optional Header (224/240字節(jié)) │
│ 記錄程序入口點(diǎn)、內(nèi)存布局、導(dǎo)入導(dǎo)出等關(guān)鍵信息 │
├─────────────────────────────────────────────────────────┤
│ Section Table (每節(jié)40字節(jié)) │
│ .text .data .rdata .rsrc 等節(jié)的"目錄" │
├─────────────────────────────────────────────────────────┤
│ │
│ Section 1 (.text) │ ← 代碼區(qū)
│ 你寫(xiě)的代碼在這里 │
│ │
├─────────────────────────────────────────────────────────┤
│ Section 2 (.data) │ ← 數(shù)據(jù)區(qū)
│ 全局變量在這里 │
├─────────────────────────────────────────────────────────┤
│ Section 3 (.rsrc) │ ← 資源區(qū)
│ 圖標(biāo)、對(duì)話(huà)框、字符串在這里 │
└─────────────────────────────────────────────────────────┘
?? 詳細(xì)結(jié)構(gòu)拆解
1?? DOS Header (IMAGE_DOS_HEADER)
偏移 大小 字段名 白話(huà)文解釋
+0x00 2字節(jié) e_magic "MZ" 標(biāo)記(0x5A4D)—— 所有PE文件必須以這兩個(gè)字母開(kāi)頭
+0x02 58字節(jié) [其他DOS字段] 基本沒(méi)用,為了兼容古董DOS系統(tǒng)
+0x3C 4字節(jié) e_lfanew **超重要!** 指向真正的PE頭在哪里
白話(huà):
這是個(gè)"假門(mén)面",為了讓W(xué)indows程序能在老DOS系統(tǒng)上顯示錯(cuò)誤提示,而不是直接崩潰。
最重要的是最后那個(gè)e_lfanew,它告訴系統(tǒng):"真正的PE頭在文件偏移XXX處"。
2?? PE Signature (4字節(jié))
+0x00 4字節(jié) Signature "PE\0\0" (0x50450000)
白話(huà):
就像蓋了個(gè)"認(rèn)證章",證明"我是正宗的Windows程序"。
3?? File Header (IMAGE_FILE_HEADER - 20字節(jié))
偏移 大小 字段名 白話(huà)文解釋
+0x00 2字節(jié) Machine CPU類(lèi)型(0x14C=x86, 0x8664=x64)
+0x02 2字節(jié) NumberOfSections 這個(gè)程序有幾個(gè)"節(jié)"(通常3-6個(gè))
+0x04 4字節(jié) TimeDateStamp 程序編譯的時(shí)間戳
+0x08 4字節(jié) PointerToSymbolTable 調(diào)試符號(hào)表位置(發(fā)布版通常是0)
+0x0C 4字節(jié) NumberOfSymbols 符號(hào)數(shù)量
+0x10 2字節(jié) SizeOfOptionalHeader 下一個(gè)頭的大小(32位=224, 64位=240)
+0x12 2字節(jié) Characteristics 文件屬性標(biāo)志
白話(huà):
這是"身份證"部分:
- 告訴系統(tǒng)這是32位還是64位程序
- 有幾個(gè)代碼/數(shù)據(jù)分區(qū)
- 什么時(shí)候編譯的
- 是個(gè)EXE還是DLL(Characteristics字段)
Characteristics 常見(jiàn)標(biāo)志:
0x0002 IMAGE_FILE_EXECUTABLE_IMAGE 可執(zhí)行文件(不是obj)
0x0100 IMAGE_FILE_32BIT_MACHINE 32位程序
0x2000 IMAGE_FILE_DLL 這是個(gè)DLL文件
4?? Optional Header (IMAGE_OPTIONAL_HEADER - 最重要!)
標(biāo)準(zhǔn)字段部分
偏移 大小 字段名 白話(huà)文解釋
+0x00 2字節(jié) Magic 0x10B(32位) / 0x20B(64位)
+0x02 1字節(jié) MajorLinkerVersion 編譯器版本
+0x03 1字節(jié) MinorLinkerVersion
+0x04 4字節(jié) SizeOfCode 代碼段總大小
+0x08 4字節(jié) SizeOfInitializedData 已初始化數(shù)據(jù)大小
+0x0C 4字節(jié) SizeOfUninitializedData未初始化數(shù)據(jù)大小
+0x10 4字節(jié) AddressOfEntryPoint **入口點(diǎn)!程序從這里開(kāi)始執(zhí)行**
+0x14 4字節(jié) BaseOfCode 代碼段起始地址
+0x18 4字節(jié) BaseOfData 數(shù)據(jù)段起始地址(僅32位)
Windows專(zhuān)用字段部分
偏移 大小 字段名 白話(huà)文解釋
+0x1C 4/8字節(jié) ImageBase 程序希望加載到內(nèi)存的哪個(gè)地址
+0x20 4字節(jié) SectionAlignment 節(jié)在內(nèi)存中的對(duì)齊單位(通常0x1000=4KB)
+0x24 4字節(jié) FileAlignment 節(jié)在文件中的對(duì)齊單位(通常0x200=512字節(jié))
+0x28 8字節(jié) [操作系統(tǒng)版本號(hào)]
+0x30 8字節(jié) [程序版本號(hào)]
+0x38 8字節(jié) [子系統(tǒng)版本號(hào)]
+0x40 4字節(jié) Win32VersionValue 保留(總是0)
+0x44 4字節(jié) SizeOfImage 程序加載到內(nèi)存后的總大小
+0x48 4字節(jié) SizeOfHeaders 所有頭的總大小
+0x4C 4字節(jié) CheckSum 校驗(yàn)和(驅(qū)動(dòng)必須正確,普通程序可為0)
+0x50 2字節(jié) Subsystem 子系統(tǒng)類(lèi)型
+0x52 2字節(jié) DllCharacteristics DLL特性標(biāo)志
+0x54 16字節(jié) [棧/堆大小設(shè)置]
+0x64 4字節(jié) NumberOfRvaAndSizes 數(shù)據(jù)目錄數(shù)量(通常是16)
Subsystem 子系統(tǒng):
1 = Native(驅(qū)動(dòng)程序)
2 = GUI(窗口程序)
3 = CUI(控制臺(tái)程序,黑框框)
DllCharacteristics 重要標(biāo)志:
0x0040 DYNAMIC_BASE 支持ASLR(地址隨機(jī)化)
0x0100 NX_COMPAT 支持DEP(數(shù)據(jù)執(zhí)行保護(hù))
0x0400 NO_SEH 不使用SEH異常處理
0x8000 TERMINAL_SERVER_AWARE 終端服務(wù)器感知
5?? 數(shù)據(jù)目錄表 (Data Directory - 16個(gè)條目)
索引 名稱(chēng) 作用
[0] Export Table 導(dǎo)出表(DLL導(dǎo)出的函數(shù)列表)
[1] Import Table 導(dǎo)入表(程序要用哪些DLL的哪些函數(shù))
[2] Resource Table 資源表(圖標(biāo)、字符串、對(duì)話(huà)框)
[3] Exception Table 異常處理表
[4] Certificate Table 數(shù)字簽名
[5] Base Relocation Table 重定位表(修正地址用)
[6] Debug 調(diào)試信息
[7] Architecture 架構(gòu)特定數(shù)據(jù)
[8] Global Ptr 全局指針寄存器
[9] TLS Table 線(xiàn)程局部存儲(chǔ)
[10] Load Config Table 加載配置
[11] Bound Import 綁定導(dǎo)入
[12] IAT 導(dǎo)入地址表(最常被破解者關(guān)注!)
[13] Delay Import Descriptor 延遲導(dǎo)入
[14] CLR Runtime Header .NET程序?qū)S?[15] Reserved 保留
每個(gè)條目結(jié)構(gòu):
+0x00 4字節(jié) VirtualAddress 數(shù)據(jù)在內(nèi)存中的RVA
+0x04 4字節(jié) Size 數(shù)據(jù)的大小
6?? 節(jié)表 (Section Table - 每節(jié)40字節(jié))
偏移 大小 字段名 白話(huà)文解釋
+0x00 8字節(jié) Name 節(jié)名稱(chēng)(如".text"、".data")
+0x08 4字節(jié) VirtualSize 在內(nèi)存中的實(shí)際大小
+0x0C 4字節(jié) VirtualAddress 在內(nèi)存中的起始地址(RVA)
+0x10 4字節(jié) SizeOfRawData 在文件中的大小
+0x14 4字節(jié) PointerToRawData 在文件中的偏移
+0x18 12字節(jié) [重定位/行號(hào)信息] 通常為0
+0x24 4字節(jié) Characteristics 節(jié)的屬性(可讀/可寫(xiě)/可執(zhí)行)
常見(jiàn)節(jié)名稱(chēng):
.text 代碼段(你的程序邏輯) 可讀+可執(zhí)行
.data 已初始化數(shù)據(jù)(全局變量) 可讀+可寫(xiě)
.rdata 只讀數(shù)據(jù)(常量字符串) 只讀
.bss 未初始化數(shù)據(jù) 可讀+可寫(xiě)
.rsrc 資源(圖標(biāo)、菜單、字符串) 只讀
.reloc 重定位信息 只讀
.idata 導(dǎo)入表(需要的DLL函數(shù)) 可讀+可寫(xiě)
.edata 導(dǎo)出表(DLL導(dǎo)出的函數(shù)) 只讀
Characteristics 節(jié)屬性:
0x00000020 CODE 包含代碼
0x00000040 INITIALIZED_DATA 包含已初始化數(shù)據(jù)
0x00000080 UNINITIALIZED_DATA 包含未初始化數(shù)據(jù)
0x20000000 EXECUTE 可執(zhí)行
0x40000000 READ 可讀
0x80000000 WRITE 可寫(xiě)
?? 關(guān)鍵概念白話(huà)解釋
RVA (Relative Virtual Address) - 相對(duì)虛擬地址
假設(shè)程序被加載到內(nèi)存地址 0x00400000
某個(gè)函數(shù)的RVA是 0x1000
那么這個(gè)函數(shù)的實(shí)際內(nèi)存地址 = 0x00400000 + 0x1000 = 0x00401000
文件偏移 vs 內(nèi)存地址
文件偏移:在硬盤(pán)上的.exe文件中的位置
內(nèi)存地址:程序運(yùn)行時(shí)在內(nèi)存中的位置
需要通過(guò)節(jié)表來(lái)轉(zhuǎn)換!
對(duì)齊 (Alignment)
FileAlignment = 0x200 (512字節(jié))
→ 文件中每個(gè)節(jié)的起始位置必須是512的倍數(shù)
SectionAlignment = 0x1000 (4096字節(jié))
→ 內(nèi)存中每個(gè)節(jié)的起始位置必須是4KB的倍數(shù)
??? 實(shí)戰(zhàn):用十六進(jìn)制編輯器看PE頭
偏移 十六進(jìn)制 解釋
00000000: 4D 5A 90 00 03 00 00 00... MZ = DOS頭開(kāi)始
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
?? 總結(jié):PE頭三句話(huà)秒懂
- DOS頭:"我偽裝成DOS程序,但真正的內(nèi)容在后面"
- PE頭:"我是Windows程序,32位/64位,從地址XXX開(kāi)始執(zhí)行"
- 節(jié)表:"我的代碼在.text節(jié),數(shù)據(jù)在.data節(jié),資源在.rsrc節(jié)"
?? 最關(guān)心的地方
? AddressOfEntryPoint → 程序從哪里開(kāi)始跑
? Import Table (IAT) → 調(diào)用了哪些關(guān)鍵API(MessageBox? RegCreateKey?)
? .text 節(jié) → 注冊(cè)驗(yàn)證代碼藏在這里
? Characteristics → 能不能改這個(gè)節(jié)的數(shù)據(jù)
? Resource Table → 修改圖標(biāo)、字符串、對(duì)話(huà)框
希望這個(gè)圖解能幫你快速理解PE結(jié)構(gòu)!如果要深入學(xué)習(xí),推薦工具:
- CFF Explorer (查看PE結(jié)構(gòu)最直觀)
- PE-bear
- 010 Editor (配合PE模板)
- windowspe 權(quán)威指南
了解所有編譯器和反匯編特征
了解vmp入口,區(qū)段,反匯編特征
看特征的方法主要有兩個(gè),一個(gè)是入口代碼的在節(jié)區(qū)中的位置,入口代碼中包含的指令和調(diào)用的外部接口
vc6特征和易語(yǔ)言








64-ia-32-architectures-software-developer-manualintel開(kāi)發(fā)手冊(cè)
<加密與解密>
揭秘><C++反匯編與逆向分析技術(shù)

Stud PE
exeinfoExeinfo PE是一款專(zhuān)業(yè)的程序查殼工具
LordPE
PE Tools 1.9
WinHex
UltraEdit
Beyond Compare數(shù)據(jù)比較工具。
iat修復(fù)工具
ImportREC
Scylla
Universal lmport Fixer
Imports Fixer
vmp指令轉(zhuǎn)化器軟件
將匯編轉(zhuǎn)vmp自定義指令,在轉(zhuǎn)化為真實(shí)指令集
好的,我們來(lái)詳細(xì)說(shuō)明如何嘗試使用 VMPDump 來(lái)處理一個(gè)被 VMProtect 2 加密的程序。
但在開(kāi)始之前,我必須先強(qiáng)調(diào)一個(gè)至關(guān)重要的事實(shí),這能為你節(jié)省大量的時(shí)間和精力:
?? 第一部分:必讀的現(xiàn)實(shí)情況與重要提醒
-
VMPDump 并非萬(wàn)能神藥:VMPDump 是一個(gè)比較早期的、針對(duì)特定舊版本 VMProtect 的自動(dòng)化分析工具。它無(wú)法真正地“還原所有代碼”。它的主要功能是嘗試自動(dòng)完成“尋找OEP”和“Dump內(nèi)存”這兩個(gè)初級(jí)階段的工作。
-
它不能“去虛擬化”:VMPDump 絕對(duì)無(wú)法將 VMProtect 的核心——虛擬化字節(jié)碼(Bytecode)——翻譯回原始的 x86/x64 匯編代碼。被虛擬化的關(guān)鍵代碼(如注冊(cè)算法)在 VMPDump 處理后,依然是虛擬化的,你用IDA打開(kāi)看仍然是一團(tuán)亂麻。
-
成功率極低:對(duì)于現(xiàn)代版本的 VMProtect 2 (例如 2.13 及以后) 或者即使是舊版本但開(kāi)啟了高強(qiáng)度保護(hù)選項(xiàng)的程序,VMPDump 的成功率趨近于零。它很可能會(huì)失敗、卡死、或者生成一個(gè)完全無(wú)法運(yùn)行的垃圾文件。
-
它的真正作用:在極少數(shù)情況下(例如面對(duì)非常古老的、保護(hù)設(shè)置很弱的VMP版本),它可能可以幫你快速脫掉外層的殼,省去你手動(dòng)尋找OEP和Dump的幾分鐘時(shí)間。請(qǐng)把它看作一個(gè)“快速?lài)L試的彩票”,而不是一個(gè)“解決方案”。
結(jié)論: 不要期望 VMPDump 能幫你“一鍵還原”所有代碼。它只是一個(gè)輔助性的、成功率很低的入門(mén)級(jí)工具。
第二部分:準(zhǔn)備工作
在你開(kāi)始之前,請(qǐng)確保你已經(jīng)準(zhǔn)備好以下環(huán)境和工具:
- 虛擬機(jī) (VM):強(qiáng)烈建議在 VMware 或 VirtualBox 等虛擬機(jī)中進(jìn)行所有操作。這可以防止目標(biāo)程序或工具損壞你的物理機(jī)系統(tǒng)。
- 目標(biāo)程序:你想要分析的那個(gè)被VMP2加密的
.exe文件。 - VMPDump 工具:你需要從一些逆向工程論壇或網(wǎng)站下載這個(gè)工具。請(qǐng)注意,這些工具可能被殺毒軟件報(bào)毒,這是因?yàn)樗鼈兊男袨椋ㄈ缱⑷脒M(jìn)程、讀取內(nèi)存)與惡意軟件相似。請(qǐng)?jiān)谔摂M機(jī)中謹(jǐn)慎使用。
- 調(diào)試器:x64dbg (強(qiáng)烈推薦)。VMPDump 經(jīng)常需要附加到一個(gè)正在運(yùn)行的進(jìn)程上,所以你需要先運(yùn)行目標(biāo)程序。
- PE 編輯器:CFF Explorer 或 010 Editor。用于后續(xù)檢查生成的文件是否結(jié)構(gòu)正常。
第三部分:使用 VMPDump 的詳細(xì)步驟
假設(shè)你已經(jīng)找到了一個(gè)可以運(yùn)行的 VMPDump 版本,并且你的目標(biāo)程序是一個(gè)32位的EXE(VMPDump主要支持32位)。
步驟 1:運(yùn)行目標(biāo)程序
首先,雙擊運(yùn)行你要分析的那個(gè) .exe 文件。讓它正常運(yùn)行起來(lái),比如顯示出主窗口或?qū)υ?huà)框。
步驟 2:以管理員身份運(yùn)行 VMPDump
右鍵點(diǎn)擊 VMPDump.exe,選擇 “以管理員身份運(yùn)行”。這能確保它有足夠的權(quán)限去讀取其他進(jìn)程的內(nèi)存。
步驟 3:附加到目標(biāo)進(jìn)程
- 在 VMPDump 的主界面上,你會(huì)看到一個(gè) “...” 按鈕,旁邊是 "Process" 或 "PID" 字段。
- 點(diǎn)擊這個(gè) “...” 按鈕,會(huì)彈出一個(gè)當(dāng)前正在運(yùn)行的進(jìn)程列表。
- 在列表中找到你的目標(biāo)程序進(jìn)程(例如
target.exe),選中它,然后點(diǎn)擊 “OK” 或 “Select”。
步驟 4:智能掃描 (Smart Scan)
這是 VMPDump 嘗試分析虛擬機(jī)的關(guān)鍵一步。
- 在界面上找到一個(gè)名為 “Smart Scan of Handlers” 的復(fù)選框,勾選它。
- 這一步會(huì)讓 VMPDump 嘗試在程序的內(nèi)存空間中搜索它認(rèn)為是 VMProtect 虛擬機(jī)處理器(VM Handlers)的代碼塊。這是它最容易失敗的地方。
步驟 5:設(shè)置 OEP (Original Entry Point)
VMPDump 會(huì)嘗試自動(dòng)檢測(cè) OEP。
- 自動(dòng)檢測(cè):通常你不需要手動(dòng)設(shè)置 OEP 字段。VMPDump 會(huì)利用一些內(nèi)置的特征碼去定位。
- 手動(dòng)設(shè)置:如果自動(dòng)檢測(cè)失敗,你需要自己通過(guò)其他方法(如 x64dbg 的 ESP 定律)找到 OEP 的相對(duì)虛擬地址(RVA),然后手動(dòng)填入 OEP 字段。對(duì)于初學(xué)者來(lái)說(shuō),如果 VMPDump 無(wú)法自動(dòng)找到 OEP,基本可以宣告失敗了。
步驟 6:執(zhí)行 Dump(轉(zhuǎn)儲(chǔ))
- 在 VMPDump 界面上,點(diǎn)擊 “Unpack” 或 “Dump” 按鈕。
- VMPDump 會(huì)開(kāi)始它的工作流程:
- 尋找 OEP。
- 掃描 VM Handlers。
- 轉(zhuǎn)儲(chǔ)內(nèi)存中的所有節(jié)區(qū)(Sections)。
- 嘗試重建導(dǎo)入地址表(IAT)。
- 如果一切順利(概率很小),它會(huì)提示你保存文件。通常會(huì)默認(rèn)保存為
[原文件名]_unpacked.exe。
步驟 7:驗(yàn)證生成的文件
這是最重要的一步,用來(lái)判斷 VMPDump 是否真的起作用了。
-
嘗試運(yùn)行:雙擊運(yùn)行生成的
_unpacked.exe文件。- 直接崩潰? -> 失敗。很可能是 IAT 修復(fù)失敗或代碼段Dump不完整。
- 可以運(yùn)行但功能異常? -> 部分失敗。可能某些保護(hù)代碼沒(méi)有被移除。
- 可以正常運(yùn)行? -> 恭喜,你完成了最基礎(chǔ)的“脫殼”。但這不代表代碼被還原了。
-
使用 PE 工具檢查:用 CFF Explorer 打開(kāi)
_unpacked.exe文件。- 檢查 “Import Directory”(導(dǎo)入目錄),看看函數(shù)列表是否看起來(lái)正常,有沒(méi)有大量的無(wú)效或亂碼條目。
- 檢查節(jié)表(Section Headers),看看
.text,.data等節(jié)的大小和權(quán)限是否合理。
-
使用 IDA Pro 分析:這是最終的檢驗(yàn)。用 IDA Pro 打開(kāi)
_unpacked.exe文件。- 找到你關(guān)心的關(guān)鍵功能(比如注冊(cè)按鈕的點(diǎn)擊事件)。
- 按 F5 嘗試反編譯。
- 結(jié)果是什么?
- 如果依然是無(wú)法反編譯、跳轉(zhuǎn)關(guān)系混亂、充滿(mǎn)了奇怪計(jì)算的代碼 -> 這就證明了 VMPDump 沒(méi)有去虛擬化。它只是把內(nèi)存里的東西原封不動(dòng)地Dump了出來(lái)。關(guān)鍵邏輯依然被VM保護(hù)著。
- 如果能看到清晰的C代碼邏輯 -> 這種情況幾乎不可能發(fā)生,除非你的目標(biāo)程序用的不是VMP的虛擬化保護(hù)。
第四部分:當(dāng) VMPDump 失敗時(shí)(99% 的情況)
你很可能會(huì)遇到以下情況,這都意味著 VMPDump 對(duì)你的目標(biāo)無(wú)效:
- VMPDump 無(wú)法找到 OEP 或 Handlers:直接報(bào)錯(cuò),提示分析失敗。
- VMPDump 在掃描過(guò)程中卡死或崩潰:說(shuō)明 VMP 的反分析機(jī)制觸發(fā)了。
- 生成的 Dump 文件無(wú)法運(yùn)行:最常見(jiàn)的結(jié)果,說(shuō)明 Dump 或修復(fù)過(guò)程出錯(cuò)了。
- 生成的 Dump 文件雖然能運(yùn)行,但關(guān)鍵功能(如注冊(cè))依然被保護(hù):證明了核心邏輯仍是虛擬化的。
當(dāng) VMPDump 失敗后,你必須回到正統(tǒng)的、手動(dòng)的逆向工程流程上來(lái):
- 手動(dòng)尋找 OEP:使用 x64dbg 和 ESP 定律。
- 手動(dòng) Dump:使用 Scylla 插件,在 OEP 處進(jìn)行內(nèi)存轉(zhuǎn)儲(chǔ)。
- 手動(dòng)修復(fù) IAT:使用 Scylla 的 IAT 搜索和修復(fù)功能。
- 手動(dòng)去虛擬化:這才是真正的挑戰(zhàn)。在 IDA 和 x64dbg 中,花費(fèi)數(shù)天、數(shù)周甚至數(shù)月的時(shí)間去:
- 分析 VM Entry 和 Dispatcher。
- 逐個(gè)逆向 VM Handlers 的功能。
- 編寫(xiě)腳本翻譯字節(jié)碼。
- 找到關(guān)鍵邏輯并進(jìn)行 Patch。
總結(jié)
使用 VMPDump 是一個(gè)“死馬當(dāng)活馬醫(yī)”的嘗試。你可以按照上述步驟操作一遍,體驗(yàn)一下自動(dòng)化工具的工作流程。但這趟旅程的終點(diǎn),大概率是讓你明白:沒(méi)有捷徑可走,對(duì)抗高強(qiáng)度的軟件保護(hù),唯有扎實(shí)的逆向工程基礎(chǔ)和巨大的耐心。
好的,我們來(lái)非常詳細(xì)地拆解和說(shuō)明 VMProtect 2 的加密功能和脫殼流程。這是一個(gè)極其復(fù)雜但非常有趣的技術(shù)話(huà)題。
?? 鄭重聲明:本內(nèi)容僅用于安全研究、技術(shù)學(xué)習(xí)和防御策略探討。對(duì)商業(yè)軟件進(jìn)行逆向工程、破解和傳播是違法行為,可能導(dǎo)致嚴(yán)重的法律后果。請(qǐng)?jiān)诜稍试S的范圍內(nèi)使用這些知識(shí),并尊重軟件開(kāi)發(fā)者的勞動(dòng)成果。
Part 1: VMProtect 2 的核心加密功能(它做了什么?)
VMProtect (VMP) 不是一個(gè)簡(jiǎn)單的“殼”,它是一個(gè)深度代碼混淆和虛擬化系統(tǒng)。其核心思想是讓你寫(xiě)的代碼,在你的電腦上都看不懂。
1. 虛擬化保護(hù) (Virtualization) - VMP的王牌
這是VMP最強(qiáng)大、最核心的功能。它能將你指定的代碼片段從標(biāo)準(zhǔn)的 x86/x64 匯編指令,轉(zhuǎn)換成一種自定義的、每次編譯都不一樣的字節(jié)碼(Bytecode)。
工作原理圖解
你的原始代碼 (x86匯編):
┌──────────────────────────────┐
│ MOV EAX, [EBP+8] ; 取一個(gè)參數(shù) │
│ ADD EAX, 10 ; 加上 10 │
│ CMP EAX, 1234 ; 和 1234 比較 │
│ JNE loc_failed ; 不相等就跳走 │
│ CALL CheckLicense ; 相等就調(diào)用授權(quán) │
└──────────────────────────────┘
↓ 經(jīng)過(guò) VMProtect 處理后...
┌──────────────────────────────────────────────────────┐
│ VM Entry(虛擬機(jī)入口) │
│ 功能: 保存當(dāng)前CPU的真實(shí)寄存器狀態(tài) (PUSHAD/PUSHFD), │
│ 初始化虛擬機(jī)的環(huán)境(虛擬寄存器、虛擬棧指針等)。 │
├──────────────────────────────────────────────────────┤
│ Bytecode(自定義的字節(jié)碼) │
│ 內(nèi)容: 0x47 0x12 0x89 0xAA 0x3F 0x91 0x05 ... │
│ 特點(diǎn): 這串字節(jié)碼不是x86指令,只有VMP自己生成的虛擬機(jī)能看懂。│
├──────────────────────────────────────────────────────┤
│ VM Dispatcher(調(diào)度器 / 指令分發(fā)器) │
│ 功能: 這是一個(gè)循環(huán),不斷地讀取下一個(gè)字節(jié)碼,然后根據(jù)這個(gè) │
│ 字節(jié)碼的值,跳轉(zhuǎn)到對(duì)應(yīng)的處理器(Handler)去執(zhí)行。 │
├──────────────────────────────────────────────────────┤
│ VM Handlers(處理器集合) │
│ 功能: 每一個(gè)Handler都是一小段真實(shí)的x86代碼,負(fù)責(zé)實(shí)現(xiàn) │
│ 一個(gè)字節(jié)碼的功能。例如: │
│ - Handler_0x47: 實(shí)現(xiàn)“虛擬加法”功能。 │
│ - Handler_0x12: 實(shí)現(xiàn)“虛擬數(shù)據(jù)加載”功能。 │
│ - Handler_0x89: 實(shí)現(xiàn)“虛擬比較”功能。 │
│ 特點(diǎn): 這些Handler之間互相穿插,充滿(mǎn)了垃圾指令,讓你難以分析。│
├──────────────────────────────────────────────────────┤
│ VM Exit(虛擬機(jī)退出) │
│ 功能: 執(zhí)行完虛擬化代碼后,從虛擬機(jī)環(huán)境退出,恢復(fù)之前保存的│
│ 真實(shí)CPU寄存器狀態(tài),然后返回到正常的x86代碼繼續(xù)執(zhí)行。│
└──────────────────────────────────────────────────────┘
為什么這讓破解變得極其困難?
- 獨(dú)一無(wú)二的指令集:每個(gè)被VMP保護(hù)的程序,其內(nèi)部的虛擬機(jī)、字節(jié)碼、Handler都是隨機(jī)生成的。你在A程序上分析得到的經(jīng)驗(yàn),完全無(wú)法用于B程序。
- 分析成本極高:你無(wú)法再用IDA Pro的F5功能直接看到C代碼。你必須先逆向分析出這套虛擬機(jī)的幾十甚至上百個(gè)Handler的功能,才能像翻譯密碼一樣,一點(diǎn)點(diǎn)地把字節(jié)碼“翻譯”回原始邏輯。這個(gè)過(guò)程可能需要數(shù)周甚至數(shù)月。
- 高度混淆:一個(gè)簡(jiǎn)單的
ADD EAX, 10指令,在VM里可能被分解成V_PUSH 10->V_PUSH EAX->V_ADD->V_POP EAX等多條虛擬指令,而每個(gè)虛擬指令的Handler本身又是高度混淆的真實(shí)x86代碼。
2. 變異混淆 (Mutation)
即使是不被虛擬化的代碼,VMP也會(huì)對(duì)其進(jìn)行“變異”,讓代碼變得臃腫、難以閱讀。
- 等價(jià)指令替換:
ADD EAX, 1會(huì)被替換成SUB EAX, -1或LEA EAX, [EAX+1]。 - 指令膨脹:
MOV EAX, EBX會(huì)變成PUSH EBX; POP EAX。 - 插入垃圾代碼:在有效指令之間插入大量無(wú)用的、迷惑性的指令,這些指令不影響程序執(zhí)行結(jié)果,但會(huì)嚴(yán)重干擾靜態(tài)分析。
- 控制流平坦化:將正常的
if-else或switch-case結(jié)構(gòu),變成一個(gè)巨大的while循環(huán)和狀態(tài)機(jī),每次都通過(guò)一個(gè)調(diào)度器來(lái)決定下一步執(zhí)行哪個(gè)代碼塊,打亂原始的邏輯順序。
3. 加殼與壓縮 (Packing)
這是VMP的基礎(chǔ)功能。它會(huì)將程序的原始代碼段(.text)、數(shù)據(jù)段(.data)等進(jìn)行加密和壓縮,然后包裹在一個(gè)“加載器”(Loader)外殼里。
- 運(yùn)行時(shí)解密:程序啟動(dòng)時(shí),首先執(zhí)行的是VMP的Loader。它會(huì)在內(nèi)存中解密、解壓原始的代碼和數(shù)據(jù)。
- 入口點(diǎn)修改:程序的入口點(diǎn)(OEP, Original Entry Point)被隱藏起來(lái),指向了VMP的Loader。
- 導(dǎo)入表保護(hù):程序調(diào)用的Windows API(如
MessageBoxA)地址不再靜態(tài)存儲(chǔ)在導(dǎo)入地址表(IAT)中,而是在運(yùn)行時(shí)動(dòng)態(tài)獲取,這使得分析者很難直接看到程序調(diào)用了哪些關(guān)鍵函數(shù)。
4. 反調(diào)試與反分析 (Anti-Debug)
VMP內(nèi)置了海量的反調(diào)試技術(shù),像地雷一樣遍布在代碼中,一旦發(fā)現(xiàn)自己被調(diào)試器(如x64dbg, IDA Pro)附加,就會(huì)改變行為。
- 調(diào)試器檢測(cè):通過(guò)
IsDebuggerPresent,CheckRemoteDebuggerPresent等API檢測(cè)。 - 時(shí)間檢測(cè):使用
RDTSC指令或GetTickCountAPI,通過(guò)計(jì)算兩段代碼執(zhí)行的時(shí)間差來(lái)判斷是否在單步調(diào)試(單步執(zhí)行會(huì)花費(fèi)更長(zhǎng)的時(shí)間)。 - 硬件斷點(diǎn)檢測(cè):檢查
DR0-DR7調(diào)試寄存器。 - 異常處理技巧:故意觸發(fā)一個(gè)異常,然后用
SEH(結(jié)構(gòu)化異常處理)來(lái)捕獲并執(zhí)行正常代碼。如果調(diào)試器干預(yù)了異常處理流程,程序就會(huì)崩潰。 - 完整性檢查:在運(yùn)行時(shí)計(jì)算自身代碼的校驗(yàn)和(CRC),如果發(fā)現(xiàn)代碼被修改(比如被打了補(bǔ)丁),就會(huì)退出。
Part 2: VMProtect 2 的脫殼流程(如何應(yīng)對(duì)?)
“脫殼”對(duì)于VMP來(lái)說(shuō)是一個(gè)非常寬泛的概念。完整的流程極其復(fù)雜,通常分為以下幾個(gè)階段。
階段一:準(zhǔn)備工作與環(huán)境配置
這是所有工作的基礎(chǔ)。你需要一個(gè)“干凈”且“武裝到牙齒”的分析環(huán)境。
-
工具清單:
- 調(diào)試器: x64dbg (主流選擇,插件豐富)
- 反匯編器: IDA Pro 7.x (靜態(tài)分析的王者)
- 反反調(diào)試插件: ScyllaHide (x64dbg插件,用于對(duì)抗各種反調(diào)試技術(shù))
- Dump工具: Scylla (集成在ScyllaHide中,用于dump內(nèi)存和修復(fù)IAT)
- PE工具: CFF Explorer 或 010 Editor (用于查看和編輯PE文件結(jié)構(gòu))
-
環(huán)境配置:
- 虛擬機(jī):強(qiáng)烈建議在VMware或VirtualBox中進(jìn)行分析,防止搞垮物理機(jī)。
- 配置ScyllaHide:在x64dbg中加載ScyllaHide插件,并盡可能多地勾選對(duì)抗選項(xiàng),如
NtQueryInformationProcess,GetTickCount等。
階段二:尋找OEP (Original Entry Point - 真實(shí)入口點(diǎn))
由于VMP加了殼,第一步就是找到VMP的Loader執(zhí)行完畢,即將跳轉(zhuǎn)到程序原始代碼的那個(gè)點(diǎn)(OEP)。
常用方法:ESP定律
- 用x64dbg加載目標(biāo)程序,程序會(huì)停在系統(tǒng)斷點(diǎn)。
- 在x64dbg的命令行輸入
bp GetModuleHandleA,然后按F9運(yùn)行。程序會(huì)在加載系統(tǒng)DLL時(shí)斷下。 - 找到ESP寄存器的值,右鍵 -> 在內(nèi)存窗口中轉(zhuǎn)到。
- 在內(nèi)存窗口中,選中ESP指向的前4個(gè)字節(jié),右鍵 -> 斷點(diǎn) -> 硬件, 訪(fǎng)問(wèn) (Dword)。
- 按F9繼續(xù)運(yùn)行。程序會(huì)在VMP的殼代碼即將
RET或JMP到OEP之前,訪(fǎng)問(wèn)這個(gè)棧地址時(shí)斷下。 - 此時(shí),單步執(zhí)行(F7/F8),很大概率就會(huì)跳轉(zhuǎn)到OEP。
如何判斷找到了OEP?
- 代碼看起來(lái)像是正常的函數(shù)開(kāi)頭(如
PUSH EBP; MOV EBP, ESP)。 - 可以看到清晰的API調(diào)用。
- IDA Pro能夠很好地分析這部分代碼。
階段三:Dump內(nèi)存鏡像
在OEP處,程序的代碼和數(shù)據(jù)已經(jīng)在內(nèi)存中解密,此時(shí)需要將它們從內(nèi)存中“倒”出來(lái),存成一個(gè)新的EXE文件。
- 在x64dbg中,停在OEP處。
- 打開(kāi)插件菜單 -> Scylla。
- 在Scylla窗口中,確保進(jìn)程已附加,OEP地址已自動(dòng)填好(如果不對(duì),手動(dòng)修改為OEP的RVA)。
- 點(diǎn)擊 "IAT Autosearch" 按鈕,Scylla會(huì)自動(dòng)尋找并定位導(dǎo)入地址表。
- 點(diǎn)擊 "Get Imports" 獲取所有導(dǎo)入函數(shù)。
- 點(diǎn)擊 "Dump" 將內(nèi)存轉(zhuǎn)儲(chǔ)到文件(例如
dumped.exe)。 - 點(diǎn)擊 "Fix Dump",選擇剛才的
dumped.exe,Scylla會(huì)嘗試修復(fù)導(dǎo)入表,并生成一個(gè)新文件(例如dumped_SCY.exe)。
階段四:去虛擬化 (De-virtualization) - 最核心、最艱難的一步
僅僅Dump出文件是遠(yuǎn)遠(yuǎn)不夠的,因?yàn)殛P(guān)鍵邏輯(如注冊(cè)碼驗(yàn)證)仍然是被虛擬化的字節(jié)碼。去虛擬化的目標(biāo)就是理解虛擬機(jī)的設(shè)計(jì)并還原原始邏輯。
-
識(shí)別VM Entry:在dump出的文件中,用IDA Pro打開(kāi)。找到調(diào)用虛擬化代碼的地方。通常這個(gè)地方會(huì)有一系列
PUSH指令(保存環(huán)境),然后是一個(gè)JMP到一個(gè)非常復(fù)雜混亂的區(qū)域,這個(gè)區(qū)域就是VM Entry。 -
分析VM架構(gòu):這是純粹的逆向工程體力活。
- 定位Dispatcher:VM Entry之后,你會(huì)找到一個(gè)核心的指令分發(fā)器。它通常是一個(gè)循環(huán),根據(jù)字節(jié)碼的值跳轉(zhuǎn)到不同的Handler。
- 分析Handlers:以Dispatcher為中心,逐個(gè)分析它跳轉(zhuǎn)到的每一個(gè)Handler。
- 在x64dbg中對(duì)某個(gè)Handler下斷點(diǎn),觀察它執(zhí)行前后對(duì)虛擬環(huán)境(通常在棧上分配的一塊內(nèi)存)做了什么修改。
- 例如,你發(fā)現(xiàn)某個(gè)Handler總是把虛擬棧頂?shù)膬蓚€(gè)值相加,那么你就可以把它標(biāo)記為
V_ADD。 - 另一個(gè)Handler可能是從某個(gè)地方加載一個(gè)常量,你可以標(biāo)記為
V_LOAD_CONST。
- 重復(fù)此過(guò)程:你需要分析出盡可能多的Handler的功能,為它們命名。這個(gè)過(guò)程極其枯燥,可能需要分析幾十到上百個(gè)Handlers。
-
提取并反編譯字節(jié)碼:
- 當(dāng)你大致搞清楚了虛擬機(jī)的指令集后,就可以在x64dbg中dump出被虛擬化的那段字節(jié)碼。
- 編寫(xiě)一個(gè)簡(jiǎn)單的腳本(如Python),根據(jù)你分析出的Handler功能,將字節(jié)碼“翻譯”成可讀的偽代碼。
Python# 偽代碼示例 bytecode = [0x10, 0x04, 0x12, 0x34, 0x56, 0x78, 0x25, 0x30, 0x01] # 翻譯后可能得到 # V_LOAD_CONST 0x12345678 (指令0x10) # V_CMP_WITH_INPUT (指令0x25) # V_JNE_FAIL (指令0x30) -
還原邏輯并Patch:
- 從翻譯出的偽代碼中,你就能看懂原始的驗(yàn)證邏輯了。
- 暴力破解:找到關(guān)鍵的跳轉(zhuǎn),例如
V_JNE_FAIL,然后在實(shí)現(xiàn)這個(gè)虛擬跳轉(zhuǎn)的真實(shí)x64代碼處,將其修改(如JNE改成JMP或NOP),從而繞過(guò)驗(yàn)證。這通常是最高效的方法。 - 算法還原:如果你的目標(biāo)是寫(xiě)注冊(cè)機(jī),那就需要完全逆向整個(gè)算法,這難度極高。
階段五:修復(fù)與清理
- 修復(fù)其他VMP“陷阱”:如完整性檢查(CRC Check)。你需要找到計(jì)算CRC的地方,然后將其N(xiāo)OP掉,或者在Patch后重新計(jì)算正確的CRC值并填回去。
- 測(cè)試:運(yùn)行你最終patch過(guò)的文件,確保所有功能正常,并且破解有效。
總結(jié)
| 步驟 | 目標(biāo) | 難度 | 常用工具 | 關(guān)鍵技巧 |
|---|---|---|---|---|
| 階段一: 準(zhǔn)備 | 搭建穩(wěn)定的分析環(huán)境 | ★☆☆☆☆ | VMware, x64dbg, ScyllaHide | 反反調(diào)試配置 |
| 階段二: 尋OEP | 找到殼執(zhí)行完后的真實(shí)入口 | ★★★☆☆ | x64dbg | ESP定律、硬件斷點(diǎn) |
| 階段三: Dump | 將內(nèi)存中的解密代碼存盤(pán) | ★★☆☆☆ | Scylla, PE-sieve | 修復(fù)IAT |
| 階段四: 去虛擬化 | 分析VM,還原原始邏輯 | ★★★★★ | IDA Pro, x64dbg | 耐心、毅力、逆向思維 |
| 階段五: 修復(fù) | 移除其他保護(hù),完成破解 | ★★★☆☆ | x64dbg, 010 Editor | 定位CRC校驗(yàn) |
結(jié)論:對(duì)VMP2進(jìn)行所謂的“暴力破解”幾乎是不可能的。真正的“破解”是一個(gè)極其漫長(zhǎng)且復(fù)雜的逆向工程過(guò)程,其核心是對(duì) VMProtect的虛擬機(jī)進(jìn)行“去虛擬化”分析。這需要深厚的匯編、操作系統(tǒng)底層知識(shí),以及超乎常人的耐心和毅力。對(duì)于大多數(shù)情況,分析者會(huì)選擇最簡(jiǎn)單的路徑,即只分析到能繞過(guò)驗(yàn)證的關(guān)鍵跳轉(zhuǎn),然后進(jìn)行Patch,而不會(huì)嘗試去完整地還原所有被虛擬化的代碼。
VMProtect 2 加密功能與脫殼流程詳解
?? 法律聲明:以下內(nèi)容僅供安全研究和學(xué)習(xí)用途。未經(jīng)授權(quán)對(duì)軟件進(jìn)行逆向工程可能違反法律和軟件許可協(xié)議。
?? Part 1: VMProtect 2 的核心加密功能
1.1 三大核心保護(hù)技術(shù)
┌─────────────────────────────────────────────────────┐
│ VMProtect 2 保護(hù)技術(shù)架構(gòu)圖 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌───────────┐ │
│ │ Virtualization│ │ Mutation │ │ Packing │ │
│ │ 虛擬化 │ │ 變異混淆 │ │ 加殼 │ │
│ └─────────────┘ └──────────────┘ └───────────┘ │
│ ↓ ↓ ↓ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Anti-Debug & Anti-Dump │ │
│ │ 反調(diào)試 & 反轉(zhuǎn)儲(chǔ) │ │
│ └─────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
1.2 虛擬化保護(hù) (Virtualization) - 最強(qiáng)大的功能
原理圖解
原始代碼(x86匯編):
┌──────────────────────────────┐
│ MOV EAX, [EBP+8] │
│ ADD EAX, 10 │
│ CMP EAX, 64 │
│ JNE loc_failed │
│ CALL CheckLicense │
└──────────────────────────────┘
↓ VMProtect 處理
┌──────────────────────────────────────────────────────┐
│ VM Entry(虛擬機(jī)入口) │
│ ? 保存真實(shí)寄存器狀態(tài) │
│ ? 初始化虛擬寄存器 │
│ ? 加載字節(jié)碼指針 │
├──────────────────────────────────────────────────────┤
│ Bytecode(自定義字節(jié)碼) │
│ 0x47 0x12 0x89 0xAA 0x3F 0x91 0x05 ... │
│ (每次編譯都不同!) │
├──────────────────────────────────────────────────────┤
│ VM Dispatcher(調(diào)度器) │
│ while (true) { │
│ opcode = *bytecode_ptr++; │
│ jump handlers[opcode]; // 跳轉(zhuǎn)到處理器 │
│ } │
├──────────────────────────────────────────────────────┤
│ VM Handlers(處理器集合) │
│ Handler_0x47: // 可能是 PUSH │
│ vm_stack[++sp] = vm_regs[...]; │
│ 混淆代碼... │
│ return to dispatcher; │
│ │
│ Handler_0x12: // 可能是 ADD │
│ vm_regs[a] = vm_regs[b] + decrypt(...); │
│ 垃圾代碼... │
│ return to dispatcher; │
│ ...(可能有 50-200+ 個(gè) handlers) │
├──────────────────────────────────────────────────────┤
│ VM Exit(虛擬機(jī)退出) │
│ ? 恢復(fù)真實(shí)寄存器 │
│ ? 返回到未保護(hù)代碼 │
└──────────────────────────────────────────────────────┘
虛擬化的"恐怖"之處
// 原始代碼:1條指令
if (serial == 0x12345678) return true;
// 虛擬化后:可能變成 100+ 條等價(jià)操作
vm_reg[0] = decrypt_constant(bytecode[0]); // 獲取 serial
vm_reg[1] = 0x12;
vm_reg[1] = (vm_reg[1] << 8) | 0x34;
vm_reg[1] = (vm_reg[1] << 8) | 0x56;
vm_reg[1] = (vm_reg[1] << 8) | 0x78;
vm_reg[2] = vm_reg[0] ^ vm_reg[1];
vm_reg[3] = ~vm_reg[2];
vm_reg[3] = vm_reg[3] + 1;
if (vm_reg[3] == 0) vm_flag = 1;
// ... 還有幾十行垃圾指令
1.3 變異混淆 (Mutation)
代碼膨脹技術(shù)
原始代碼:
ADD EAX, 5
變異后(可能變成以下任意一種):
方式1: 等價(jià)替換
SUB EAX, -5
方式2: 多步驟分解
ADD EAX, 3
ADD EAX, 2
方式3: 加入垃圾指令
PUSH EBX
MOV EBX, 5
ADD EAX, EBX
POP EBX
方式4: 使用復(fù)雜運(yùn)算
LEA EAX, [EAX + 5]
方式5: 插入無(wú)效代碼
JMP $+5
DB 0xE8, 0xFF, 0xFF // 看起來(lái)像CALL,但跳過(guò)了
ADD EAX, 5
控制流平坦化 (Control Flow Flattening)
原始代碼:
A → B → C → D
平坦化后:
┌→ Dispatcher ←┐
│ ↓ │
│ switch(state) {
│ case 1: A; state=3; break;
│ case 2: C; state=4; break;
│ case 3: B; state=2; break;
│ case 4: D; state=0; break;
│ } │
└─────────────┘
1.4 加殼與壓縮 (Packing)
原始PE文件結(jié)構(gòu):
┌────────────────┐
│ PE Headers │
├────────────────┤
│ .text (100KB) │ ← 代碼段
├────────────────┤
│ .data (50KB) │ ← 數(shù)據(jù)段
├────────────────┤
│ .rsrc (200KB) │ ← 資源
└────────────────┘
↓ VMP 加殼處理
加殼后的文件:
┌──────────────────────┐
│ Original PE Headers │ ← 保留原始頭(部分修改)
├──────────────────────┤
│ VMP Loader │ ← 解密解壓代碼
├──────────────────────┤
│ Encrypted .text │ ← 加密的代碼段
│ (壓縮 + 加密) │
├──────────────────────┤
│ Encrypted .data │
├──────────────────────┤
│ VM Core Engine │ ← 虛擬機(jī)引擎代碼
├──────────────────────┤
│ Encrypted Bytecode │ ← 虛擬化后的字節(jié)碼
├──────────────────────┤
│ Anti-Debug Code │ ← 反調(diào)試模塊
├──────────────────────┤
│ Import Protection │ ← 導(dǎo)入表保護(hù)
└──────────────────────┘
1.5 反調(diào)試與反分析
多層反調(diào)試技術(shù)清單
╔══════════════════════════════════════════════════════════╗
║ VMProtect 2 反調(diào)試技術(shù)大全 ║
╠══════════════════════════════════════════════════════════╣
║ [硬件層] ║
║ ? 硬件斷點(diǎn)檢測(cè) (DR0-DR7寄存器) ║
║ ? 單步檢測(cè) (Trap Flag) ║
║ ? 性能計(jì)數(shù)器檢測(cè) ║
╠══════════════════════════════════════════════════════════╣
║ [API層] ║
║ ? IsDebuggerPresent ║
║ ? CheckRemoteDebuggerPresent ║
║ ? NtQueryInformationProcess (ProcessDebugPort) ║
║ ? NtSetInformationThread (HideFromDebugger) ║
║ ? OutputDebugString 技巧 ║
╠══════════════════════════════════════════════════════════╣
║ [時(shí)間檢測(cè)] ║
║ ? RDTSC 指令(檢測(cè)時(shí)間差) ║
║ ? GetTickCount / QueryPerformanceCounter ║
║ ? 兩次執(zhí)行間隔異常 → 判定為單步調(diào)試 ║
╠══════════════════════════════════════════════════════════╣
║ [進(jìn)程/線(xiàn)程檢測(cè)] ║
║ ? 檢測(cè)調(diào)試器進(jìn)程名(OllyDbg.exe, x64dbg.exe等) ║
║ ? 檢測(cè)調(diào)試器窗口類(lèi)名 ║
║ ? 父進(jìn)程檢測(cè)(是否被調(diào)試器啟動(dòng)) ║
╠══════════════════════════════════════════════════════════╣
║ [內(nèi)存檢測(cè)] ║
║ ? PEB.BeingDebugged 標(biāo)志檢測(cè) ║
║ ? PEB.NtGlobalFlag 檢測(cè) ║
║ ? Heap Flags 檢測(cè) ║
║ ? 校驗(yàn)和檢測(cè)(代碼被修改) ║
╠══════════════════════════════════════════════════════════╣
║ [異常處理] ║
║ ? SEH 反調(diào)試(異常處理鏈) ║
║ ? VEH 反調(diào)試(向量化異常) ║
║ ? INT 3 / INT 2D 檢測(cè) ║
║ ? 故意觸發(fā)異常,檢查處理流程 ║
╠══════════════════════════════════════════════════════════╣
║ [虛擬機(jī)檢測(cè)] ║
║ ? VMware / VirtualBox 檢測(cè) ║
║ ? CPUID 指令檢測(cè) ║
║ ? 虛擬化環(huán)境特征檢測(cè) ║
╚══════════════════════════════════════════════════════════╝
?? Part 2: VMProtect 2 脫殼流程詳解
2.1 脫殼總體流程圖
┌─────────────────────────────────────────────────────────────┐
│ VMProtect 2 脫殼流程 │
└─────────────────────────────────────────────────────────────┘
階段一:環(huán)境準(zhǔn)備
├─ 安裝必要工具
├─ 配置反反調(diào)試環(huán)境
└─ 樣本初步分析
↓
階段二:定位 OEP (Original Entry Point)
├─ 方法1: ESP定律
├─ 方法2: 內(nèi)存斷點(diǎn)
├─ 方法3: 最后一次異常
└─ 方法4: SFX自解壓特征
↓
階段三:Dump內(nèi)存鏡像
├─ 使用 Scylla / OllyDumpEx
├─ 完整dump進(jìn)程內(nèi)存
└─ 修正 ImageSize
↓
階段四:修復(fù)導(dǎo)入表 (IAT)
├─ 自動(dòng)搜索IAT
├─ 修復(fù)Thunk
└─ 重建導(dǎo)入表
↓
階段五:去虛擬化 (最難!)
├─ 識(shí)別VM Entry
├─ 分析VM架構(gòu)
├─ 提取Handler
├─ 反編譯字節(jié)碼
└─ 重建原始邏輯
↓
階段六:修復(fù)與測(cè)試
├─ 修復(fù)重定位表
├─ 修復(fù)資源
├─ 刪除反調(diào)試代碼
└─ 測(cè)試運(yùn)行
2.2 階段一:環(huán)境準(zhǔn)備與工具配置
必備工具清單
┌──────────────────────────────────────────────────────────┐
│ 工具類(lèi)型 工具名稱(chēng) 用途 │
├──────────────────────────────────────────────────────────┤
│ 調(diào)試器 x64dbg 主力調(diào)試 │
│ OllyDbg 2.01 32位程序備用 │
│ WinDbg 內(nèi)核態(tài)調(diào)試 │
├──────────────────────────────────────────────────────────┤
│ 反匯編器 IDA Pro 7.x 靜態(tài)分析 │
│ Ghidra 開(kāi)源替代品 │
├──────────────────────────────────────────────────────────┤
│ 反反調(diào)試 ScyllaHide 對(duì)抗反調(diào)試 │
│ TitanHide 驅(qū)動(dòng)級(jí)隱藏 │
│ HideOD 隱藏OllyDbg │
├──────────────────────────────────────────────────────────┤
│ Dump工具 Scylla dump+IAT修復(fù) │
│ OllyDumpEx Olly插件 │
│ PE-sieve 內(nèi)存掃描 │
├──────────────────────────────────────────────────────────┤
│ PE工具 CFF Explorer PE結(jié)構(gòu)查看 │
│ LordPE PE編輯 │
│ StudPE PE分析 │
├──────────────────────────────────────────────────────────┤
│ 去虛擬化 VMPDump (部分有效) 自動(dòng)化工具 │
│ Code Virtualizer Tools 輔助分析 │
│ 自寫(xiě)腳本 IDAPython等 │
├──────────────────────────────────────────────────────────┤
│ 監(jiān)控工具 Process Monitor 文件/注冊(cè)表監(jiān)控 │
│ API Monitor API調(diào)用監(jiān)控 │
│ WinAPIOverride API Hook │
└──────────────────────────────────────────────────────────┘
x64dbg 配置示例
1. 安裝 ScyllaHide 插件
- 下載 ScyllaHide.zip
- 解壓到 x64dbg\plugins\
- 重啟 x64dbg
- 插件 → ScyllaHide → Options
- 勾選所有反調(diào)試對(duì)抗選項(xiàng):
? NtQueryInformationProcess
? NtQuerySystemInformation
? NtSetInformationThread
? NtClose
? OutputDebugString
? GetTickCount
? ...
2. 配置選項(xiàng)
- 選項(xiàng) → 首選項(xiàng) → 異常
? 忽略所有異常(第一輪)
- 選項(xiàng) → 首選項(xiàng) → 引擎
? 禁用調(diào)試特權(quán)
? 保存數(shù)據(jù)庫(kù)
3. 安裝 Scylla
- 插件 → Scylla → Hide from PEB
2.3 階段二:定位 OEP (真實(shí)入口點(diǎn))
方法1: ESP 定律(最常用)
原理:
VMP的殼代碼執(zhí)行完后,會(huì)用類(lèi)似 RETN 的指令跳轉(zhuǎn)到OEP
此時(shí)ESP指向的棧位置存放著OEP地址
步驟:
1. x64dbg 加載程序(暫停在系統(tǒng)斷點(diǎn))
2. 命令行執(zhí)行:
bp GetProcAddress // 或者 bp LoadLibraryA
F9 運(yùn)行
3. 斷下后,在命令行:
hr esp // 對(duì)ESP指向的內(nèi)存地址下硬件訪(fǎng)問(wèn)斷點(diǎn)
4. F9 繼續(xù)運(yùn)行
5. 程序會(huì)在訪(fǎng)問(wèn)這個(gè)棧地址時(shí)斷下
觀察此時(shí)的指令,通常是:
PUSH xxxxxxxx
RETN ← 斷在這里
6. 單步執(zhí)行 (F7),會(huì)跳轉(zhuǎn)到一個(gè)新地址
此時(shí)查看代碼,如果看到:
- 正常的函數(shù)序言(PUSH EBP; MOV EBP,ESP)
- 或者開(kāi)始調(diào)用API
- 代碼段名稱(chēng)變成 .text
→ 這就是 OEP!
7. 記錄當(dāng)前地址,例如:00401000
方法2: 內(nèi)存斷點(diǎn)法
步驟:
1. 運(yùn)行程序,在 x64dbg 中打開(kāi)內(nèi)存映射 (Alt+M)
2. 找到 .text 節(jié)(代碼段)
- 名稱(chēng):.text
- 權(quán)限:ER-(可執(zhí)行,可讀)
- 大小:通常最大的可執(zhí)行段
3. 右鍵 → 在反匯編中轉(zhuǎn)到 → 地址
記錄起始地址,例如:00401000
4. 在命令行設(shè)置內(nèi)存訪(fǎng)問(wèn)斷點(diǎn):
bpm 00401000, 1, x // 在00401000地址,1字節(jié),執(zhí)行時(shí)中斷
5. F9 運(yùn)行,當(dāng)?shù)谝淮螆?zhí)行 .text 段代碼時(shí)會(huì)斷下
→ 這通常就是 OEP 附近
注意:VMP可能會(huì)在OEP前多次訪(fǎng)問(wèn).text段進(jìn)行完整性檢查
方法3: 異常斷點(diǎn)法
原理:VMP使用大量異常作為混淆手段,最后一個(gè)異常通常在OEP附近
步驟:
1. x64dbg → 選項(xiàng) → 首選項(xiàng) → 異常
- 取消"忽略所有異常"
- 只保留"忽略INT 3"
2. F9 運(yùn)行,每次異常斷下時(shí):
- 查看當(dāng)前位置是否在 .text 段
- 如果不是,按 Shift+F9 傳遞異常
3. 重復(fù)多次后,最終會(huì)停在接近OEP的地方
4. 配合反匯編判斷是否為OEP
如何確認(rèn)找到的是真正的 OEP?
特征檢查清單:
? 代碼段名稱(chēng)是 .text 或 CODE
? 可以看到清晰的函數(shù)調(diào)用(CALL API)
? 有正常的函數(shù)序言:
PUSH EBP
MOV EBP, ESP
SUB ESP, xxx
? 或者是 WinMain 的標(biāo)準(zhǔn)開(kāi)頭:
PUSH EBX
PUSH ESI
PUSH EDI
? 附近有字符串引用
? IDA Pro F5能正常反編譯
? 地址接近PE頭中的 AddressOfEntryPoint(但不完全相同)
2.4 階段三:Dump 內(nèi)存鏡像
使用 Scylla 進(jìn)行 Dump
步驟:
1. 在 OEP 處暫停程序(上一步已找到)
2. x64dbg → 插件 → Scylla
3. Scylla 窗口設(shè)置:
[*] Attach to: [選擇目標(biāo)進(jìn)程]
[*] OEP: 00001000 ← 輸入相對(duì)于ImageBase的偏移
[*] IAT AutoSearch ← 自動(dòng)搜索導(dǎo)入表
4. 點(diǎn)擊 "IAT Autosearch"
- 如果成功,會(huì)顯示:
Found IAT: 00402000 (Size: 0x400)
5. 點(diǎn)擊 "Get Imports"
- Scylla會(huì)分析IAT,列出所有導(dǎo)入函數(shù)
- 檢查是否有 "invalid" 標(biāo)記的項(xiàng)
6. 如果有invalid項(xiàng):
- 右鍵 → Cut Thunk
- 或手動(dòng)修復(fù)
7. 點(diǎn)擊 "Dump"
- 選擇保存位置,例如:dumped.exe
8. 點(diǎn)擊 "Fix Dump"
- 選擇剛才保存的 dumped.exe
- Scylla會(huì)修復(fù)IAT并生成 dumped_SCY.exe
手動(dòng) Dump (OllyDumpEx)
在 OllyDbg 中:
1. 插件 → OllyDumpEx
2. 設(shè)置參數(shù):
Base Address: 00400000 ← 從內(nèi)存窗口查看
Entry Point: 00001000 ← OEP的RVA
Size: 00050000 ← 從PE頭SizeOfImage獲取
3. 勾選選項(xiàng):
? Rebuild Import
? Fix Dump
4. 點(diǎn)擊 "Dump" 保存
注意:手動(dòng)dump可能需要后續(xù)修復(fù)重定位表
2.5 階段四:修復(fù)導(dǎo)入表 (IAT Fix)
IAT修復(fù)原理
正常程序的IAT:
┌──────────────────────────┐
│ .idata 節(jié) │
├──────────────────────────┤
│ Import Directory │
│ DLL: kernel32.dll │
│ Thunk Array: │
│ → CreateFileA │
│ → ReadFile │
│ → CloseHandle │
├──────────────────────────┤
│ IAT (導(dǎo)入地址表) │
│ 0x402000: 76A81234 ← CreateFileA的真實(shí)地址
│ 0x402004: 76A85678 ← ReadFile的真實(shí)地址
│ 0x402008: 76A89ABC ← CloseHandle的真實(shí)地址
└──────────────────────────┘
VMP加密后:
┌──────────────────────────┐
│ Import Directory 被破壞 │
│ 或指向假數(shù)據(jù) │
├──────────────────────────┤
│ IAT 被加密/動(dòng)態(tài)生成 │
│ 0x402000: 00000000 │
│ 運(yùn)行時(shí)才填充真實(shí)地址 │
└──────────────────────────┘
修復(fù)目標(biāo):
重建 Import Directory 和 IAT,使dump出的文件能獨(dú)立運(yùn)行
Scylla 自動(dòng)修復(fù)
1. "IAT Autosearch" 原理:
- 在OEP附近掃描內(nèi)存
- 查找連續(xù)的、指向DLL代碼段的指針
- 識(shí)別為IAT
2. "Get Imports" 原理:
- 讀取IAT中的地址
- 通過(guò)地址找到對(duì)應(yīng)的DLL模塊
- 在DLL的導(dǎo)出表中查找函數(shù)名
- 重建導(dǎo)入表
3. 常見(jiàn)問(wèn)題及解決:
問(wèn)題1: "Found invalid imports"
原因:某些地址不是真正的API地址
解決:右鍵 → Cut Thunk (刪除)
問(wèn)題2: "IAT not found"
原因:IAT被嚴(yán)重混淆
解決:手動(dòng)搜索
- 在OEP處下斷點(diǎn)
- 運(yùn)行程序,觀察哪些API被調(diào)用
- 在內(nèi)存中搜索這些API的地址
- 找到IAT的大致范圍后,手動(dòng)指定
手動(dòng)修復(fù) IAT(高級(jí))
工具:IDA Pro + IDAPython
腳本示例:
import idaapi
import idc
def rebuild_iat(start, end):
"""重建IAT"""
addr = start
imports = []
while addr < end:
# 讀取指針
ptr = idc.get_qword(addr) if idaapi.get_inf_structure().is_64bit() \
else idc.get_wide_dword(addr)
# 檢查是否指向DLL代碼
seg = idaapi.getseg(ptr)
if seg and seg.type == idaapi.SEG_XTRN:
# 獲取函數(shù)名
name = idc.get_name(ptr)
if name:
imports.append((addr, name))
print(f"{hex(addr)}: {name}")
addr += 4 if not idaapi.get_inf_structure().is_64bit() else 8
return imports
# 使用
iat_start = 0x00402000 # 手動(dòng)確定的IAT起始
iat_end = 0x00402400 # 手動(dòng)確定的IAT結(jié)束
rebuild_iat(iat_start, iat_end)
2.6 階段五:去虛擬化 (De-virtualization) - 核心難點(diǎn)
5.1 識(shí)別 VM Entry
特征碼搜索法:
在IDA Pro中搜索以下特征:
特征1: 大量 PUSHFD / PUSHAD (保存寄存器)
60 PUSHAD
9C PUSHFD
...
大量MOV/PUSH操作
...
特征2: 跳轉(zhuǎn)到動(dòng)態(tài)計(jì)算的地址
MOV EAX, [EBP+var_XX]
JMP [EAX*4+table_base] ← VM Dispatcher
特征3: 大量的RET鏈
RETN
RETN
RETN ← 每個(gè)Handler結(jié)束都用RETN返回Dispatcher
手動(dòng)識(shí)別:
1. 從OEP開(kāi)始F5查看
2. 如果看到極其復(fù)雜的代碼,無(wú)法理解的嵌套
3. 大量單字節(jié)變量操作
4. 頻繁的函數(shù)調(diào)用但沒(méi)有明顯邏輯
→ 這些都是VM化的代碼
5.2 分析 VM 架構(gòu)
目標(biāo):理解這個(gè)虛擬機(jī)的"指令集"
┌─────────────────────────────────────────┐
│ VM 架構(gòu)分析清單 │
├─────────────────────────────────────────┤
│ 1. 虛擬寄存器 (Virtual Registers) │
│ - 在哪里存儲(chǔ)?(棧上/全局內(nèi)存) │
│ - 有多少個(gè)?(通常8-16個(gè)) │
│ - 如何訪(fǎng)問(wèn)? │
├─────────────────────────────────────────┤
│ 2. 虛擬棧 (Virtual Stack) │
│ - Stack Pointer 在哪? │
│ - PUSH/POP 如何實(shí)現(xiàn)? │
├─────────────────────────────────────────┤
│ 3. 字節(jié)碼 (Bytecode) │
│ - 存儲(chǔ)位置 │
│ - 讀取方式 │
│ - 格式:定長(zhǎng)?變長(zhǎng)? │
├─────────────────────────────────────────┤
│ 4. Dispatcher (調(diào)度器) │
│ - 入口地址 │
│ - 解碼邏輯 │
│ - 跳轉(zhuǎn)表結(jié)構(gòu) │
├─────────────────────────────────────────┤
│ 5. Handlers (處理器) │
│ - 數(shù)量統(tǒng)計(jì) │
│ - 功能分類(lèi) │
│ - 參數(shù)傳遞方式 │
└─────────────────────────────────────────┘
5.3 提取 VM Handlers
方法:動(dòng)態(tài)跟蹤 + 靜態(tài)分析結(jié)合
x64dbg 腳本示例:
--------------------
// 在 Dispatcher 下斷點(diǎn)
bp 00401234 // Dispatcher地址
// 記錄每個(gè)Handler
var handler_list
log "Opcode, Handler Address"
loop:
run
mov eax, [esp] // 假設(shè)棧頂是返回地址
mov ebx, [ebp+opcode] // 假設(shè)EBP+offset存放opcode
log "{ebx}, {eax}"
esti // 執(zhí)行直到返回
jmp loop
--------------------
輸出示例:
Opcode, Handler Address
0x01, 0x00401500 ← Handler_01
0x02, 0x00401650 ← Handler_02
0x03, 0x004017A0 ← Handler_03
...
然后在IDA中逐個(gè)分析這些地址的代碼
5.4 反編譯字節(jié)碼
步驟一:提取字節(jié)碼
-----------------
x64dbg內(nèi)存dump:
1. 運(yùn)行到VM Entry后斷下
2. 在dump窗口,找到字節(jié)碼起始地址(通常從某個(gè)寄存器得到)
例如:EBP+0x100 指向字節(jié)碼
3. 右鍵 → Dump Memory to File
4. 保存為 bytecode.bin
步驟二:編寫(xiě)反編譯器
-------------------
Python示例:
class VMDecompiler:
def __init__(self, bytecode_file):
with open(bytecode_file, 'rb') as f:
self.bytecode = f.read()
self.pc = 0 # Program Counter
# Handler映射表(需手動(dòng)填充)
self.handlers = {
0x01: self.vm_push,
0x02: self.vm_pop,
0x03: self.vm_add,
0x04: self.vm_sub,
0x05: self.vm_mov,
0x10: self.vm_cmp,
0x11: self.vm_jne,
# ... 更多
}
def read_byte(self):
val = self.bytecode[self.pc]
self.pc += 1
return val
def read_dword(self):
val = int.from_bytes(self.bytecode[self.pc:self.pc+4], 'little')
self.pc += 4
return val
def vm_push(self):
val = self.read_dword()
print(f"PUSH {hex(val)}")
def vm_add(self):
print("ADD vreg0, vreg1")
def vm_jne(self):
offset = self.read_dword()
print(f"JNE {hex(offset)}")
# ... 實(shí)現(xiàn)所有handler
def decompile(self):
while self.pc < len(self.bytecode):
opcode = self.read_byte()
if opcode in self.handlers:
self.handlers[opcode]()
else:
print(f"Unknown opcode: {hex(opcode)}")
break
# 使用
dec = VMDecompiler('bytecode.bin')
dec.decompile()
5.5 重建原始邏輯(最終目標(biāo))
輸入:反編譯的字節(jié)碼
PUSH 0x12345678
PUSH [ebp+8]
CALL vm_hash
CMP vreg0, vreg1
JNE fail_label
PUSH 1
RET
輸出:還原的C代碼
int check_serial(char* input) {
int hashed = vm_hash(input);
if (hashed == 0x12345678) {
return 1;
}
return 0;
}
然后可以:
1. 用nop掉驗(yàn)證邏輯
2. 或者寫(xiě)注冊(cè)機(jī)(基于還原的算法)
3. 或者直接patch跳轉(zhuǎn)
2.7 階段六:修復(fù)與測(cè)試
6.1 刪除反調(diào)試代碼(暴力法)
在IDA Pro中搜索并NOP以下代碼:
1. IsDebuggerPresent
搜索:CALL IsDebuggerPresent
替換:NOP x 5
2. 時(shí)間檢測(cè)
搜索:RDTSC / CPUID
替換:XOR EAX,EAX (清零結(jié)果)
3. 檢查BeingDebugged
搜索:MOV AL, [FS:0x30] + 0x02
替換:MOV AL, 0
4. 異常反調(diào)試
搜索:INT 3 / INT 2D
替換:NOP
6.2 修復(fù)重定位表
使用 CFF Explorer:
1. 打開(kāi) dump_fixed.exe
2. Relocation Editor
3. 如果顯示 "No relocations":
- 可能需要手動(dòng)重建
- 或者將ImageBase固定為原始值
手動(dòng)fix (如果必要):
使用 LordPE:
1. PE Editor
2. Section Headers → .reloc
3. 如果損壞,從原始文件復(fù)制.reloc節(jié)
6.3 測(cè)試流程
1. 靜態(tài)測(cè)試
- CFF Explorer 打開(kāi),檢查PE結(jié)構(gòu)完整性
- ImportREC 驗(yàn)證導(dǎo)入表
2. 沙箱測(cè)試
- 虛擬機(jī)中運(yùn)行
- 檢查是否崩潰
- Process Monitor 監(jiān)控行為
3. 功能測(cè)試
- 主要功能是否正常
- 是否還有反調(diào)試觸發(fā)
- 是否有完整性檢查
4. 對(duì)比測(cè)試
- 與原始程序?qū)Ρ華PI調(diào)用序列
- 對(duì)比關(guān)鍵算法輸出
?? Part 3: 實(shí)戰(zhàn)案例分析
案例:破解一個(gè)簡(jiǎn)單的VMP2保護(hù)程序
目標(biāo)程序:CrackMe_VMP2.exe
保護(hù)方式:VMProtect 2.08
目標(biāo):繞過(guò)序列號(hào)驗(yàn)證
=======================================
步驟記錄:
=======================================
1. 查殼
? PEID: VMProtect 2.0x
? DIE: VMProtect 2.08
2. 初步運(yùn)行
? 彈出對(duì)話(huà)框:"Enter Serial"
? 輸入錯(cuò)誤顯示:"Wrong Serial!"
? 推測(cè):驗(yàn)證邏輯被VM保護(hù)
3. x64dbg加載 + ScyllaHide
? 所有反調(diào)試選項(xiàng)全開(kāi)
? F9運(yùn)行,程序正常彈窗
4. 定位驗(yàn)證函數(shù)
方法:API斷點(diǎn)
? bp GetDlgItemTextA // 獲取輸入的API
? F9運(yùn)行,在輸入框輸入"test",點(diǎn)確定
? 斷下!
5. 回溯查找驗(yàn)證邏輯
? 單步返回(Ctrl+F9)到調(diào)用者
? 發(fā)現(xiàn)代碼段:
CALL GetDlgItemTextA
LEA EAX, [EBP-0x100]
PUSH EAX
CALL 00401234 ← 可疑的驗(yàn)證函數(shù)
TEST EAX, EAX
JZ fail_label
? 00401234 就是驗(yàn)證函數(shù)入口
6. 分析驗(yàn)證函數(shù)
? 跟入 CALL 00401234
? 發(fā)現(xiàn)大量混淆代碼:
PUSH ECX
PUSH EDX
MOV EAX, [ESP+8]
JMP 00405678 ← 跳轉(zhuǎn)到VM Entry!
7. 識(shí)別VM Entry
地址:00405678
特征:
PUSHAD
PUSHFD
MOV EBP, ESP
SUB ESP, 0x200
MOV ESI, [大量初始化]
...
JMP [EAX*4+0x00406000] ← Dispatcher!
8. 簡(jiǎn)化破解法:爆破跳轉(zhuǎn)
? 不去虛擬化了,直接改跳轉(zhuǎn)
? 在第5步的代碼處:
TEST EAX, EAX
JZ fail_label ← 改成 JNZ
? 使用x64dbg修改:
選中 JZ 指令
空格 → 編輯
改成:JNZ fail_label
右鍵 → Patch → Patch File
保存為 CrackMe_VMP2_cracked.exe
9. 測(cè)試
? 運(yùn)行 CrackMe_VMP2_cracked.exe
? 輸入任意序列號(hào)
? 成功彈出:"Correct!"
=======================================
總結(jié):
對(duì)于簡(jiǎn)單目標(biāo),不需要完全去虛擬化
找到關(guān)鍵跳轉(zhuǎn)直接patch即可
但對(duì)于復(fù)雜的License系統(tǒng),可能需要完整分析
=======================================
?? Part 4: 進(jìn)階技術(shù)
4.1 自動(dòng)化去虛擬化工具
工具1: VMPAttack
- GitHub開(kāi)源項(xiàng)目
- 支持部分VMP版本
- 能自動(dòng)識(shí)別Handler
使用:
python vmpattack.py target.exe --trace --devirt
工具2: Kryptonite
- IDA Pro插件
- 符號(hào)執(zhí)行 + 污點(diǎn)分析
- 半自動(dòng)去虛擬化
工具3: 自己用Unicorn引擎寫(xiě)模擬器
from unicorn import *
from unicorn.x86_const import *
# 模擬VM執(zhí)行
uc = Uc(UC_ARCH_X86, UC_MODE_32)
uc.mem_map(0x00400000, 2 * 1024 * 1024)
uc.mem_write(0x00400000, code)
uc.emu_start(0x00401000, 0x00401234)
4.2 對(duì)抗完整性檢查
VMP的CRC校驗(yàn):
1. 在運(yùn)行時(shí)計(jì)算代碼段的校驗(yàn)和
2. 與內(nèi)置值比較
3. 不匹配則退出
對(duì)策:
方法1:Hook校驗(yàn)函數(shù)
bp [校驗(yàn)函數(shù)地址]
修改返回值為"正確"
方法2:修復(fù)校驗(yàn)值
計(jì)算patch后的新CRC
在內(nèi)存中搜索舊CRC值
替換為新值
方法3:NOP掉校驗(yàn)代碼
找到CRC計(jì)算和比較代碼
全部NOP
總結(jié)
VMProtect 2 的脫殼是逆向工程中的終極挑戰(zhàn)之一:
難度評(píng)級(jí): ????? (5/5)
時(shí)間成本:
- 簡(jiǎn)單patch:1-3小時(shí)
- 完整dump:1-2天
- 去虛擬化:1周-數(shù)月
成功率:
- 基本脫殼:90%
- IAT修復(fù):70%
- 完全去虛擬化:<20%
關(guān)鍵建議:
- 從簡(jiǎn)單目標(biāo)開(kāi)始練習(xí)
- 理論+實(shí)踐結(jié)合
- 善用自動(dòng)化工具
- 專(zhuān)注于關(guān)鍵邏輯,不要試圖還原全部
- 考慮替代方案(如內(nèi)存patch而不是文件patch)
最后強(qiáng)調(diào):將知識(shí)用于合法的安全研究和學(xué)習(xí),尊重軟件版權(quán)!
這是一個(gè)非常好的問(wèn)題,也是無(wú)數(shù)逆向工程愛(ài)好者和安全研究人員夢(mèng)寐以求的“圣杯”。
答案非常明確且殘酷:不存在,也幾乎不可能存在一個(gè)能夠自動(dòng)化、通用、完美修復(fù)VMProtect加密程序的“萬(wàn)能工具”。
下面我將詳細(xì)解釋為什么這在技術(shù)上是一個(gè)近乎無(wú)解的難題,以及為什么你看到的所謂“工具”都只是輔助性的。
第一部分:為什么“萬(wàn)能工具”是一個(gè)幻想?
要理解這一點(diǎn),我們必須區(qū)分**“加殼器 (Packer)”** 和 “虛擬化保護(hù)器 (Virtualizer)” 的根本區(qū)別。
1. 簡(jiǎn)單的加殼器 (如 UPX)
- 工作原理:像一個(gè)
zip壓縮程序。它有一套固定的算法來(lái)壓縮原始代碼,然后在程序入口點(diǎn)放一小段固定的解壓代碼。 - 脫殼原理:因?yàn)榻鈮核惴ㄊ枪潭ǖ模钥梢詫?xiě)一個(gè)固定的“解壓”工具(脫殼機(jī))。
UPX -d就是這樣一個(gè)萬(wàn)能脫殼工具,因?yàn)樗耆私?code class="rounded px-1.5 py-0.5 bg-surface-tertiary font-mono text-[0.9em] text-text-primary font-medium">UPX的壓縮算法。
2. VMProtect (虛擬化保護(hù)器)
- 工作原理:它不是一個(gè)
zip程序,而是一個(gè)**“語(yǔ)言發(fā)明工廠(chǎng)”**。- 當(dāng)你用VMProtect保護(hù)你的程序時(shí),VMP會(huì)為你程序的關(guān)鍵代碼發(fā)明一種全新的、獨(dú)一無(wú)二的、隨機(jī)的編程語(yǔ)言(字節(jié)碼)。
- 同時(shí),它還會(huì)為你**生成一個(gè)唯一的解釋器(虛擬機(jī)VM)**來(lái)執(zhí)行這種新語(yǔ)言。
- 核心特性:無(wú)限變種 (Infinite Variation)
- 不同的程序,不同的VM:用VMP保護(hù)
A.exe和B.exe,會(huì)得到兩個(gè)完全不同的虛擬機(jī)。A.exe里的VM不認(rèn)識(shí)B.exe的字節(jié)碼,反之亦然。 - 同一程序,每次編譯都不同:你用完全相同的設(shè)置,兩次保護(hù)同一個(gè)
A.exe,得到的A_v1.exe和A_v2.exe,它們內(nèi)部的虛擬機(jī)和字節(jié)碼也是不一樣的。
- 不同的程序,不同的VM:用VMP保護(hù)
把這個(gè)概念具象化:
想象一下,你想寫(xiě)一個(gè)“萬(wàn)能翻譯器”。
- 對(duì)于
UPX,這很簡(jiǎn)單,因?yàn)槟阒恍枰g一種公開(kāi)的語(yǔ)言(比如英語(yǔ)翻譯成中文),算法是固定的。- 對(duì)于
VMProtect,這相當(dāng)于要求你的“萬(wàn)能翻譯器”能夠自動(dòng)翻譯地球上從未出現(xiàn)過(guò)的、由外星人剛剛隨機(jī)發(fā)明的、且每一個(gè)外星人說(shuō)的都不一樣的神秘語(yǔ)言。你會(huì)發(fā)現(xiàn),你根本無(wú)法制造這樣一個(gè)“萬(wàn)能翻譯器”。你只能逐個(gè)去接觸每一個(gè)外星人,花費(fèi)大量時(shí)間學(xué)習(xí)并破解他那套獨(dú)有的語(yǔ)言體系。
第二部分:三大技術(shù)壁壘,讓“萬(wàn)能工具”無(wú)法實(shí)現(xiàn)
1. 虛擬機(jī)架構(gòu)的無(wú)限隨機(jī)性
一個(gè)自動(dòng)化工具需要依賴(lài)固定的模式(Pattern)來(lái)識(shí)別和處理代碼。但VMP的設(shè)計(jì)哲學(xué)就是消滅一切固定模式。
每次保護(hù),以下所有元素都是隨機(jī)生成的:
- 虛擬指令集 (Bytecode):
0x01在A程序里可能是“加法”,在B程序里可能是“跳轉(zhuǎn)”。 - 虛擬機(jī)處理器 (VM Handlers):執(zhí)行“加法”的真實(shí)x86代碼,在A和B程序里完全不同,充滿(mǎn)了各種垃圾指令和混淆。
- 虛擬寄存器:虛擬機(jī)的寄存器數(shù)量、存儲(chǔ)位置(在棧上還是在特定內(nèi)存區(qū)域)都是隨機(jī)的。
- 調(diào)度器 (Dispatcher):分發(fā)指令的核心邏輯也是千變?nèi)f化。
一個(gè)工具無(wú)法通過(guò)靜態(tài)分析找到任何可依賴(lài)的“簽名”或“特征碼”來(lái)自動(dòng)化還原這個(gè)過(guò)程。
2. 代碼變異與深度混淆 (Mutation & Obfuscation)
即使是虛擬機(jī)本身的代碼(那些VM Handlers),也是經(jīng)過(guò)VMP深度混淆的。一個(gè)簡(jiǎn)單的 ADD EAX, EBX 會(huì)被變成幾十條甚至上百條等價(jià)但難以理解的指令。自動(dòng)化工具無(wú)法分辨哪些是有效代碼,哪些是垃圾代碼,更無(wú)法將其還原成最簡(jiǎn)形式。
3. 無(wú)處不在的反分析陷阱 (Anti-Analysis Traps)
VMP會(huì)在代碼中埋下無(wú)數(shù)“地雷”,專(zhuān)門(mén)用來(lái)對(duì)抗自動(dòng)化分析工具。
- 完整性檢查 (CRC Check):自動(dòng)化工具在分析和修改代碼時(shí),會(huì)改變文件的校驗(yàn)和。VMP的自校驗(yàn)代碼一旦發(fā)現(xiàn)文件被修改,就會(huì)導(dǎo)致程序崩潰或行為異常。一個(gè)“萬(wàn)能工具”除非能智能地定位并修復(fù)所有校驗(yàn)點(diǎn),否則生成的程序就是個(gè)廢品。
- 反調(diào)試和時(shí)序攻擊:自動(dòng)化工具的某些分析技術(shù)(如符號(hào)執(zhí)行)可能會(huì)被VMP的時(shí)鐘檢測(cè)、API行為檢測(cè)等機(jī)制發(fā)現(xiàn),從而觸發(fā)對(duì)抗。
第三部分:那市面上的“VMP工具”是做什么的?
你可能聽(tīng)過(guò)或用過(guò)一些名字里帶“VMP”的工具,比如VMPDump、VMP Unpacker by VT,或者一些開(kāi)源腳本。它們都不是“萬(wàn)能工具”,而是輔助分析的腳本或小程序,作用非常有限。
-
自動(dòng)化Dump工具 (如VMPDump)
- 能做什么:嘗試自動(dòng)完成“尋找OEP”和“內(nèi)存轉(zhuǎn)儲(chǔ)”這兩個(gè)最最基礎(chǔ)的步驟。
- 不能做什么:完全不能去虛擬化。它Dump出來(lái)的文件,關(guān)鍵代碼依然是VMP的字節(jié)碼。
- 成功率:對(duì)非常古老、保護(hù)選項(xiàng)弱的VMP版本可能有效。對(duì)現(xiàn)代VMP版本基本100%失敗。
-
分析輔助腳本 (如IDAPython腳本)
- 能做什么:幫助人類(lèi)分析師提高效率。例如,一個(gè)腳本可以自動(dòng)地:
- 追蹤VM Dispatcher,并列出所有VM Handler的地址。
- 為每個(gè)Handler打上標(biāo)簽,方便你逐個(gè)分析。
- 嘗試對(duì)字節(jié)碼進(jìn)行初步的染色或分組。
- 不能做什么:它不能告訴你某個(gè)Handler是“加法”還是“減法”。這個(gè)最關(guān)鍵的、需要智力判斷的工作,仍然需要你手動(dòng)完成。
- 能做什么:幫助人類(lèi)分析師提高效率。例如,一個(gè)腳本可以自動(dòng)地:
-
學(xué)術(shù)研究型工具 (基于符號(hào)執(zhí)行、污點(diǎn)分析)
- 能做什么:這是最前沿的嘗試。通過(guò)模擬執(zhí)行每一條指令,并追蹤數(shù)據(jù)的流動(dòng)(污點(diǎn)分析),理論上有可能推斷出部分代碼的原始邏輯。
- 不能做什么:
- 極度緩慢:分析一小段VMP代碼可能需要幾個(gè)小時(shí)甚至幾天。
- 路徑爆炸:遇到復(fù)雜的分支,需要探索的路徑呈指數(shù)級(jí)增長(zhǎng),很快就會(huì)耗盡所有計(jì)算資源。
- 使用復(fù)雜:需要博士級(jí)別的專(zhuān)業(yè)知識(shí)來(lái)配置和使用,絕非“一鍵運(yùn)行”。
結(jié)論
放棄尋找“自動(dòng)化脫殼VMP的萬(wàn)能工具”的想法。
對(duì)抗VMProtect的唯一途徑,是將其視為一場(chǎng)人與機(jī)器的智力對(duì)抗。真正的“工具”是:
- 你的大腦:你的逆向工程知識(shí)、邏輯推理能力和分析經(jīng)驗(yàn)。
- 強(qiáng)大的調(diào)試器和反匯編器:如
x64dbg和IDA Pro,它們是你的眼睛和手術(shù)刀。 - 無(wú)盡的耐心:去虛擬化是一個(gè)極其枯燥和漫長(zhǎng)的過(guò)程,以周或月為單位計(jì)算。
如果你真的想攻克它,正確的道路是學(xué)習(xí)扎實(shí)的逆向工程基礎(chǔ),而不是在網(wǎng)上尋找那個(gè)不存在的“魔法按鈕”。
加密程序的執(zhí)行流程
解密流程
1.脫殼流程
進(jìn)入程序的真實(shí)入口
dump內(nèi)存文件
修復(fù)pe數(shù)據(jù)、修復(fù)iat
重定位及其他檢查

這里殼的作用就是解壓和解密代碼段和數(shù)據(jù)段和資源段。入口點(diǎn)變?yōu)榱颂摷俚臍と肟冢皇谴a段的mian函數(shù)。
什么是修復(fù)
目標(biāo)是使ida可以正常工作,可以靜態(tài)分析。
保證pe結(jié)構(gòu)關(guān)鍵數(shù)據(jù)的完整及正確。
保證對(duì)數(shù)據(jù)的引用正確,全局變量涉及重定位。
保證跳轉(zhuǎn)指令的跳轉(zhuǎn)地址正確,cal、jmp指令涉及重定位。
保證api的調(diào)用正確,涉及iat的修復(fù)


到OEP如何
特征法:根據(jù)原程序入口特征查找些編譯器編譯程序的入口特征

同時(shí)我們知道OEP的位置在dump中的ida程序中是沒(méi)有建立函數(shù)識(shí)別的,因?yàn)闆](méi)有任何一段函數(shù)會(huì)引用這段函數(shù),基本可以定位到這個(gè)OEP位置
殼分析法:根據(jù)殼代碼查找這里需要我們熟悉殼代碼的流程,知道殼代碼將控制權(quán)轉(zhuǎn)交給OEP的位置。
API方法;


我識(shí)別是什么程序我們可以dump內(nèi)存獲取特征
這里我用
https://www.unpac.me/results/556bdbc0-32f7-467f-ad28-42d5cf9112be
上傳樣本自動(dòng)分析流程發(fā)現(xiàn)特折


vmp檢測(cè)軟件和硬件斷點(diǎn)就無(wú)法設(shè)置oep處的斷點(diǎn)


1.執(zhí)行后第一次會(huì)自動(dòng)斷點(diǎn)到系統(tǒng)庫(kù)nt停止,點(diǎn)擊執(zhí)行后第二次第三次,執(zhí)行是gdi庫(kù),第四此就進(jìn)入到了軟件的vmp段,也就是正式進(jìn)入了殼中。

2.ctrl+g 輸入 vs2017的入口函數(shù)特征 GetSystemTimeAsFileTime,點(diǎn)擊定位后雙擊進(jìn)入這個(gè)api,

雙擊進(jìn)入這個(gè)api,在第二個(gè)匯編雙擊下斷點(diǎn)。因?yàn)関mp會(huì)檢測(cè)第一句代碼。 然后運(yùn)行
3.繼續(xù)運(yùn)行,我們?cè)诙褩7祷睾瘮?shù)地址,檢查段區(qū)域,發(fā)現(xiàn)在vmp中,說(shuō)明vmp中也用了這個(gè)api,我們跳過(guò)這個(gè)
4.繼續(xù)運(yùn)行,我們?cè)诙褩7祷睾瘮?shù)地址,檢查段區(qū)域,發(fā)現(xiàn)在text段中,我們右鍵在反匯編展示這個(gè)段匯編定位到返回地址,也就是說(shuō)調(diào)用這個(gè)api的入口函數(shù)就在這里附近。

我們?cè)谡鎸?shí)入口OEP下斷即可,并記錄oep地址,取消api斷點(diǎn),重啟這個(gè)程序并重新執(zhí)行到這個(gè)斷點(diǎn)處,也就是text的用f4執(zhí)行到GetSystemTimeAsFileTime的下一條指令f4。我們也就完成了vmp段的執(zhí)行將text段的代碼解壓也就完成了。這里為什么我們要下斷后重啟程序,因?yàn)槲覀兿聰嗟膐ep位置已經(jīng)執(zhí)行完畢了代碼,數(shù)據(jù)已經(jīng)發(fā)生了變化,為了保持原始數(shù)據(jù),我們必須在oep處下斷重啟執(zhí)行。

我們查看上一個(gè)棧的返回值,我們右鍵同步反匯編試圖發(fā)現(xiàn)這里就是真正的oep位置
因此我們清楚了調(diào)用流程是
vmp到 txt的oep 再到 txt的api,我們現(xiàn)在在api下。
為了驗(yàn)證猜想,我們下斷回退代碼。
我們?cè)趖xt的api下一個(gè)返回地址下端,執(zhí)行,單步執(zhí)行,發(fā)現(xiàn)返回到了oep,同樣的操作就返回的了殼子的調(diào)用oep的位置。這里就不演示了。
dump文件整數(shù)據(jù)保存在文件中。相當(dāng)于模塊內(nèi)存的將加載到內(nèi)存中的模塊的完一個(gè)拷貝。
查看dump文件需要16進(jìn)制編輯器,如winhex
因?yàn)榧恿藲さ某绦颍a和數(shù)據(jù)是以壓縮或者加密的形式保存在文件中。只有程序運(yùn)行到OEP時(shí),內(nèi)存中才會(huì)有完整的原代碼和原數(shù)據(jù)。所以我們需要在OEP處dump模塊。
思考一個(gè)問(wèn)題:是不是OEP以后的位置都可以dump?
答案:不是的,需要在處理數(shù)據(jù)之前,我們還要保證數(shù)據(jù)段是初始的狀態(tài)。
當(dāng)EIP在EP時(shí)原代碼段是沒(méi)有數(shù)據(jù)的,所以這里dump它,是沒(méi)有意義的。
當(dāng)EIP在OEP時(shí),其實(shí)OEP就在text段說(shuō)明代碼段已經(jīng)有了數(shù)據(jù)。
我們已經(jīng)找到了OEP的位置我們直接到這里,通過(guò)比較框架代碼,OEP的指令是"call,jmp我們通過(guò)棧回溯到OEPMVIO OA/S
第二部修復(fù)ida

因?yàn)関mp保護(hù)的程序,它的導(dǎo)入表是殼的導(dǎo)入表,不是原程序的導(dǎo)入表。我們dump下來(lái)后,代碼和數(shù)據(jù)有了,但是代碼中的API調(diào)用是錯(cuò)誤的。脫殼后,殼的導(dǎo)入表就失去作用,可以去掉它,


修復(fù)vmcall,就是修復(fù)代碼中的call調(diào)用
xx vm iat x32插件發(fā),主要用于修復(fù)vmp保護(hù)的API調(diào)用。
我們?cè)趏d里操作因?yàn)椴寮趚64dbg有問(wèn)題,打開(kāi)軟件運(yùn)行目標(biāo)程序。
ctrl+g 輸入api名稱(chēng)GetSystemTimeAsFileTime,下一個(gè)匯編處下斷點(diǎn)運(yùn)行
發(fā)現(xiàn)在vmp段里,再次運(yùn)行發(fā)現(xiàn)在text段內(nèi)
在棧返回地址右鍵同步匯編代碼,在匯編試圖右鍵取消分析顯示匯編代碼,我們?cè)?eb處下斷點(diǎn)
開(kāi)始修復(fù)call



在按鈕區(qū)域點(diǎn)擊I,查看日志,顯示成功

我們點(diǎn)擊窗口標(biāo)題后完成所有修復(fù)

dump內(nèi)存
https://down.52pojie.cn/Tools/PEtools/?amp%3BO=A
這有兩種方法,一個(gè)插件,一個(gè)可執(zhí)行,
我們用右鍵管理員執(zhí)行可執(zhí)行的32或64位exe,具體位數(shù)取決于你的目標(biāo)程序位數(shù),打開(kāi)選擇這個(gè)程序,并填入oep虛擬地址地址,點(diǎn)擊dump

修復(fù)pe

這里的模塊基地址和內(nèi)存的一致。因?yàn)橹囟ㄎ灰潭K地址,不在修復(fù)模塊偏移。

將40改為00,去除dll屬性重定位。

我們看到DlCharacteristics是0x8140,有模塊重定位標(biāo)志0x40,我們?nèi)サ羲某稍斍槲覀冊(cè)诘?節(jié)0x8100,講解。
修復(fù)IAT
uifUniversal lmport Fixer,在內(nèi)存中重建導(dǎo)入地址表if,

修復(fù)完成后不要關(guān)閉
修復(fù)IT
lmports Fixer,重建dump文件的導(dǎo)入表


注意上面是oep是偏移
點(diǎn)擊修復(fù)dump,選這上次的dump出的文件,修復(fù)即可生成if.dump文件





我們上一節(jié)中,修復(fù)完iat,程序就已經(jīng)可以運(yùn)行了,看起來(lái)不用修復(fù)重定位了,其實(shí)我們是使用了比較簡(jiǎn)便的辦法。我們回想一下ageBase是0x00400000,并且我我們修復(fù)PE數(shù)據(jù)時(shí),看到Im的模塊重定位標(biāo)志,dump文件時(shí)們?nèi)サ袅薉lCharacteristics中內(nèi)存中模塊地址是0x00400000,而dump文件的尋址指令中的立000對(duì)應(yīng)的,所以我們只要讓dump即數(shù)都是和模塊地址0x00400文件加載到0x00400000這個(gè)也址,所有的尋址指令就都對(duì)上了,就可以正常運(yùn)行了。
指令中API的調(diào)用我們也是因?yàn)楣潭四K地址。
殼代碼隱藏了原程序的重定位數(shù)據(jù),我們看不到重定位表
想要找到原程序的重定位表會(huì)非常困難,學(xué)會(huì)了虛擬代碼還原之后我們就可以做到。
對(duì)于exe,我們可以使用固定模塊地址的方法,d則不可以。
重定表的結(jié)構(gòu)是一樣的。前面我vmp修改了的重定位的數(shù)據(jù)定義,們看到正常的重定位數(shù)據(jù)是16位,高4位為標(biāo)志,低12位為頁(yè)偏移vmp自己的重定位數(shù)據(jù),高12位為頁(yè)偏移,低4位為標(biāo)志
資源檢查
例子中沒(méi)有使用vmp資源加密。關(guān)于資源的方面的知識(shí)點(diǎn),可以查看相關(guān)文檔資料進(jìn)行學(xué)習(xí)。













破解VMProtect v.1.6x – 1.7殼
1.附加進(jìn)程
2.輸入要跟隨的表達(dá)式:VirtualProtect
3.找到Call VirtualProtectEx下端點(diǎn)
4.運(yùn)行,至堆棧窗口顯示ReadOnly。(即代碼釋放完畢)
5.打開(kāi)內(nèi)存窗口,在第一個(gè)代碼段設(shè)置內(nèi)存訪(fǎng)問(wèn)斷點(diǎn)
6.運(yùn)行后停下來(lái),右鍵斷點(diǎn)–刪除內(nèi)存短點(diǎn)
7.找到寄存器的ESP,數(shù)據(jù)窗口中跟隨
8.從最低找,找到第一個(gè)“返回Kernel32…”,在其上一行設(shè)置硬件寫(xiě)入斷點(diǎn)(Byte)
9.運(yùn)行,打開(kāi)內(nèi)存窗口,第一個(gè)代碼段設(shè)置內(nèi)存訪(fǎng)問(wèn)斷點(diǎn)。
10.繼續(xù)運(yùn)行,到達(dá)OEP。
11.可以使用od自帶插件,也可用LordPE脫殼,再用ImportREC修復(fù)轉(zhuǎn)存。
posted on 2025-10-25 18:41 GKLBB 閱讀(98) 評(píng)論(0) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)