使用者手冊

VMProtect 完整中文使用者手冊,涵蓋軟體保護的全部功能和 API 文件。

簡介

不存在保護軟體免受未經授權使用和分發的理想方法。現有的任何系統都無法提供絕對的安全性,也無法阻止潛在的駭客對其進行破解。然而,使用高質量和高效的保護可以使破解軟體變得極其困難,使投入的時間和精力完全不值得。雖然軟體保護可以追求不同的目標,但任何保護系統的基礎都是保護應用程式免受分析,因為抵抗逆向工程的能力決定了保護系統的整體效率。

術語表

如果不瞭解相應領域的專業術語,就無法有效地使用工具。以下術語表解釋了 VMProtect 中使用的術語。

軟體分析、破解與保護

軟體產品可以透過靜態分析動態分析來進行分析。靜態分析意味著保護破解演算法基於對受保護應用程式的反彙編結果分析或反編譯。動態分析用於破解加密或動態變化的可執行檔案,因為對這些程式進行靜態分析已證明是困難的。

在動態分析中,被破解的程式在偵錯程式框架中執行。這樣,程式執行期間發生的一切都可以被偵錯程式控制。在動態分析期間,破解者使用除錯模式逐一繞過程式的所有保護演算法,特別是註冊金鑰的生成和檢查過程。動態分析經常使用的另一個工具是跟蹤被破解程式查詢的檔案、系統服務、埠和外部裝置。

保護應用程式免受破解嘗試的主要工具是軟體保護器。大多數保護器提供的保護基於對原始可執行檔案的加殼和/或加密,並高度重視保護解殼/解密過程。

這種演算法通常不足以提供可靠的保護。如果應用程式受加殼保護,駭客可以在解殼器完成工作後立即對記憶體進行轉儲,輕鬆獲取原始的未加殼檔案。此外,有多種自動化工具可以破解最流行的保護器。加密也是如此:在獲得適當的授權金鑰後,破解者可以解密受保護的程式碼部分。

一些軟體保護器使用多種反除錯技術。然而,每種技術都會顯著影響受保護程式的效能。此外,反除錯方法僅對動態分析有效,對靜態分析完全無效。更甚的是,現代保護器使用的所有反除錯方法都是眾所周知的,破解者已經編寫了許多實用程式來避免或繞過它們。

更有效的保護應用程式方式是混淆虛擬化,它們使受保護應用程式的程式碼分析變得複雜。通常,這些保護方法的高效性基於人為因素:程式碼越複雜、應用程式使用的資源越多,破解者就越難理解程式邏輯,從而越難破解保護。

混淆透過新增多餘的指令來"混淆"應用程式的程式碼。虛擬化將原始碼轉換為由模擬具有特定指令集的虛擬機器的特殊直譯器執行的位元組碼。因此,虛擬化導致結果程式碼具有高且不可逆的複雜性,如果正確應用,用這種方法保護的程式碼不包含顯式恢復原始程式碼的方法。虛擬化的主要優勢在於虛擬化的程式碼片段在執行期間不會轉換為機器語言指令,這防止了破解者獲取應用程式的原始程式碼。

什麼是 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 個版本:LiteProfessionalUltimate

功能LiteProfessionalUltimate
混淆方法
變異
虛擬化
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 時,應用檢查輸入的密碼是否正確並顯示相應的訊息。

Delphi 專案示例

密碼使用非常簡單的演算法進行檢查:第一步將其轉換為數字形式,然後計算除以 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 檔案選擇要虛擬化的程式碼片段,序言和尾聲也將被虛擬化,從而顯著增強保護能力。此外,如果一個虛擬化函式從另一個虛擬化函式中呼叫,控制權在它們之間轉移時不會實際跳轉到被呼叫函式的地址。這也加強了程式的保護。

使用 PDB/MAP 檔案

要建立 MAP 檔案,您需要在編譯器設定中啟用相應選項。

Visual Studio

在 IDE 主選單中,開啟專案屬性(Project – Properties),在 "Linker – Debugging" 選項卡中將 "Generate MAP File" 設定為 "Yes (/MAP)":

Visual Studio MAP 檔案設定

Borland Delphi

在 Delphi IDE 主選單中開啟專案選項(Project – Options),在 "Linker" 選項卡中將 "MAP file" 部分設定為 "Detailed":

Delphi MAP 檔案設定

啟用 MAP 檔案生成後,必須重新構建專案。載入 MAP 檔案時,VMProtect 會比較 MAP 檔案和受保護檔案的修改日期和時間。如果不同,則不會載入 MAP 檔案。

使用標記

要保護程式碼的各個片段以及保護字串常量,您可以在原始碼中插入特殊標記。標記是從 SDK 庫匯入的函式呼叫:

.NET

Windows

Linux

