簡介
不存在保護軟體免受未經授權使用和分發的理想方法。現有的任何系統都無法提供絕對的安全性,也無法阻止潛在的駭客對其進行破解。然而,使用高質量和高效的保護可以使破解軟體變得極其困難,使投入的時間和精力完全不值得。雖然軟體保護可以追求不同的目標,但任何保護系統的基礎都是保護應用程式免受分析,因為抵抗逆向工程的能力決定了保護系統的整體效率。
術語表
如果不瞭解相應領域的專業術語,就無法有效地使用工具。以下術語表解釋了 VMProtect 中使用的術語。
- 位元組碼 — 將真實處理器的指令轉碼為虛擬機器指令後得到的程式碼。
- 虛擬化 — 將應用程式可執行程式碼的一部分轉換為虛擬機器指令的過程,該虛擬機器具有對潛在駭客而言未知的指令系統、架構和執行邏輯。虛擬化的程式碼片段由虛擬機器直譯器執行,無需將其轉換為物理處理器的機器語言程式碼。通常,對虛擬化片段的逆向工程歸結為構建一個與虛擬機器模擬的處理器具有相同架構的反彙編器,並分析所得的反彙編程式碼。
- 虛擬機器 — 在受保護的應用程式中直接執行位元組碼的程式程式碼。
- 浮水印 — 每個使用者唯一的位元組陣列,可以明確識別被破解程式副本的合法所有者。
- 變異 — 用等效指令或產生相同結果的指令集替換原始指令。
- 混淆 — 旨在使程式程式碼分析變得複雜的一組方法和技術。根據受保護程式所使用的程式語言,會使用不同的混淆型別。解釋型語言(Perl、PHP 等)應用程式的混淆透過修改原始碼來實現:刪除註釋、給變數無意義的名稱、加密字串常量等。Java / .NET 應用程式的混淆透過轉換虛擬機器處理的位元組碼來實現。編譯程式的混淆依賴於修改機器語言程式碼:混淆器新增各種"垃圾"指令、"死程式碼"、隨機跳轉,同時變異原始指令、將部分操作轉移到堆疊,並進行若干結構性轉換。
- 保護器 — 旨在保護其他程式免受破解的軟體。當今大多數保護器不會修改應用程式的原始碼,而是對應用進行加殼或加密。
- 入口點 — 載入到記憶體中的應用程式開始執行的初始地址。
- 加殼 — 一種透過使用非典型演算法壓縮程式的可執行檔案和/或庫來保護程式程式碼的方式。
- 加密 — 使用強加密演算法保護應用程式程式碼的一部分。受加密保護的軟體要求終端使用者輸入啟用碼,以解除開發者為程式未註冊版本設定的限制。
軟體分析、破解與保護
軟體產品可以透過靜態分析或動態分析來進行分析。靜態分析意味著保護破解演算法基於對受保護應用程式的反彙編結果分析或反編譯。動態分析用於破解加密或動態變化的可執行檔案,因為對這些程式進行靜態分析已證明是困難的。
在動態分析中,被破解的程式在偵錯程式框架中執行。這樣,程式執行期間發生的一切都可以被偵錯程式控制。在動態分析期間,破解者使用除錯模式逐一繞過程式的所有保護演算法,特別是註冊金鑰的生成和檢查過程。動態分析經常使用的另一個工具是跟蹤被破解程式查詢的檔案、系統服務、埠和外部裝置。
保護應用程式免受破解嘗試的主要工具是軟體保護器。大多數保護器提供的保護基於對原始可執行檔案的加殼和/或加密,並高度重視保護解殼/解密過程。
這種演算法通常不足以提供可靠的保護。如果應用程式受加殼保護,駭客可以在解殼器完成工作後立即對記憶體進行轉儲,輕鬆獲取原始的未加殼檔案。此外,有多種自動化工具可以破解最流行的保護器。加密也是如此:在獲得適當的授權金鑰後,破解者可以解密受保護的程式碼部分。
一些軟體保護器使用多種反除錯技術。然而,每種技術都會顯著影響受保護程式的效能。此外,反除錯方法僅對動態分析有效,對靜態分析完全無效。更甚的是,現代保護器使用的所有反除錯方法都是眾所周知的,破解者已經編寫了許多實用程式來避免或繞過它們。
更有效的保護應用程式方式是混淆和虛擬化,它們使受保護應用程式的程式碼分析變得複雜。通常,這些保護方法的高效性基於人為因素:程式碼越複雜、應用程式使用的資源越多,破解者就越難理解程式邏輯,從而越難破解保護。
混淆透過新增多餘的指令來"混淆"應用程式的程式碼。虛擬化將原始碼轉換為由模擬具有特定指令集的虛擬機器的特殊直譯器執行的位元組碼。因此,虛擬化導致結果程式碼具有高且不可逆的複雜性,如果正確應用,用這種方法保護的程式碼不包含顯式恢復原始程式碼的方法。虛擬化的主要優勢在於虛擬化的程式碼片段在執行期間不會轉換為機器語言指令,這防止了破解者獲取應用程式的原始程式碼。
什麼是 VMProtect?
VMProtect 是新一代的軟體保護工具。VMProtect 支援使用 C/C++、C#/VB .NET、Rust、Golang 編譯的 x86/x86_64/ARM64 二進位制檔案和 .NET 程式集,適用於所有最流行的作業系統:Windows、Linux、macOS 和 Android。VMProtect 支援全系列可執行檔案,即 Windows 版本可以處理 Linux/macOS 的二進位制檔案,反之亦然。
VMProtect 的基石原則是透過使應用程式程式碼和邏輯對於進一步分析和破解變得非常複雜,從而提供對應用程式程式碼免受審查的高效保護。VMProtect 應用的主要軟體程式碼保護機制包括:虛擬化、變異以及結合變異和後續虛擬化的組合保護。
VMProtect 中使用的虛擬化方法的關鍵優勢在於,執行虛擬化程式碼片段的虛擬機器被嵌入到受保護應用程式的結果程式碼中。因此,受 VMProtect 保護的應用程式無需第三方庫或模組即可執行。VMProtect 允許使用多個不同的虛擬機器來保護同一應用程式的不同程式碼片段,從而使破解過程更加複雜。
VMProtect 中應用的程式碼變異方法基於混淆——一個嚮應用程式程式碼中新增各種多餘的"垃圾"指令、"死"程式碼部分、隨機條件跳轉的過程。它還變異原始指令並將某些操作的執行轉移到堆疊。
VMProtect 與其他軟體保護器的關鍵區別在於它能夠使用不同的方法保護程式碼的不同部分:部分程式碼可以虛擬化,另一部分被混淆,關鍵片段使用組合方法保護。
VMProtect 的另一個獨特功能是將浮水印嵌入到應用程式的程式碼中。浮水印可以明確識別被破解程式副本的官方所有者。
版本對比
VMProtect 提供 3 個版本:Lite、Professional、Ultimate。
| 功能 | Lite | Professional | Ultimate |
|---|---|---|---|
| 混淆方法 | |||
| 變異 | ✓ | ✓ | ✓ |
| 虛擬化 | ✓ | ✓ | ✓ |
| Ultra(變異+虛擬化) | ✓ | ✓ | ✓ |
| 保護選項 | |||
| 記憶體保護 | ✓ | ✓ | ✓ |
| 匯入保護 | ✓ | ✓ | ✓ |
| 資源保護 | ✓ | ✓ | ✓ |
| 加殼 | ✓ | ✓ | ✓ |
| 偵錯程式檢測 | ✓ | ✓ | ✓ |
| 虛擬化工具檢測 | ✓ | ✓ | ✓ |
| 附加功能 | |||
| 控制檯版本 | ✗ | ✓ | ✓ |
| 浮水印 | ✗ | ✓ | ✓ |
| 指令碼語言 | ✗ | ✓ | ✓ |
| 授權系統 | ✗ | ✗ | ✓ |
| 啟用系統 | ✗ | ✗ | ✓ |
| 虛擬檔案 | ✗ | ✗ | ✓ |
保護應用程式的建議
VMProtect 是保護應用程式程式碼免受分析和破解的可靠工具,但只有在正確構建應用內保護機制、避免可能破壞整個保護的典型錯誤時,才能實現最高效的使用。讓我們回顧開發良好程式保護的關鍵要素。
註冊過程
許多開發者在設計註冊過程時犯的一個典型錯誤是將整個註冊金鑰檢查封裝到一個單獨的函式中,該函式返回一個易於理解的值:
function CheckRegistration(const RegNumber: String): Boolean;
begin
if RegNumber='123' then
Result:=True
else
Result:=False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
...
if not CheckRegistration(RegNumber) then
exit;
Application.CreateForm(TForm2, Form2);
Form2.ShowModal;
...
end;
採用這種方法,入侵者甚至不需要理解金鑰檢查演算法。他只需修改檢查過程開頭的程式碼,使其始終返回正確的值:
function CheckRegistration(const RegNumber: String): Boolean;
begin
Result:=True;
exit;
...
end;
一種更有效的方法是將正確性檢查嵌入到程式的主要操作邏輯中,使註冊金鑰檢查的演算法無法與呼叫過程的演算法分離。我們建議將操作邏輯與註冊金鑰檢查過程"融合",使程式在檢查被繞過時無法正常工作:
function CheckRegistration(const RegNumber: String): Boolean;
begin
if RegNumber='123' then
begin
Application.CreateForm(TForm2, Form2);
Result:=True
end
else
Result:=False;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
...
Form2:=nil;
if not CheckRegistration(RegNumber) then
exit;
Form2.ShowModal;
...
end;
如果以這種方式實現 CheckRegistration 函式,入侵者必須詳細分析註冊金鑰檢查的程式碼才能繞過它。如果此應用程式受 VMProtect 保護,建議對 CheckRegistration 函式和 TForm1.Button1Click 過程都進行虛擬化。為了使破解更加複雜,您可以開啟 "Ultra" 保護模式來結合程式碼變異和後續虛擬化。
檢查註冊金鑰
開發者犯的另一個關鍵錯誤是註冊金鑰檢查的錯誤實現。通常,輸入的金鑰只是與正確的值進行簡單比較。破解者可以透過跟蹤字串比較函式的引數輕鬆匹配正確的金鑰值:
var ValidRegNumber: String;
...
function CheckRegistration(const RegNumber: String): Boolean;
begin
if RegNumber=ValidRegNumber then
Result:=True
else
Result:=False;
end;
為了避免這種情況,我們建議比較金鑰的雜湊值,而不是實際值。雜湊函式是不可逆的,因此破解者無法從雜湊中檢索真實的金鑰值。
儲存檢查結果
通常,即使在註冊過程上花了大量時間的開發者,也沒有對保護註冊過程結果給予應有的關注。下面的例子使用全域性變數儲存註冊狀態。對於入侵者來說,找到全域性變數輕而易舉——他只需比較註冊前後的資料段即可。
var IsRegistered: Boolean;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
...
if not IsRegistered then
IsRegistered:=CheckRegistration(RegNumber);
if not IsRegistered then
exit;
...
end;
為了避免這種情況,建議將與程式註冊相關的所有檢查結果儲存在動態記憶體中:
type PBoolean = ^Boolean;
var IsRegistered: PBoolean;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
...
if not IsRegistered^ then
IsRegistered^:=CheckRegistration(RegNumber);
if not IsRegistered^ then
exit;
...
end;
...
initialization
New(IsRegistered);
使用 VMProtect
在開始使用 VMProtect 之前,請檢視以下章節:
準備專案
讓我們來看一個非常簡單的應用程式,它僅由一個窗體(Form1)、一個文字元素(Edit1)和一個按鈕(Button1)組成。當點選 Button1 時,應用檢查輸入的密碼是否正確並顯示相應的訊息。
密碼使用非常簡單的演算法進行檢查:第一步將其轉換為數字形式,然後計算除以 17 的餘數。如果餘數等於 13,則密碼正確:
function TForm1.CheckPassword: Boolean;
begin
Result:=(StrToIntDef(Edit1.Text, 0) mod 17=13);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if CheckPassword then
MessageDlg('Correct password', mtInformation, [mbOK], 0)
else
begin
MessageDlg('Incorrect password', mtError, [mbOK], 0);
Edit1.SetFocus;
end;
end;
可以透過三種方式選擇要保護的過程和函式:
- 使用 PDB/MAP 檔案 — 由編譯器在編譯時建立。MAP 檔案包含應用程式所有過程和函式的名稱和地址等必要資訊。使用 MAP 檔案後,每次重新編譯專案時,VMProtect 會自動確定過程和函式的新地址。
- 使用標記 — 插入到應用程式原始碼中的特殊標記,VMProtect 用於確定受保護片段邊界。當您只想保護函式或過程的一部分時,使用標記就很有意義。
- 透過可執行檔案中受保護過程的地址 — 每次修改和重新編譯應用程式時,都必須重新指定所有地址。建議用於沒有原始碼的應用程式。
重要提示:如果使用 PDB/MAP 檔案選擇要虛擬化的程式碼片段,序言和尾聲也將被虛擬化,從而顯著增強保護能力。此外,如果一個虛擬化函式從另一個虛擬化函式中呼叫,控制權在它們之間轉移時不會實際跳轉到被呼叫函式的地址。這也加強了程式的保護。
使用 PDB/MAP 檔案
要建立 MAP 檔案,您需要在編譯器設定中啟用相應選項。
Visual Studio
在 IDE 主選單中,開啟專案屬性(Project – Properties),在 "Linker – Debugging" 選項卡中將 "Generate MAP File" 設定為 "Yes (/MAP)":
Borland Delphi
在 Delphi IDE 主選單中開啟專案選項(Project – Options),在 "Linker" 選項卡中將 "MAP file" 部分設定為 "Detailed":
啟用 MAP 檔案生成後,必須重新構建專案。載入 MAP 檔案時,VMProtect 會比較 MAP 檔案和受保護檔案的修改日期和時間。如果不同,則不會載入 MAP 檔案。
使用標記
要保護程式碼的各個片段以及保護字串常量,您可以在原始碼中插入特殊標記。標記是從 SDK 庫匯入的函式呼叫:
.NET
- VMProtect.SDK.dll
Windows
- 32 位使用者模式應用程式 - VMProtectSDK32.dll
- 32 位核心驅動程式 - VMProtectDDK32.sys
- 64 位使用者模式應用程式 - VMProtectSDK64.dll
- 64 位核心驅動程式 - VMProtectDDK64.sys
Linux
- 32 位應用程式 - libVMProtectSDK32.so
- 64 位應用程式 - libVMProtectSDK64.so
macOS
- libVMProtectSDK.dylib
SDK 中的過程和函式不執行任何操作,僅作為 VMProtect 用來確定受保護程式碼邊界的標籤。受保護塊的開頭和結尾標記如下:
C/C++
#include "VMProtectSDK.h"
VMProtectBegin(MARKER_TITLE);
...
VMProtectEnd();
C#
using System.Reflection;
class Foo
{
[Obfuscation(Feature = "virtualization", Exclude = false)]
public Foo()
{
...
Pascal
uses VMProtectSDK;
VMProtectBegin(MARKER_TITLE);
...
VMProtectEnd;
此外可以使用具有預定義編譯型別的標記代替 VMProtectBegin:
- VMProtectBeginVirtualization — 使用 "虛擬化" 編譯型別。
- VMProtectBeginMutation — 使用 "變異" 編譯型別。
- VMProtectBeginUltra — 使用 "Ultra" 編譯型別。
- VMProtectBeginVirtualizationLockByKey — 使用 "虛擬化" 編譯型別並啟用 "鎖定到序號"。
- VMProtectBeginUltraLockByKey — 使用 "Ultra" 編譯型別並啟用 "鎖定到序號"。
當 VMProtect 分析受保護應用程式的程式碼時,它會定位所有對 VMProtectSDK 過程和函式的呼叫。受保護塊的邊界由標記對 VMProtectBegin 和 VMProtectEnd 定義。然後,VMProtect 處理受保護應用程式程式碼時會刪除標記和所有對 VMProtectSDK 的引用,因此無需將這些庫包含在安裝檔中。
重要提示:使用標記時,不應允許從非保護區域跳轉到標記內部。如果使用標記的應用程式在保護後變得無法正常工作,您可以透過啟用"除錯模式"選項來檢測來自非保護區域的跳轉和地址。
.NET ObfuscationAttribute
VMProtect 支援以下 ObfuscationAttribute.Feature 值:
- renaming — 允許將類/方法排除在重新命名之外。Exclude 值為 true/false。
- virtualization, virtualizationlockbykey, ultra, ultralockbykey, mutation — 允許為方法/類指定編譯型別。ApplyToMembers 值為 true/false。
- strings — 允許對方法/類進行字串混淆。ApplyToMembers 值為 true/false。
SDK 函式
SDK 函式可以整合到受保護應用程式的原始碼中,用於設定受保護區域的邊界、檢測偵錯程式或虛擬化工具。
程式碼標記
- VMProtectBegin — 受保護程式碼區域開頭的標記。
- VMProtectBeginVirtualization — 預定義 "虛擬化" 編譯型別的開頭標記。
- VMProtectBeginMutation — 預定義 "變異" 編譯型別的開頭標記。
- VMProtectBeginUltra — 預定義 "Ultra" 編譯型別的開頭標記。
- VMProtectBeginVirtualizationLockByKey — 預定義 "虛擬化" 並啟用 "鎖定到序號" 的開頭標記。
- VMProtectBeginUltraLockByKey — 預定義 "Ultra" 並啟用 "鎖定到序號" 的開頭標記。
- VMProtectEnd — 受保護程式碼區域結尾的標記。
服務函式
- VMProtectIsProtected — 如果檔案已被 VMProtect 處理,則返回 True。
- VMProtectIsDebuggerPresent — 檢測應用程式是否在偵錯程式中執行。
- VMProtectIsVirtualMachinePresent — 檢測應用程式是否在虛擬機器環境中執行。
- VMProtectIsValidImageCRC — 檢測可執行模組在程式記憶體中是否被修改。
- VMProtectDecryptStringA — 解密 ANSI 字串常量。
- VMProtectDecryptStringW — 解密 Unicode 字串常量。
- VMProtectFreeString — 釋放為解密字串分配的動態記憶體。
授權函式
- VMProtectSetSerialNumber — 將序號載入到授權系統。
- VMProtectGetSerialNumberState — 返回序號的狀態標誌。
- VMProtectGetSerialNumberData — 獲取序號的詳細資訊。
- VMProtectGetCurrentHWID — 獲取當前計算機的硬體識別符號。
啟用函式
- VMProtectActivateLicense — 將啟用碼傳送到伺服器並返回序號。
- VMProtectDeactivateLicense — 將序號傳送到伺服器進行停用。
- VMProtectGetOfflineActivationString — 獲取離線啟用字串。
- VMProtectGetOfflineDeactivationString — 獲取離線停用字串。
重要提示:保護後,應用程式將不再需要 SDK 庫。
GUI 版本
主視窗由以下元素組成:
主選單
檔案選單
- 開啟 — 選擇要保護的可執行檔案或專案檔案 (*.vmp)。
- 儲存專案 — 將應用程式保護設定儲存到 *.vmp 檔案。
- 另存為… — 將專案檔案儲存為新名稱。
- 關閉 — 完成當前專案的工作。
- 退出 — 關閉 VMProtect。
專案選單
- 新增函式 — 向受保護物件列表新增新函式。
- 新增資料夾 — 向專案新增資料夾。
- 新增授權 — 向專案新增授權。
- 匯出金鑰對 — 將專案金鑰匯出為指定格式的金鑰生成器。
- 匯入 — 從序號或其他專案匯入授權。
- 編譯 — 使用當前保護設定編譯受保護的應用程式。
- 執行 — 執行受保護的應用程式。
工具選單
- 浮水印 — 開啟浮水印對話視窗。
- 首選項 — 開啟程式的首選項視窗。
幫助選單
- 主頁 — 開啟 VMProtect 主頁。
- 內容 — 開啟 VMProtect 幫助檔案。
- 關於… — 檢視 VMProtect 的一般資訊。
工具欄
工具欄由以下元素組成:開啟專案或受保護檔案、儲存專案、受保護檔案的名稱、編譯專案、執行原始/受保護檔案、預設操作、快速搜尋框。
專案區域
"專案" 區域包含以下子區域:
- 保護的函式 — 允許選擇要保護的物件
- 授權 — 允許管理授權和序號
- 檔案 — 允許將額外資料檔案或 DLL 包含到受保護的 EXE 檔案中
- 指令碼 — 允許透過內建 LUA 指令碼語言增強保護能力
- 選項 — 允許配置應用程式保護引數
保護的函式
此區域用於選擇必須保護的函式。
編譯型別:為每個受保護物件設定編譯方式,以實現效能和程式碼安全性之間的最佳平衡:
- 變異 — 在 CPU 指令級別修改可執行程式碼。此編譯型別在保護程式碼方面相對較弱,其主要目的是防止自動簽名分析器檢測已處理的函式。變異提供低階別保護,但程式碼執行效能高。
- 虛擬化 — 將可執行程式碼翻譯為在虛擬機器上執行的位元組碼。此編譯型別應用於所有需要嚴格防破解和分析對策的關鍵程式碼部分。虛擬化提供中等保護級別和中等執行速度。
- Ultra(變異 + 虛擬化) — 在處理器指令級別變異可執行程式碼,然後翻譯為虛擬機器位元組碼。當執行速度不是關鍵因素時,應將此編譯型別應用於所有程式碼片段。Ultra 提供高保護級別,執行速度較慢。
鎖定到序號 — 如果啟用此選項,受保護的函式在未輸入有效序號的情況下將不可用,並將終止應用程式。這樣您可以限制未註冊版本中某些功能的造訪。
管理授權
預設情況下,授權功能處於關閉狀態。要啟用它們,您需要在 "專案" 區域的 "授權" 子區域中建立一對金鑰。初始化完成後,"鎖定到序號" 選項將可用,您將能夠建立和處理序號。
"授權" 區域在左側面板顯示授權的完整列表,在主面板顯示所選元素的引數。右側面板顯示所選授權的詳細資訊,還允許封鎖序號、將其複製到剪貼簿或檢視硬體 ID 資訊。
檔案區域
"檔案" 區域允許開發者將受保護 EXE 檔案執行所需的額外資料(如影象、資料檔案、文字資源和動態連結庫)包含到其中。在執行受保護的 EXE 檔案期間,包括 DLL 在內的所有型別的資料都直接從程式記憶體中載入,而不會將這些資料寫入磁碟。
指令碼區域
"專案" 區域的 "指令碼" 子區域用於使用內建指令碼語言編寫指令碼。您可以在此區域的主面板上編輯指令碼程式碼。
選項區域
"選項" 區域允許您配置各種保護引數:
虛擬機器選項
- 版本 — 指定虛擬機器版本(預設為當前版本)。選擇 "Compact" 將使用舊版虛擬機器,具有更小的位元組碼大小。
- 例項數 — 指定虛擬機器副本的數量(預設為 10)。每個虛擬機器將具有唯一的屬性集,使虛擬化程式碼的分析和破解更加困難。
- 複雜度 — 指定在虛擬機器內部建立複雜處理程式的機率,進一步複雜化虛擬化程式碼的分析。
檔案選項
- 記憶體保護 — 保護檔案在記憶體中的映像免受任何更改。
- 匯入保護 — 隱藏受保護程式使用的 API 列表。
- 資源保護 — 加密程式的資源(圖示、清單和其他服務資源除外)。
- 打包輸出檔案 — 打包受保護檔案以減小其大小。
檢測選項
- 偵錯程式 — 阻止除錯受保護檔案。
- 虛擬化工具 — 禁止在各種虛擬環境中執行受保護檔案。
函式區域
"函式" 區域列出所有可供保護的函式。選擇函式後,您可以在主面板上檢視其屬性和保護選項。對於每個函式,您可以指定編譯型別並啟用鎖定到序號。
詳細資訊區域
"詳細資訊" 區域顯示有關受保護應用程式的各種資訊。它還允許您從打包中排除某些資料段或資源。包含以下子區域:
- 目錄 — 顯示可執行檔案的目錄列表。
- 段 — 顯示可執行檔案的段列表,允許從打包中排除某些段。
- 匯入 — 顯示匯入庫和函式列表。
- 匯出 — 顯示匯出函式列表。
- 資源 — 顯示應用程式資源列表,允許從打包中排除某些資源。
- 計算器 — 用於十六進位制/十進位制地址轉換。
- 轉儲 — 顯示可執行檔案的記憶體區域。
控制檯版本
在 GUI 模式下建立專案後,您可以使用控制檯版本(VMProtect_Con.exe)。執行方式如下:
VMProtect_Con File [Output File] [-pf Project File] [-sf Script File] [-lf Licensing Parameters File] [-bd Build Date (yyyy-mm-dd)] [-wm Watermark Name] [-we]
- File — 要保護的可執行檔名(*.exe、*.dll 等)或專案檔名(*.vmp)。
- Output File — 處理後生成的受保護檔案的檔名和路徑。
- Project File — 在 GUI 模式下建立的專案檔案的檔名和路徑。
- Script File — 處理受保護檔案所使用的指令碼檔名。
- Licensing Parameters File — 包含授權引數的檔名。
- Build Date — 應用程式構建日期,格式為 "yyyy-mm-dd"。
- Watermark Name — 插入到受保護檔案中的浮水印名稱。
- -we — 設定此引數後,所有警告將顯示為錯誤。
重要提示:控制檯版本不適用於 Lite 版本。
授權系統
授權系統建立並驗證序號的有效性。它支援按日期和時間限制受保護軟體、鎖定到特定硬體、使用序號加密程式碼、限制免費更新期限等功能。該系統基於非對稱加密演算法,以最大限度地減少構建未經授權的序號生成器("序號產生器")的可能性。
功能特性
- 安全的序號 — 授權系統使用非對稱加密演算法。如果沒有私鑰,就不可能建立有效的序號。駭客必須破解非對稱加密演算法或完全禁用授權系統。VMProtect 授權系統的關鍵部分被虛擬化,使得後者極其困難。
- 程式碼鎖定 — 一個或多個函式可以"鎖定"到序號。被鎖定的函式首先被虛擬化,然後加密。只有當使用者提供正確的序號時,授權系統才會解密被鎖定的函式。無論駭客如何修補序號檢查程式碼,如果沒有正確的序號,被鎖定的函式仍然無法工作。
- 免費升級期限 — 您可以為不同的使用者設定不同的免費升級期限。序號中包含一個日期,如果受保護應用程式的編譯日期晚於此日期,授權系統將返回"序號過期"錯誤程式碼,否則一切正常。這允許為不同型別的授權提供不同的免費升級期限。
- 過期日期 — 序號可以有過期日期。如果日期已過,授權系統將返回"序號過期"錯誤程式碼。可用於建立產品的限時演示版。
- 硬體鎖定 — 序號可以繫結到特定計算機。授權系統會分析硬體識別符號並將其與序號中記錄的識別符號進行比較。如果不匹配,則返回"硬體識別符號不匹配"錯誤程式碼。
- 黑名單 — 如果序號被洩露或盜版,您可以將其新增到黑名單中。在下次更新應用程式時,黑名單中的序號將不再被接受。
- 資料儲存 — 序號可以包含最多 255 位元組的任意資料,授權系統將按原樣傳遞給程式。
- 限時演示 — 序號可以包含程式的最大執行時間限制(以分鐘為單位)。
工作原理
保護和授權的流程如下:
- 開發者在 VMProtect 中建立金鑰對(公鑰和私鑰)。公鑰將嵌入到受保護的應用程式中,私鑰用於生成序號。
- 開發者設定保護選項,選擇要保護/虛擬化的函式,可選地將某些函式鎖定到序號。
- VMProtect 編譯並保護應用程式。
- 使用者購買軟體後,開發者(或自動系統)使用私鑰生成序號。
- 使用者將序號輸入到應用程式中,授權系統驗證序號並解鎖相應功能。
整合到應用程式
將授權系統整合到應用程式中分為兩個階段。在第一階段,您可以使用測試模式來熟悉 API 並測試各種功能,而無需使用 VMProtect 處理應用程式。在第二階段,您將建立受 VMProtect 保護的真實應用程式並測試其在實際環境中的執行情況。
步驟 1.1:建立測試應用程式
首先建立一個簡單的控制檯應用程式,包含 VMProtectSDK 標頭檔案。將程式與 VMProtectSDK 庫連結。在測試模式下,授權系統使用 INI 檔案來模擬序號的行為。
建立一個名為 "test_licensing.ini" 的檔案,內容如下:
[TestLicense]
AcceptedSerialNumber=Xserialnumber
AcceptedSerialNumber 引數指定授權系統接受的序號。引數的值可以是任意的,但必須與程式中傳遞的值匹配。
步驟 1.2:檢查序號
使用 VMProtectSetSerialNumber() 函式將序號傳遞給授權系統。該函式返回一個位掩碼,如果返回 0 則表示序號有效:
int main(int argc, char **argv)
{
char *serial = "Xserialnumber";
int res = VMProtectSetSerialNumber(serial);
print_state(res);
return 0;
}
步驟 1.3:獲取序號資訊
使用 VMProtectGetSerialNumberData() 函式獲取序號中的詳細資訊,包括使用者名稱、信箱、過期日期等:
VMProtectSerialNumberData sd = {0};
VMProtectGetSerialNumberData(&sd, sizeof(sd));
printf("User name: %ls\n", sd.wUserName);
printf("E-Mail: %ls\n", sd.wEMail);
步驟 1.4:序號過期日期
在 INI 檔案中新增過期日期:
ExpDate=20051001
格式為 YYYYMMDD。如果當前日期晚於過期日期,VMProtectSetSerialNumber() 將返回 SERIAL_STATE_FLAG_DATE_EXPIRED 標誌。
步驟 1.5:執行時間限制
在 INI 檔案中新增 RunningTimeLimit 引數(以分鐘為單位)。如果程式執行時間超過限制,VMProtectGetSerialNumberState() 將返回 SERIAL_STATE_FLAG_RUNNING_TIME_OVER 標誌。
步驟 1.6:免費升級日期
在 INI 檔案中新增 MaxBuildDate 引數。如果受保護程式的編譯日期晚於此日期,將返回 SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED 標誌。
步驟 1.7:使用者名稱和信箱
在 INI 檔案中新增 UserName 和 EMail 引數,使用 VMProtectGetSerialNumberData() 來讀取這些資料。
步驟 1.8:黑名單
在 INI 檔案中新增 BlackListedSerialNumber 引數。如果當前序號在黑名單中,將返回 SERIAL_STATE_FLAG_BLACKLISTED 標誌。
步驟 1.9:硬體鎖定
使用 VMProtectGetCurrentHWID() 獲取硬體識別符號:
int nSize = VMProtectGetCurrentHWID(NULL, 0);
char *buf = new char[nSize];
VMProtectGetCurrentHWID(buf, nSize);
printf("HWID: %s\n", buf);
delete [] buf;
在 INI 檔案中設定 MyHWID(模擬當前硬體)和 KeyHWID(序號中的硬體識別符號)。如果兩者不匹配,將返回 SERIAL_STATE_FLAG_BAD_HWID 標誌。
重要提示:程式只有在被 VMProtect 處理後才會顯示真實的硬體識別符號。
步驟 1.10:使用者資料
序號可以包含最多 255 位元組的任意資料。在 INI 檔案中使用 UserData 引數以 HEX 格式指定資料:
UserData=010203A0B0C0D0E0
使用 VMProtectGetSerialNumberData() 讀取 nUserDataLength 和 bUserData 欄位獲取這些資料。
步驟 2.1:建立受保護的應用程式
在第一階段我們使用測試模式編寫了幾個簡單應用來測試授權 API。現在在第二階段我們將建立一個實際的應用程式,編譯時不包含除錯資訊但啟用 MAP 檔案生成。
步驟 2.2:建立 VMProtect 保護專案
在 VMProtect Ultimate 中開啟可執行檔案。將需要保護的函式新增到專案中。然後在"授權"區域初始化授權系統,建立 2048 位的金鑰對。
步驟 2.3:首次啟動受保護產品
授權系統初始化完成後,編譯 VMProtect 專案並執行受保護檔案。此時程式不再使用 VMProtectSDK.dll,因為授權模組已內建到程式中。在"授權"區域生成序號,儲存到檔案中,程式即可正常工作。
步驟 2.4:測試結果
測試各種場景:建立帶過期日期的序號、將序號新增到黑名單等。驗證授權系統對不同情況的響應是否正確。
步驟 2.5:將程式碼鎖定到序號
破解程式最常見的方式之一是定位序號檢查處及其後的條件跳轉。VMProtect 允許將一個或多個函式的程式碼鎖定到序號,使其在沒有正確序號的情況下無法執行:
- 在"函式"區域選擇函式,將"鎖定到序號"選項設為"是"
- 函式體被虛擬化後加密,只有正確的序號才能解密
- 即使駭客修復了條件跳轉,被鎖定的函式仍然無法工作
應該鎖定什麼?建議鎖定僅在註冊版本中執行的函式。由於鎖定需要虛擬化,應考慮效能損失。例如,如果文字編輯器的演示版不允許儲存,您可以將儲存文件函式鎖定到序號。
鎖定與無效序號:當呼叫 VMProtectSetSerialNumber() 時,授權模組檢查序號。加密的程式碼片段僅在序號完全正確時才會執行。某些限制可能在程式執行期間"觸發"(例如執行時間到期),但授權模組仍會繼續執行已鎖定的函式,因為突然停止可能導致應用程式故障。
授權系統 API
授權系統 API 是 VMProtect API 和 SDK 的組成部分。API 允許您指定序號並檢索有關它的所有資訊。
VMProtectSetSerialNumber
將序號載入到授權系統中。呼叫語法:
int VMProtectSetSerialNumber(const char *SerialNumber);
輸入引數 SerialNumber 必須是指向以 null 結尾的字串的指標,包含 base-64 編碼的序號。函式返回序號狀態標誌的位掩碼。如果返回 0,表示序號"正確"。
VMProtectGetSerialNumberState
返回由 VMProtectSetSerialNumber() 指定的序號的狀態標誌:
int VMProtectGetSerialNumberState();
如果至少設定了一個標誌,則序號存在問題。詳細的標誌說明如下:
| 標誌 | 值 | 說明 |
|---|---|---|
| SERIAL_STATE_FLAG_CORRUPTED | 0x01 | 授權系統已損壞。可能原因:保護專案設定不正確、破解嘗試。 |
| SERIAL_STATE_FLAG_INVALID | 0x02 | 序號不正確。授權系統無法解密序號。 |
| SERIAL_STATE_FLAG_BLACKLISTED | 0x04 | 序號與產品匹配,但在 VMProtect 中被列入黑名單。 |
| SERIAL_STATE_FLAG_DATE_EXPIRED | 0x08 | 序號已過期。 |
| SERIAL_STATE_FLAG_RUNNING_TIME_OVER | 0x10 | 程式執行時間已耗盡。 |
| SERIAL_STATE_FLAG_BAD_HWID | 0x20 | 硬體識別符號與序號中的不匹配。 |
| SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED | 0x40 | 序號與受保護程式的當前版本不匹配。 |
VMProtectGetSerialNumberData
獲取序號的詳細資訊:
bool VMProtectGetSerialNumberData(VMProtectSerialNumberData *Data, int Size);
VMProtectSerialNumberData 結構體包含以下欄位:
| 欄位 | 型別 | 說明 |
|---|---|---|
| nState | int | 金鑰狀態的位標誌掩碼。 |
| wUserName | wchar_t[256] | 客戶名稱(UNICODE,以 null 結尾)。 |
| wEMail | wchar_t[256] | 客戶信箱(UNICODE,以 null 結尾)。 |
| dtExpire | VMProtectDate | 金鑰過期日期。 |
| dtMaxBuild | VMProtectDate | 金鑰可用的最大產品構建日期。 |
| bRunningTime | int | 程式執行的最大分鐘數。 |
| nUserDataLength | unsigned char | 使用者資料的長度。 |
| bUserData | unsigned char[255] | 放入金鑰的使用者資料。 |
VMProtectDate
日期結構體:
| 欄位 | 型別 | 說明 |
|---|---|---|
| wYear | unsigned short | 年份。 |
| bMonth | unsigned char | 月份,從 1 開始。 |
| bDay | unsigned char | 日期,從 1 開始。 |
VMProtectGetCurrentHWID
獲取程式執行所在 PC 的硬體識別符號:
int VMProtectGetCurrentHWID(char *HWID, int Size);
如果第一個引數為 NULL,函式返回儲存硬體識別符號所需的位元組數。正確用法:
int nSize = VMProtectGetCurrentHWID(NULL, 0);
char *pBuf = new char[nSize];
VMProtectGetCurrentHWID(pBuf, nSize);
// 使用識別符號
delete [] pBuf;
序號生成器
用途
除了 VMProtect 外,其他軟體也可以生成序號。這對於自動化傳送序號是必要的。客戶購買產品後,電子商務代理向供應商的網站傳送 HTTP 請求,伺服器上執行的生成器根據客戶資料生成序號。序號傳送給客戶和供應商。供應商隨後使用匯入授權對話方塊在 VMProtect 中手動新增序號。
工作原理
VMProtect 的授權系統基於非對稱演算法,因此生成序號需要產品的私鑰。您可以在專案屬性視窗中匯出此金鑰,並以任何合適的方式將其傳遞給生成器。
現有的生成器
授權系統附帶三個可用的序號生成器:DLL 版本(Windows)、.NET 版本和 PHP 版本(UNIX)。
安全建議
- 使用 HTTPS — 如果電子商務提供商可以傳送 HTTPS 請求,所有資料以加密形式傳輸,生成的序號無法被截獲。
- "隱藏"您的生成器 — 確保沒有人可以偶然開啟生成器。不要放置外部連結,不要在網站目錄或 robot.txt 中列出。
- 驗證呼叫者 — 檢查呼叫者的 IP 地址是否在電子商務提供商的 IP 範圍內。
- 檢查輸入引數 — 確保所有必需引數都已傳遞且格式正確。不要對錯誤請求產生任何響應。
- 新增"密碼" — 在查詢中指定一個附加引數作為密碼,名稱和值應不明顯。
Windows 版本
Windows 金鑰生成器是用於 x86 和 x64 平台的 DLL 檔案,附帶 C 語言標頭檔案和 MSVC 相容的 lib 檔案。所有檔案位於 %Examples%\Keygen\DLL 資料夾中。
生成器 API
生成器僅匯出兩個函式:第一個生成序號,第二個釋放第一個函式分配的記憶體:
VMProtectErrors __stdcall VMProtectGenerateSerialNumber(
VMProtectProductInfo *pProductInfo,
VMProtectSerialNumberInfo *pSerialInfo,
char **pSerialNumber
);
void __stdcall VMProtectFreeSerialNumberMemory(char *pSerialNumber);
VMProtectSerialNumberInfo 結構體:
struct VMProtectSerialNumberInfo
{
INT flags;
wchar_t * pUserName;
wchar_t * pEMail;
DWORD dwExpDate;
DWORD dwMaxBuildDate;
BYTE nRunningTimeLimit;
char * pHardwareID;
size_t nUserDataLength;
BYTE * pUserData;
};
flags 欄位包含以下位標誌:
- HAS_USER_NAME — 將使用者名稱放入序號。
- HAS_EMAIL — 將信箱放入序號。
- HAS_EXP_DATE — 序號將在指定日期後過期。
- HAS_MAX_BUILD_DATE — 序號僅適用於指定日期前的版本。
- HAS_TIME_LIMIT — 程式執行時間限制(以分鐘為單位,最大 255)。
- HAS_HARDWARE_ID — 程式僅在指定硬體上執行。
- HAS_USER_DATA — 將自定義使用者資料放入序號。
VMProtectGenerateSerialNumber 函式返回值:
- ALL_RIGHT — 無錯誤,序號已生成。
- UNSUPPORTED_ALGORITHM — 傳遞了不正確的加密演算法。
- USER_NAME_IS_TOO_LONG — UTF-8 編碼的使用者名稱超過 255 位元組。
- EMAIL_IS_TOO_LONG — UTF-8 編碼的信箱超過 255 位元組。
- SERIAL_NUMBER_TOO_LONG — 序號太長,超出演算法的位數限制。
- BAD_PRODUCT_INFO — 第一個引數不正確或為 NULL。
- BAD_SERIAL_NUMBER_INFO — 第二個引數不正確或為 NULL。
dwExpDate 和 dwMaxBuildDate 格式
日期欄位使用特定格式:0xYYYYMMDD。使用宏 MAKEDATE(y, m, d),例如:MAKEDATE(2010, 05, 12)。
.NET 版本
.NET 版本的金鑰生成器是一個包含生成序號所需一切內容的程式集。原始碼位於 %Examples%\Keygen\Net,包含 KeyGen(生成器本身)和 Usage(使用示例)兩個專案。
使用步驟:
- 將 Usage 專案的程式碼作為基礎,新增對 VMProtect.KeyGen.dll 的引用。
- 在 VMProtect 中開啟"專案 | 匯出金鑰對"對話方塊,選擇".NET KeyGen 引數"選項。
- 將匯出的文字複製到應用程式中作為字串常量。
示例程式碼:
try
{
string data = @""; // 在此放入匯出的資料
Generator g = new Generator(data);
g.UserName = "John Doe";
g.EMail = "john@doe.com";
g.ExpirationDate = DateTime.Now.AddMonths(1);
g.MaxBuildDate = DateTime.Now.AddYears(1);
g.RunningTimeLimit = 15;
g.HardwareID = "AQIDBAgHBgU=";
g.UserData = new byte[] { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
string serial = g.Generate();
Console.WriteLine("Serial number:\n{0}\n", serial);
}
catch (Exception ex)
{
Console.WriteLine("Error: {0}", ex);
}
UNIX 版本
UNIX 版本的金鑰生成器是一個 PHP 檔案,包含序號生成所需的所有資訊。檔案位於 %Examples%\Keygen\PHP。
配置生成器
在 PHP 檔案開頭有設定程式碼,由 VMProtect 自動生成,對每個產品唯一:
$exported_algorithm = "RSA";
$exported_bits = 2048;
$exported_private = "PJvj4kEpoQMIpYK+9wEt......xKeiSZgzdiln8Q==";
$exported_modulus = "rOlny/3QgZb/VmGr3CmY......I6ESAUmtQ+RBqQ==";
$exported_product_code = "oLQdGUn8kVk=";
金鑰內容
$params = array(
user_name => "John Doe",
email => "john@doe.com",
hwid => "vHGMdMRvGCPjWcCQ",
expire_date => array(year => 2009, month => 10, day => 1),
maxbuild_date => array(year => 2009, month => 10, day => 1),
time_limit => 10,
user_data => base64_decode("CGCvRvMWcPHGdMjQ"),
);
注意事項
- 使用者名稱和信箱必須以 UTF-8 字串傳遞。
- 非對稱加密是複雜的數學過程。生成器使用
gmp_powm、bi_powmod、bcpowmod函式。如果生成時間過長,建議啟用這些函式。
序號格式
序號結構
序號由多個塊組成。每個塊以標識位元組開始,指示塊的內容和可能的長度。最後一個塊的識別符號始終為 255,包含序號的校驗和。
塊格式
| ID | 名稱 | 大小(位元組) | 說明 |
|---|---|---|---|
| 0x01 | 版本 | 1 | 序號版本,必須為 1。 |
| 0x02 | 使用者名稱 | 1 + N | UTF-8 編碼的使用者名稱,前有 1 位元組長度。最大 255 位元組。 |
| 0x03 | 信箱 | 1 + N | UTF-8 編碼的信箱,前有 1 位元組長度。最大 255 位元組。 |
| 0x04 | 硬體識別符號 | 1 + N | 由 VMProtectGetCurrentHWID() 返回的 base-64 字串的解碼版本。長度必須是 4 的倍數,最大 32 位元組。 |
| 0x05 | 授權過期日期 | 4 | 序號過期日期。 |
| 0x06 | 最大執行時間 | 1 | 程式可執行的時間(分鐘),最大 255 分鐘。 |
| 0x07 | 產品程式碼 | 8 | 由 VMProtect 建立的產品程式碼,此塊是必需的。 |
| 0x08 | 使用者資料 | 1 + N | 最多 255 位元組的自定義使用者資料。 |
| 0x09 | 最大構建日期 | 4 | 應用程式的最大構建日期。 |
| 0xFF | 校驗和 | 4 | 序號校驗和,使用 SHA-1 雜湊演算法計算。 |
日期儲存格式
日期以雙字儲存 — 0xYYYYMMDD。高位字包含年份,低位字包含日和月。位元組遵循小端序表示。
校驗和計算
校驗和使用 SHA-1 雜湊演算法計算。結果是五個 32 位字。第一個字用作序號的校驗和。注意:該字為小端序。如果 SHA-1 雜湊由字串函式生成(如在 PHP 中),則雜湊的前四個位元組必須反轉。
序號加密演算法
授權系統的安全性基於非對稱密碼學演算法。當前版本實現了金鑰長度從 1024 到 16384 位的 RSA 演算法。未來版本計劃實現基於 ECC 的其他演算法。
每個產品使用的演算法是唯一的。用一種演算法制作的金鑰不能與另一種演算法一起使用,這意味著在建立至少一個授權後不允許更改演算法。
RSA 演算法
序號使用 RSA 演算法加密的步驟:
- 在序號開頭新增隨機資料 — 基於 RFC2313 的方法。在金鑰開頭新增:
00 02 NN…NN 00,其中 NN…NN 是 8 到 16 個隨機非零位元組。 - 在序號末尾新增隨機資料 — 序號的總位元組數必須等於演算法金鑰位數除以 8。格式為:
00 02 NN..NN 00 DD..DD MM..MM。 - 加密 — 使用處理大數的標準過程。
- 打包 — 加密後的位元組集被編碼為 base-64,即為傳送給客戶的序號。
啟用系統
什麼是啟用?
啟用是註冊應用程式的兩階段過程。在第一階段(通常在購買後立即),使用者收到一個啟用碼,通常看起來像 XXXX-YYYY-ZZZZ。在第二階段,客戶將程式碼輸入到應用程式中,應用程式透過網際網路連線到開發者的伺服器,伺服器檢查啟用碼並返回繫結到該啟用碼的序號,通常鎖定到使用者的硬體。
為什麼需要啟用?
- 短序號 — 啟用碼可以解決長序號的所有問題,同時充分利用其優勢。
- 安裝控制 — 所有啟用對開發者實時線上可見。每次啟用都可以被監控、分析、封鎖。
- 試用期 — 您可以透過使用產品"模式"來建立限時序號。
- 訂閱 — 類似於試用期,但需付費。您可以向使用者出售在指定時間內使用程式的權利。
需要什麼?
VMProtect Ultimate 提供了多個函式用於相對輕鬆地實現線上和離線啟用。您還需要在伺服器上安裝 Web License Manager,並在 VMProtect 中配置保護專案。
在 VMProtect 中配置啟用
要使啟用 API 正常工作,需要 WebLM URL。在 VMProtect 中開啟選項區域:
在"啟用伺服器"欄位中輸入地址,格式為:http://yourserver/weblm_path。這是遇到線上啟用問題時首先要檢查的地方。
Web License Manager 中的啟用
進入 Web License Manager 並建立產品。然後將產品匯出為 VMProtect 專案以配置授權和啟用。設定完成後,在 WebLM 左側面板中點選"新增新程式碼"連結:
從上方下拉選單中選擇所需產品,填寫其餘表單資料,然後點選"儲存"按鈕。您將看到可用於除錯啟用 API 的啟用碼。
啟用 API
啟用 API 僅包含 4 個函式。兩個用於線上啟用,另外兩個用於計算機無法造訪網際網路時的離線啟用。
VMProtectActivateLicense
將啟用碼傳遞到伺服器並返回此特定計算機的序號:
int VMProtectActivateLicense(const char *code, char *serial, int size);
code 引數是從 Web License Manager 獲取的啟用碼。serial 引數指定存放 WebLM 生成的序號的記憶體塊。
VMProtectDeactivateLicense
將序號傳遞到伺服器進行停用:
int VMProtectDeactivateLicense(const char *serial);
VMProtectGetOfflineActivationString / VMProtectGetOfflineDeactivationString
這兩個函式的工作方式與前兩個類似,但不嘗試連線 WebLM 伺服器。它們返回一個文字塊,使用者需要將其複製到聯網計算機上,開啟 WebLM 離線啟用表單並貼上:
int VMProtectGetOfflineActivationString(const char *code, char *buf, int size);
int VMProtectGetOfflineDeactivationString(const char *serial, char *buf, int size);
buf 引數應指向 1000 位元組或更大的緩衝區。
可能的錯誤碼
| 程式碼 | 值 | 說明 |
|---|---|---|
| ACTIVATION_OK | 0 | 啟用成功。序號已放入 serial 變數。 |
| ACTIVATION_SMALL_BUFFER | 1 | 緩衝區太小。 |
| ACTIVATION_NO_CONNECTION | 2 | 無法連線到 Web License Manager。 |
| ACTIVATION_BAD_REPLY | 3 | 啟用伺服器返回了意外結果。 |
| ACTIVATION_BANNED | 4 | 此啟用碼已被軟體供應商在伺服器上封禁。 |
| ACTIVATION_CORRUPTED | 5 | 啟用模組自檢系統出錯,通常表示破解嘗試。 |
| ACTIVATION_BAD_CODE | 6 | 指定的程式碼在啟用伺服器資料庫中未找到。 |
| ACTIVATION_ALREADY_USED | 7 | 此程式碼的啟用計數器已耗盡。 |
| ACTIVATION_SERIAL_UNKNOWN | 8 | 給定的序號在伺服器資料庫中未找到。 |
| ACTIVATION_EXPIRED | 9 | 程式碼的啟用期已過期。 |
| ACTIVATION_NOT_AVAILABLE | 10 | 啟用/停用不可用。 |
提示和技巧
不要忘記為有網際網路問題的使用者提供離線啟用方式。啟用 API 不會儲存它收到的序號,也不會將其傳遞給授權模組 — 這應該由開發者完成。您不必在每次啟動應用程式時呼叫啟用 API,只需呼叫一次,從 WebLM 獲取序號,儲存到適當位置,然後使用儲存的副本。
使用指令碼
VMProtect 內建了強大的指令碼語言 LUA,極大地增強了 VMProtect 在每個保護階段的預設保護能力。
LUA 語法與 JavaScript 非常相似,但與 JavaScript 不同,LUA 不包含顯式類。儘管如此,指令碼語言允許輕鬆實現物件導向程式設計機制,如類、繼承和事件。指令碼使用示例可在 "VMProtect/Examples/Scripts" 資料夾中找到。
重要提示:指令碼編輯器不適用於 Lite 版本。
指令碼類參考
VMProtect 內建的 LUA 指令碼語言是物件導向的。指令碼語言包括提供基本功能的標準類和提供應用程式保護功能造訪的專用類。
類層次結構
Core — 用於操作 VMProtect 核心的類:
- Watermarks / Watermark — 浮水印管理
- Licenses / License — 授權管理
- Files / File — 檔案管理
- Folders / Folder — 資料夾管理
PEFile — 用於操作 PE 檔案的類:
- PEArchitecture — PE 架構
- PESegments / PESegment — 段
- PESections / PESection — 節
- PEDirectories / PEDirectory — 目錄
- PEImports / PEImport — 匯入
- PEExports / PEExport — 匯出
- PEResources / PEResource — 資源
- PERelocs / PEReloc — 重定位
MacFile — 用於操作 Mach-O 檔案的類:
- MacArchitecture — Mach-O 架構
- MacSegments / MacSegment — 段
- MacSections / MacSection — 節
- MacCommands / MacCommand — 載入命令
- MacSymbols / MacSymbol — 符號
- MacImports / MacImport — 匯入
- MacExports / MacExport — 匯出
- MacRelocs / MacReloc — 重定位
其他類:
- MapFunctions / MapFunction — 可用於保護的函式列表
- References / Reference — 引用列表
- IntelFunctions / IntelFunction — Intel 函式及命令
- CommandLinks / CommandLink — 命令連結
- FFILibrary / FFIFunction — 外部函式介面
VMProtect 全域性函式
namespace vmprotect {
Core core();
string extractFilePath(string name);
string extractFileName(string name);
string extractFileExt(string name);
string expandEnvironmentVariables(string value);
void setEnvironmentVariable(string name, string value);
string commandLine();
FFILibrary openLib(string name);
}
Core 類
專案選項列舉:
enum ProjectOption {
None, Pack, ImportProtection, MemoryProtection,
ResourceProtection, CheckDebugger, CheckKernelDebugger,
CheckVirtualMachine, StripFixups, StripDebugInfo, DebugMode
}
Core 類方法:
class Core {
public:
string projectFileName(); // 返回專案檔名
void saveProject(); // 儲存專案
string inputFileName(); // 返回原始檔名
string outputFileName(); // 返回輸出檔名
void setOutputFileName(string); // 設定輸出檔名
string watermarkName(); // 返回浮水印名稱
void setWatermarkName(string); // 設定浮水印名稱
int options(); // 返回專案選項
void setOptions(int); // 設定專案選項
string vmSectionName(); // 返回 VM 段名稱
void setVMSectionName(); // 設定 VM 段名稱
Licenses licenses(); // 返回授權列表
Files files(); // 返回檔案列表
Watermarks watermarks(); // 返回浮水印列表
PEFile/MacFile inputFile(); // 返回原始檔
PEFile/MacFile outputFile(); // 返回輸出檔案
};
Licenses 類
class Licenses {
public:
int keyLength(); // 返回金鑰長度
string publicExp(); // 返回公鑰指數
string privateExp(); // 返回私鑰指數
string modulus(); // 返回模數
License item(int index); // 返回指定索引的授權
int count(); // 返回授權數量
};
class License {
public:
string date(string format = "%c"); // 返回授權日期
string customerName(); // 返回客戶名稱
string customerEmail(); // 返回客戶信箱
string orderRef(); // 返回訂單 ID
string comments(); // 返回註釋
string serialNumber(); // 返回序號
bool blocked(); // 返回是否被封鎖
void setBlocked(bool); // 設定封鎖狀態
};
PE 檔案類
class PEFile {
public:
string name(); // 返回檔名
string format(); // 返回格式名稱 "PE"
uint64 size(); // 返回檔案大小
int count(); // 返回架構數量
PEArchitecture item(int index); // 返回指定索引的架構
uint64 seek(uint64 offset); // 設定檔案位置
uint64 tell(); // 返回檔案位置
int write(string buffer);// 寫入緩衝區
};
class PEArchitecture {
public:
string name(); // 返回架構名稱
PEFile file(); // 返回父檔案
uint64 entryPoint(); // 返回入口點
uint64 imageBase(); // 返回基址
OperandSize cpuAddressSize(); // 返回架構位數
uint64 size(); // 返回架構大小
PESegments segments(); // 返回段列表
PESections sections(); // 返回節列表
PEDirectories directories(); // 返回目錄列表
PEImports imports(); // 返回匯入庫列表
PEExports exports(); // 返回匯出函式列表
PEResources resources(); // 返回資源列表
PERelocs relocs(); // 返回重定位列表
MapFunctions mapFunctions(); // 返回可保護的函式列表
IntelFunctions functions(); // 返回受保護的函式列表
bool addressSeek(uint64 address); // 按地址定位
uint64 seek(uint64 offset); // 設定檔案位置
int write(string buffer); // 寫入緩衝區
};
Intel 函式類
編譯型別:
enum CompilationType {
Virtualization, Mutation, Ultra
};
class IntelFunctions {
public:
IntelFunction item(int index);
int count();
void clear();
IntelFunction itemByAddress(uint64 address);
IntelFunction itemByName(string name);
IntelFunction addByAddress(uint64 address, CompilationType type = ctVirtualization);
};
class IntelFunction {
public:
uint64 address();
string name();
ObjectType type();
IntelCommand item(int index);
int count();
CompilationType compilationType();
void setCompilationType(CompilationType value);
CommandLinks links();
IntelCommand itemByAddress(uint64 address);
void destroy();
Folder folder();
void setFolder(Folder folder);
};
FFI 庫類
class FFILibrary {
public:
string name();
uint64 address();
void close();
FFIFunction getFunction(string name, ParamType ret, ParamType param1, ...);
};
class FFIFunction {
string name();
uint64 address();
};
內建函式
除了指令碼語言的類方法和屬性外,VMProtect 還向使用者提供各種基本操作函式。包括處理字串、日期和數字的通用系統函式,以及處理 VMProtect 核心和浮水印的專用函式:
指令碼事件
內建指令碼語言是自動化建立受保護應用程式的有效方式。在構建受保護檔案的各個階段所需的過程和函式透過 VMProtect 核心處理的特定事件進行呼叫。您可以為以下 5 個事件設定自定義處理程式:
OnBeforeCompilation
function OnBeforeCompilation()
end
在建立保護物件列表時呼叫。在此處理程式中,您可以向專案新增新過程,或修改/刪除已有的過程。
OnBeforeSaveFile
function OnBeforeSaveFile()
end
在編譯期間建立的所有物件寫入輸出檔案之前呼叫。在此事件處理程式中,您可以更改檔案及其屬性(如資源列表、匯出函式列表、節名稱等)。
OnBeforePackFile
function OnBeforePackFile()
end
在打包受保護檔案之前呼叫。您可以修改即將被打包的檔案。僅當啟用了"打包輸出檔案"選項時才會呼叫此事件。
OnAfterSaveFile
function OnAfterSaveFile()
end
在編譯期間建立的所有物件寫入輸出檔案之後呼叫。事件處理程式可以向輸出檔案新增新資料或更改之前寫入的資料。
OnAfterCompilation
function OnAfterCompilation()
end
在編譯專案的所有物件之後呼叫。在此階段,使用者可以造訪已編譯的專案,並可以執行任何操作,例如新增數字簽名(證書)。
浮水印
VMProtect 提供了向受保護檔案新增所有者隱藏資訊的獨特功能。浮水印是每個使用者唯一的位元組陣列。如果浮水印被嵌入到受保護檔案中,您始終可以確定洩露副本的所有者(例如,如果被破解的程式被分發)並採取相應措施。
浮水印資料庫檔案儲存位置:
- Windows:
%ApplicationData%/VMProtect Software/VMProtect.dat - macOS:
/Users/Shared/VMProtect Software/VMProtect.dat
"浮水印"對話視窗包含兩個選項卡:
- 設定 — 管理浮水印:新增、刪除、重新命名浮水印。每個浮水印包含名稱和值,可以生成隨機值。值中的 "?" 符號在插入到受保護檔案時會被替換為隨機值。
- 搜尋 — 在可執行檔案或受保護應用程式的指定程式中定位浮水印。支援在檔案中搜尋和在執行中的模組中搜尋。
重要提示:浮水印不適用於 Lite 版本。當搜尋加殼後的可執行檔案中的浮水印時,應該在執行中的應用程式中搜尋("在模組中搜尋"模式),因為浮水印(以及程式碼和資料)已被打包,只有在應用程式執行時才會解包。
常見問題
通用問題
有沒有辦法自動加密字串和資料陣列?
在 VMProtect 中,您可以隱藏 ANSI 常量和 Unicode 常量。程式碼操作的所有其他資料保持不變。我們建議將所有機密資訊加密儲存,並在使用前直接解密。解密器本身可以被虛擬化。
有沒有辦法保護從多個執行緒呼叫的過程?
VMProtect 100% 多執行緒相容,此類保護沒有任何特殊限制。
可以將 VMProtect 與其他保護器(打包器)一起使用嗎?
在 VMProtect 處理檔案後使用任何其他打包器(保護器)可能會導致受保護的應用程式無法正常工作。
我應該將 VMProtectSDK32.dll/VMProtectSDK64.dll 包含在程式的安裝檔中嗎?
這些庫僅在程式的除錯階段使用(在保護之前)。使用 VMProtect 保護應用程式後,有關使用這些 DLL 的所有資訊將被完全刪除,因此不需要將它們包含在釋出包中。
編譯器訊息
"Address is used by procedure" 錯誤
此錯誤意味著同一地址的命令被兩個包含在受保護物件列表中的過程使用。要解決此問題,應從受保護物件列表中排除其中一個過程。
"Minimum procedure size for compilation is 5 bytes" 錯誤
此錯誤意味著過程太小,無法被保護。要解決此問題,請從受保護物件列表中排除此過程。
"The .text section allocates space required for the new section" 錯誤
此錯誤通常在保護驅動程式時發生。這意味著檔案第一個節和檔案頭中的服務資訊之間的空閒空間太小,無法建立新節。要解決此問題,請增加驅動程式原始碼中的節對齊引數值並完全重建驅動程式。