主页 » 支持 » 用户手册 » 介绍 » 关于保护您的应用程序的建议

关于保护您的应用程序的建议

VMProtect 是保护应用程序代码免遭分析和破解的可靠工具,但只有正确构建应用程序内保护机制才能实现最有效的使用,并且没有可能破坏整个保护的典型错误。让我们回顾一下为您的程序开发良好保护的关键要素。

注册步骤

许多开发人员在设计自己的应用程序注册过程时犯的一个典型错误是将整个注册密钥检查封装到一个单独的函数中,该函数还返回一个易于理解的值:

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;

使用这种方法,入侵者甚至不需要了解密钥检查算法。他可以简单地修改检查过程开始时的代码,使其始终返回正确的注册密钥值:

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 过程进行虚拟化。为了使破解者攻击更加复杂,您可以打开 “超级(虚拟+变异)” 保护模式 ,将代码的变异与后续的虚拟化结合起来。

检查注册密钥

开发人员犯的另一个严重错误是注册密钥检查的错误实施。通常将输入的密钥与正确的值进行比较。破解者可以通过跟踪字符串比较函数的参数轻松匹配键的正确值:

var ValidRegNumber: String;
...
function CheckRegistration(const RegNumber: String): Boolean;
begin
  if RegNumber=ValidRegNumber then
   Result:=True
  else
   Result:=False;
end;

为了避免这种情况,我们建议比较键的哈希值,而不是它们的实际值。哈希函数是不可逆的,因此破解者无法从哈希中检索出真正的键值,不得不花费更多的时间研究程序,因为现在需要分析更多的代码片段,而不仅仅是注册密钥检查程序:

var
  HashOfValidRegNumber: Longint;
...
// Peter Weinberger's PJW hashing algorithm example of use
function HashPJW(const Value: String): Longint;
var I:Integer;
    G:Longint;
begin
  Result:=0;
  for I:=1 to Length(Value) do
   begin
    Result:=(Result shl 4)+Ord(Value[I]);
    G:=Result and $F0000000;
    if G<>0 then
     Result:=(Result xor (G shr 24)) xor G;
   end;
end;

function CheckRegistration(const RegNumber: String): Boolean;
begin
  if HashPJW(RegNumber)=HashOfValidRegNumber then
   Result:=True
  else
   Result:=False;
end;
...
initialization
  HashOfValidRegNumber:=HashPJW(ValidRegNumber);

end.

在使用 VMProtect 保护应用程序时,应利用 HashPJW 和 CheckRegistration 函数来让破解者的解决办法更复杂化。

保存检查结果

通常,即使是在注册程序上花费大量时间的开发人员也没有适当注意保护注册程序的结果。下面的示例在调用序列号检查过程之前使用全局变量来存储和控制应用程序的注册状态。对于入侵者来说,找到一个全局变量是小菜一碟 – 他只需比较注册前后的数据段。顺便说一句,流行的 ArtMoney 程序使用相同的原理。

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);

这些是利用内置保护机制的最简单方法。注册过程、注册密钥检查和存储结果的实际实现仅限于开发人员的创造力。无论如何,在开发自己的保护机制时,您应该了解这些潜在的错误以避免它们。