macOS

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:

當 VMProtect 分析受保護應用程式的程式碼時,它會定位所有對 VMProtectSDK 過程和函式的呼叫。受保護塊的邊界由標記對 VMProtectBegin 和 VMProtectEnd 定義。然後,VMProtect 處理受保護應用程式程式碼時會刪除標記和所有對 VMProtectSDK 的引用,因此無需將這些庫包含在安裝檔中。

重要提示:使用標記時,不應允許從非保護區域跳轉到標記內部。如果使用標記的應用程式在保護後變得無法正常工作,您可以透過啟用"除錯模式"選項來檢測來自非保護區域的跳轉和地址。

.NET ObfuscationAttribute

VMProtect 支援以下 ObfuscationAttribute.Feature 值:

SDK 函式

SDK 函式可以整合到受保護應用程式的原始碼中,用於設定受保護區域的邊界、檢測偵錯程式或虛擬化工具。

程式碼標記

服務函式

授權函式

啟用函式

重要提示:保護後,應用程式將不再需要 SDK 庫。

GUI 版本

主視窗由以下元素組成:

VMProtect 主視窗

檔案選單

專案選單

工具選單

幫助選單

工具欄

工具欄由以下元素組成:開啟專案或受保護檔案、儲存專案、受保護檔案的名稱、編譯專案、執行原始/受保護檔案、預設操作、快速搜尋框。

專案區域

"專案" 區域包含以下子區域:

保護的函式

此區域用於選擇必須保護的函式。

編譯型別:為每個受保護物件設定編譯方式,以實現效能和程式碼安全性之間的最佳平衡:

鎖定到序號 — 如果啟用此選項,受保護的函式在未輸入有效序號的情況下將不可用,並將終止應用程式。這樣您可以限制未註冊版本中某些功能的造訪。

管理授權

預設情況下,授權功能處於關閉狀態。要啟用它們,您需要在 "專案" 區域的 "授權" 子區域中建立一對金鑰。初始化完成後,"鎖定到序號" 選項將可用,您將能夠建立和處理序號。

"授權" 區域在左側面板顯示授權的完整列表,在主面板顯示所選元素的引數。右側面板顯示所選授權的詳細資訊,還允許封鎖序號、將其複製到剪貼簿或檢視硬體 ID 資訊。

檔案區域

"檔案" 區域允許開發者將受保護 EXE 檔案執行所需的額外資料(如影象、資料檔案、文字資源和動態連結庫)包含到其中。在執行受保護的 EXE 檔案期間,包括 DLL 在內的所有型別的資料都直接從程式記憶體中載入,而不會將這些資料寫入磁碟。

指令碼區域

"專案" 區域的 "指令碼" 子區域用於使用內建指令碼語言編寫指令碼。您可以在此區域的主面板上編輯指令碼程式碼。

選項區域

"選項" 區域允許您配置各種保護引數:

虛擬機器選項

檔案選項

檢測選項

函式區域

"函式" 區域列出所有可供保護的函式。選擇函式後,您可以在主面板上檢視其屬性和保護選項。對於每個函式,您可以指定編譯型別並啟用鎖定到序號。

詳細資訊區域

"詳細資訊" 區域顯示有關受保護應用程式的各種資訊。它還允許您從打包中排除某些資料段或資源。包含以下子區域:

控制檯版本

在 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]

重要提示:控制檯版本不適用於 Lite 版本。

授權系統

授權系統建立並驗證序號的有效性。它支援按日期和時間限制受保護軟體、鎖定到特定硬體、使用序號加密程式碼、限制免費更新期限等功能。該系統基於非對稱加密演算法,以最大限度地減少構建未經授權的序號生成器("序號產生器")的可能性。

功能特性

工作原理

保護和授權的流程如下:

  1. 開發者在 VMProtect 中建立金鑰對(公鑰和私鑰)。公鑰將嵌入到受保護的應用程式中,私鑰用於生成序號。
  2. 開發者設定保護選項,選擇要保護/虛擬化的函式,可選地將某些函式鎖定到序號。
  3. VMProtect 編譯並保護應用程式。
  4. 使用者購買軟體後,開發者(或自動系統)使用私鑰生成序號。
  5. 使用者將序號輸入到應用程式中,授權系統驗證序號並解鎖相應功能。

整合到應用程式

將授權系統整合到應用程式中分為兩個階段。在第一階段,您可以使用測試模式來熟悉 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 檔案中新增 UserNameEMail 引數,使用 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() 讀取 nUserDataLengthbUserData 欄位獲取這些資料。

