用户手冊

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" 錯誤

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