步驟 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_CORRUPTED0x01授權系統已損壞。可能原因:保護專案設定不正確、破解嘗試。
SERIAL_STATE_FLAG_INVALID0x02序號不正確。授權系統無法解密序號。
SERIAL_STATE_FLAG_BLACKLISTED0x04序號與產品匹配,但在 VMProtect 中被列入黑名單。
SERIAL_STATE_FLAG_DATE_EXPIRED0x08序號已過期。
SERIAL_STATE_FLAG_RUNNING_TIME_OVER0x10程式執行時間已耗盡。
SERIAL_STATE_FLAG_BAD_HWID0x20硬體識別符號與序號中的不匹配。
SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED0x40序號與受保護程式的當前版本不匹配。

VMProtectGetSerialNumberData

獲取序號的詳細資訊:

bool VMProtectGetSerialNumberData(VMProtectSerialNumberData *Data, int Size);

VMProtectSerialNumberData 結構體包含以下欄位:

欄位型別說明
nStateint金鑰狀態的位標誌掩碼。
wUserNamewchar_t[256]客戶名稱(UNICODE,以 null 結尾)。
wEMailwchar_t[256]客戶信箱(UNICODE,以 null 結尾)。
dtExpireVMProtectDate金鑰過期日期。
dtMaxBuildVMProtectDate金鑰可用的最大產品構建日期。
bRunningTimeint程式執行的最大分鐘數。
nUserDataLengthunsigned char使用者資料的長度。
bUserDataunsigned char[255]放入金鑰的使用者資料。

VMProtectDate

日期結構體:

欄位型別說明
wYearunsigned short年份。
bMonthunsigned char月份,從 1 開始。
bDayunsigned 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(使用示例)兩個專案。

使用步驟:

  1. 將 Usage 專案的程式碼作為基礎,新增對 VMProtect.KeyGen.dll 的引用。
  2. 在 VMProtect 中開啟"專案 | 匯出金鑰對"對話方塊,選擇".NET KeyGen 引數"選項。
  3. 將匯出的文字複製到應用程式中作為字串常量。

示例程式碼:

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_powmbi_powmodbcpowmod 函式。如果生成時間過長,建議啟用這些函式。

序號格式

序號結構

序號由多個塊組成。每個塊以標識位元組開始,指示塊的內容和可能的長度。最後一個塊的識別符號始終為 255,包含序號的校驗和。

塊格式

ID名稱大小(位元組)說明
0x01版本1序號版本,必須為 1。
0x02使用者名稱1 + NUTF-8 編碼的使用者名稱,前有 1 位元組長度。最大 255 位元組。
0x03信箱1 + NUTF-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 演算法加密的步驟:

  1. 在序號開頭新增隨機資料 — 基於 RFC2313 的方法。在金鑰開頭新增:00 02 NN…NN 00,其中 NN…NN 是 8 到 16 個隨機非零位元組。
  2. 在序號末尾新增隨機資料 — 序號的總位元組數必須等於演算法金鑰位數除以 8。格式為:00 02 NN..NN 00 DD..DD MM..MM
  3. 加密 — 使用處理大數的標準過程。
  4. 打包 — 加密後的位元組集被編碼為 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 左側面板中點選"新增新程式碼"連結:

WebLM 新增啟用碼

從上方下拉選單中選擇所需產品,填寫其餘表單資料,然後點選"儲存"按鈕。您將看到可用於除錯啟用 API 的啟用碼。

WebLM 啟用碼

啟用 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_OK0啟用成功。序號已放入 serial 變數。
ACTIVATION_SMALL_BUFFER1緩衝區太小。
ACTIVATION_NO_CONNECTION2無法連線到 Web License Manager。
ACTIVATION_BAD_REPLY3啟用伺服器返回了意外結果。
ACTIVATION_BANNED4此啟用碼已被軟體供應商在伺服器上封禁。
ACTIVATION_CORRUPTED5啟用模組自檢系統出錯,通常表示破解嘗試。
ACTIVATION_BAD_CODE6指定的程式碼在啟用伺服器資料庫中未找到。
ACTIVATION_ALREADY_USED7此程式碼的啟用計數器已耗盡。
ACTIVATION_SERIAL_UNKNOWN8給定的序號在伺服器資料庫中未找到。
ACTIVATION_EXPIRED9程式碼的啟用期已過期。
ACTIVATION_NOT_AVAILABLE10啟用/停用不可用。

提示和技巧

不要忘記為有網際網路問題的使用者提供離線啟用方式。啟用 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 核心和浮水印的專用函式:

  • string — 字串操作
  • table — 表操作
  • math — 數學函式
  • bit32 — 位操作
  • io — 輸入/輸出
  • os — 作業系統
  • 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" 錯誤

此錯誤通常在保護驅動程式時發生。這意味著檔案第一個節和檔案頭中的服務資訊之間的空閒空間太小,無法建立新節。要解決此問題,請增加驅動程式原始碼中的節對齊引數值並完全重建驅動程式。