翻译|使用教程|编辑:安雯斯|2023-05-23 14:26:24.093|阅读 214 次
概述:VMProtect是新一代软件保护实用程序。本文分享VMProtect入门使用教程许可证部分,欢迎查阅~
# 慧都年终大促·界面/图表报表/文档/IDE等千款热门软控件火热促销中 >>
相关链接:
VMProtect是新一代软件保护实用程序。VMProtect支持德尔菲、Borland C Builder、Visual C/C++、Visual Basic(本机)、Virtual Pascal和XCode编译器。
同时,VMProtect有一个内置的反汇编程序,可以与Windows和Mac OS X可执行文件一起使用,并且还可以链接编译器创建的MAP文件,以快速选择要保护的代码片段。
为了轻松实现应用程序保护任务的自动化,VMProtect实现了内置脚本语言。VMProtect完全支持Windows系列的32/64位操作系统(从Windows 2000开始)和Mac OSX(从版本10.6开始)。重要的是,无论目标平台如何,VMProtect都支持所有范围的可执行文件,即Windows版本可以处理Mac OS X版本的文件,反之亦然。有其他问题请咨询加密解密技术QQ群:766135708
VMProtect 是保护应用程序代码免遭分析和破解的可靠工具,但只有在正确构建应用程序内保护机制并且没有可能破坏整个保护的典型错误的情况下才能最有效地使用。
“许可制度”包含以下小节:
许可系统功能
许可系统如何运作
管理许可证
如何将系统集成到您的应用程序的示例
自动序列号生成
在下面描述的几个步骤中,我们将创建一个查询许可系统的测试应用程序:向其提供序列号,接收序列号的状态及其内容。第一阶段,我们在测试模式下使用许可系统;第二阶段我们使用它,因为它会在实际实践中使用。
许可制度的工作模式
建筑保护总是经历两个主要步骤:开发和发布。至于许可,首先您创建一个应用程序,将保护集成到其中,然后添加检查和功能限制。只有经过全面测试,您才能将产品提供给用户并开始第二阶段。受保护应用程序的测试是一个复杂的过程,因为您需要确保所有检查和条件跳转都正确运行。为所有可能的测试用例制作“真实的”序列号是不方便的。这就是许可系统也提供“开发者模式”(又名“测试模式”)的原因。在这种工作模式下,不对应用程序进行保护,系统对提供的序列号的反应在配置文件中进行调整。当应用程序没有错误并且可以正确地与许可系统一起工作时,VMProtect 将“测试”许可模块替换为执行真实序列号检查的真实许可模块。这是在应用程序受到保护时完成的,因此您不能错误地避免这一步。
第一阶段:测试模式
在测试模式下,许可系统(状态和它返回的数据)对提供的序列号的所有反应都在配置文件中描述。该文件名为 VMPLicense.ini,应位于应用程序的工作文件夹中。在下面提供的 10 个步骤中,我们将从创建最简单的应用程序到在具有硬件锁定和限制免费升级期限的测试模式下使用许可系统的全功能。
第一步是创建一个应用程序。这将是一个简单的应用程序,没有任何用户界面,也没有重要的功能。我们的目标是将序列号传递给许可系统并接收其答案。
#include <windows.h> #include <stdio.h> bool is_registered(const char *serial) { return serial && serial[0] == 'X'; } int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity if (!is_registered(serial)) { printf("please register!\n"); return 0; } printf("We are registered.\n"); return 0; }
该程序使用一种非常简单的方法来检查序列号。is_registered ()函数将序列号的第一个符号与“X”进行比较,如果它们匹配则认为该数字是正确的。对于错误的序列号,会显示一条注册消息,而如果用户输入正确的密钥,则会显示“我们已注册”。改为显示
下一步是添加代码以使用 VMProtect 的许可系统检查序列号
如果您以前没有这样做,是时候将 VMProtect SDK 包含到您的项目中了。SDK包含三个文件:头文件(VMProtectSDK.h)、库文件(VMProtectSDK32.lib)和带实现的dll文件(VMProtectSDK32.dll)。对于 64 位系统,库和 dll 文件有单独的实现。
将 dll 文件、头文件和库文件放入我们应用程序的工作文件夹中,源文件所在的位置,并将头文件包含到主文件中:
#include <windows.h> #include <stdio.h> #include "VMProtectSDK.h"
构建项目并确保它像以前一样编译和运行。许可系统尚未激活。
将序列号发送到许可系统
现在,在序列号行的正下方,我们添加对许可系统的 SDK 函数的调用:
char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); printf("res = 0x%08X\n", res);
如果执行此操作后程序停止并提示缺少所需的 dll 文件,请确保将相应的 DLL 文件放入我们应用程序的工作文件夹中。如果执行成功,您应该会看到以下消息:
2 对应于API 中描述的SERIAL_STATE_FLAG_INVALID 标志。这意味着许可系统认为我们的密钥不正确,这是真的,因为我们没有向系统“解释”哪些密钥是正确的,哪些不是。
[TestLicense] AcceptedSerialNumber=Xserialnumber
现在,再次运行我们的程序。如果您仍然收到“2”错误代码,请确保 ini 文件位于应用程序的工作文件夹中。这次我们应该收到“0”。这是许可系统接受并批准序列号的标志。现在我们可以从代码中删除is_registered()函数——许可系统现在负责检查序列号:
#include <windows.h> #include <stdio.h> #include "VMProtectSDK.h" int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); printf("res = 0x%08X\n", res); if (res) { printf("please register!\n"); return 0; } printf("We are registered.\n"); return 0; }
打印标志的便捷功能
首先,我们需要一个方便的函数来将标志的数值转换为序列号的可理解状态。下面是这个函数的代码:
#define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag) void print_state(INT state) { if (state == 0) { printf("state = 0\n"); return; } printf("state = "); PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED); PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID); PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED); PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED); PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER); PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID); PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED); printf("\n"); }
尽管大小不一,但功能非常简单——一一检查所有位标志并打印状态变量中存在的所有内容。在检查调用print_state的序列号后替换代码中的printf,并更改我们传递给许可系统的序列号:
char *serial = "Xserialnumber1"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); print_state(res);
现在,如果我们运行这个程序,下面的消息将被打印到控制台:
state = SERIAL_STATE_FLAG_INVALID please register!
现在,我们通过删除“1”放回旧密钥并再次运行程序:
state = 0 We are registered.
现在,我们可以看到序列号的状态标志,让我们开始从序列号中检索标志和数据。
检索序列号状态
您可以通过三种方式获取序列号的状态:通过调用VMProtectSetSerialNumber()、通过调用VMProtectGetSerialNumberState()或通过调用VMProtectGetSerialNumberData() – 状态标志被放入结构的字段之一。每种方法都旨在在特定时间使用。第一次检查序列号是在安装期间执行的。此时应拒绝错误号码、过期号码、黑名单号码等。一些限制,例如,程序的最长运行时间或序列号到期日期也应在运行时检查。和VMProtectGetSerialNumberState()方法是这里最快和最方便的方法。如果您需要接收有关序列号的完整信息,您可以使用更强大的VMProtectGetSerialNumberData()函数。
让我们从简单的事情开始。我们想从序列号中获取用户的姓名和电子邮件,以便在“关于”窗口(或其他任何地方)中显示它们。为此,我们必须在 ini 文件中再添加两行:
[TestLicense] AcceptedSerialNumber=Xserialnumber UserName=John Doe EMail=john@doe.com
而在程序中,如果注册成功,我们获取这些数据并输出到屏幕:
VMProtectSerialNumberData sd = {0}; VMProtectGetSerialNumberData(&sd, sizeof(sd)); printf("name = %ls,\ne-mail = %ls\n", sd.wUserName, sd.wEMail);
该结构包含 UNICODE 数据,因此printf()使用 %ls 说明符而不是 %s。该程序应在屏幕上打印以下文本:
state = 0 We are registered. name = John Doe, e-mail = john@doe.com
现在按照以下格式在 ini 文件中添加一个新行:ExpDate=YYYYMMDD。例如:
ExpDate=20000101
此行中指定的日期必须已经过去,即最大日期是昨天。当我们运行程序时,我们应该看到以下内容:
state = SERIAL_STATE_FLAG_DATE_EXPIRED please register!
现在让我们在显示“请注册”消息和程序存在之前获取更多信息:
if (res) { VMProtectSerialNumberData sd = {0}; VMProtectGetSerialNumberData(&sd, sizeof(sd)); printf("exp. date: y = %d, m = %d, d = %d\n", sd.dtExpire.wYear, sd.dtExpire.bMonth, sd.dtExpire.bDay); printf("please register!\n"); return 0; }
该应用程序的第二次运行现在为我们提供了更多详细信息:
state = SERIAL_STATE_FLAG_DATE_EXPIRED exp. date: y = 2000, m = 1, d = 1 please register!
好的,现在从 ini 文件中删除 ExpDate=… 行,这样它就不会影响我们要做的其他事情。
您可以限制程序从启动的那一刻开始运行。这对于演示目的很有用:您向用户提供了一个真实的序列号,但该程序的运行时间不超过 5 分钟。许可系统不会强制关闭此类程序,而只是设置状态标志。因此,让我们通过将以下行添加到 ini 文件来将最长工作时间设置为一分钟:
TimeLimit=1
并修改程序如下:
int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); print_state(res); if (res) return 0; VMProtectSerialNumberData sd = {0}; VMProtectGetSerialNumberData(&sd, sizeof(sd)); printf("I will run for %d minute(s)\n", sd.bRunningTime); print_state(VMProtectGetSerialNumberState()); Sleep(60 * 1000 * sd.bRunningTime); printf("After %d minute(s):\n", sd.bRunningTime); print_state(VMProtectGetSerialNumberState()); return 0; }
该程序在启动时打印序列号的状态,然后计算最长运行时间并等待其到期。然后再次打印序列号状态。将最大操作时间设置为一分钟后,我们应该会收到以下结果:
state = 0 I will run for 1 minute(s) state = 0 After 1 minute(s): state = SERIAL_STATE_FLAG_RUNNING_TIME_OVER
受保护程序应定期分析序列号的状态,并在设置标志时关闭。许可系统不会自动执行此操作,因为程序可能需要释放内存、将数据保存到文件等。此外,您可能希望程序在操作时间到期后不要停止,而是切换到更受限制的模式。许可系统将此留给开发人员。
怎么运行的
当 VMProtect 保护应用程序时,它会记录日期。许可系统将此日期视为应用程序的构建日期。您可以将此序列号可以使用的最大构建日期放入序列号中。因此,如果您将当前日期加上一年作为序列号,它将适用于您将在一年内发布的所有程序版本。一年零一天后发布的版本将无法使用此序列号,用户可以选择:使用旧版本的程序或购买新密钥以使用最新版本的程序再使用一年.
让我们试试
将格式为 MaxBuildDate=YYYYMMDD 的行放入 ini 文件中:
MaxBuildDate=20000101
在测试模式下,许可系统将今天视为构建日期,因此这一行中指定的日期已经过去很重要。也就是说,最大日期是昨天。修改main()函数的代码,使其看起来像这样:
int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); print_state(res); if (res) { VMProtectSerialNumberData sd = {0}; VMProtectGetSerialNumberData(&sd, sizeof(sd)); printf("max. build date: y = %d, m = %d, d = %d\n", sd.dtMaxBuild.wYear, sd.dtMaxBuild.bMonth, sd.dtMaxBuild.bDay); printf("please register!\n"); return 0; } printf("I'm registered\n"); return 0; }
然后,在程序运行时,您应该看到以下内容:
state = SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED max. build date: y = 2000, m = 1, d = 1 please register!
通过将 ini 文件中的日期替换为今天或明天,我们最终得到了“工作”程序:
state = 0 I'm registered
从 ini 文件中删除 MaxBuildDate=… 行,这样它就不会影响我们的进一步步骤。
许可系统不应接受在 VMProtect 中标记为“已阻止”的序列号。当您下次重建您的应用程序时,VMProtect 会将黑名单序列号的哈希值添加到受保护的应用程序中。因此,应用程序的许可系统将来会拒绝这些序列号。
首先,让我们最小化main()函数的内容:
int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); print_state(res); return 0; }
现在,运行程序并确保许可系统接受我们的序列号:
int main(int argc, char **argv) { char *serial = "Xserialnumber"; // we set the serial number directly in the code, for simplicity int res = VMProtectSetSerialNumber(serial); print_state(res); return 0; }
现在,将这个序列号添加到许可系统的黑名单中。将以下行添加到 ini 文件中:
BlackListedSerialNumber=Xserialnumber
并再次运行程序:
state = SERIAL_STATE_FLAG_BLACKLISTED
我们是否应该通知用户他或她输入的序列号被列入黑名单?它是由你决定。您可以简单地告诉序列号不正确或通知用户密钥已泄露。许可系统只是通知程序有关使用黑名单序列号的事实。
接收硬件标识符
在我们锁定硬件之前,我们必须收到一个硬件标识符。标识符被放入序列号中,当序列号传递给许可系统时,它会检查标识符是否匹配。因此,首先我们需要接收硬件的标识符。让我们将main()函数减少到最低限度:
int main(int argc, char **argv) { int nSize = VMProtectGetCurrentHWID(NULL, 0); char *buf = new char[nSize]; VMProtectGetCurrentHWID(buf, nSize); printf("HWID: %s\n", buf); delete [] buf; return 0; }
通过运行该程序,我们会收到一个默认的测试硬件标识符:
HWID:myhwid
要更改标识符,请将以下行添加到 ini 文件中:
MyHWID=test
如果我们之后运行程序,我们可以看到系统认为“test”是我们 PC 的硬件标识符:
HWID: test
重要的!只有经过 VMProtect 处理后,程序才会显示真实的硬件标识符。
硬件锁定序列号
要将我们的测试序列号锁定到硬件,我们应该在 ini 文件中再添加一行。这次我们定义“放入”序列号的标识符:
KeyHWID=test
然后我们将main()复杂化一点。现在它将传递一个序列号并分析它得到的结果:
int main(int argc, char **argv) { int nSize = VMProtectGetCurrentHWID(NULL, 0); char *buf = new char[nSize]; VMProtectGetCurrentHWID(buf, nSize); printf("HWID: %s\n", buf); delete [] buf; char *serial = "Xserialnumber"; int res = VMProtectSetSerialNumber(serial); print_state(res); return 0; }
运行代码后我们会看到如下结果:
HWID: test state = 0
许可系统已将当前硬件标识符与序列号中写入的标识符进行比较。标识符相等,因此VMProtectSetSerialNumber()函数返回 0——序列号匹配。
现在让我们尝试在另一个硬件上“运行”我们的程序。我们只需将 ini 文件中 MyHWID 参数的值从“test”更改为“new test”。再次运行程序:
HWID: new test state = SERIAL_STATE_FLAG_BAD_HWID
这次许可系统返回了 SERIAL_STATE_FLAG_BAD_HWID 标志,这意味着真实的硬件标识符与存储在序列号中的不匹配。我们在屏幕上看到的当前标识符是“new test”,而序列号是“test”。如果我们将 ini 文件中的 KeyHWID 参数更改为“new test”,我们也可以让我们的序列号在这个“硬件”上工作。
序列号最多可容纳 255 个字节的任意数据,许可系统按原样传递给程序。数据可以包含有关销售的任何其他信息、完整版操作所需的数据或其他内容。让我们修改我们的main()函数,让它从序列号中读取数据并将它们显示在屏幕上:
int main(int argc, char **argv) { char *serial = "Xserialnumber"; int res = VMProtectSetSerialNumber(serial); print_state(res); if (res) return 0; VMProtectSerialNumberData sd = {0}; VMProtectGetSerialNumberData(&sd, sizeof(sd)); printf("Serial number has %d byte(s) of data\n", sd.nUserDataLength); for (int i = 0; i < sd.nUserDataLength; i++) printf("%02X ", sd.bUserData[i]); printf("\n"); return 0; }
我们还将 Ini 文件缩减为:
[TestLicense] AcceptedSerialNumber=Xserialnumber
现在,我们运行程序并确保我们的序列号正常工作,但不包含任何数据:
state = 0 Serial number has 0 byte(s) of data
要将新的用户数据添加到序列号中,我们需要在 ini 文件中创建 UserData 变量,并以 HEX 格式为其分配数据。符号必须成对出现,即一行的长度必须是 2 的倍数。像这样:
UserData=010203A0B0C0D0E0
在这种情况下,如果我们运行该程序,我们将收到以下结果:
state = 0 Serial number has 8 byte(s) of data 01 02 03 A0 B0 C0 D0 E0
第二阶段:实模式
在实模式下,VMProtect 许可系统为受保护的应用程序放置了一个特殊的许可模块。该模块执行与 SDK 中的测试模块相同的功能,但使用序列号的内容而不是配置 ini 文件。接下来的五个步骤说明了使用基于 VMProtect 和许可系统的全功能保护来保护简单应用程序的过程。
在第一阶段,我们制作了几个简单的应用程序来测试许可系统的 API。现在,在第二阶段,我们将只创建一个应用程序。它还将是一个控制台应用程序,其foo()函数仅在注册版本中有效。这是我们的测试应用程序的代码:
#include <windows.h> #include <stdio.h> #include "VMProtectSDK.h" #define PRINT_HELPER(state, flag) if (state & flag) printf("%s ", #flag) void print_state(INT state) { if (state == 0) { printf("state = 0\n"); return; } printf("state = "); PRINT_HELPER(state, SERIAL_STATE_FLAG_CORRUPTED); PRINT_HELPER(state, SERIAL_STATE_FLAG_INVALID); PRINT_HELPER(state, SERIAL_STATE_FLAG_BLACKLISTED); PRINT_HELPER(state, SERIAL_STATE_FLAG_DATE_EXPIRED); PRINT_HELPER(state, SERIAL_STATE_FLAG_RUNNING_TIME_OVER); PRINT_HELPER(state, SERIAL_STATE_FLAG_BAD_HWID); PRINT_HELPER(state, SERIAL_STATE_FLAG_MAX_BUILD_EXPIRED); printf("\n"); } char *read_serial(const char *fname) { FILE *f; if (0 != fopen_s(&f, fname, "rb")) return NULL; fseek(f, 0, SEEK_END); int s = ftell(f); fseek(f, 0, SEEK_SET); char *buf = new char[s + 1]; fread(buf, s, 1, f); buf[s] = 0; fclose(f); return buf; } // The foo() method is very short, but we need it to be an individual function // so we asked the compiler to not compile it inline __declspec(noinline) void foo() { printf("I'm foo!\n"); } int main(int argc, char **argv) { char *serial = read_serial("serial.txt"); int res = VMProtectSetSerialNumber(serial); delete [] serial; if (res) { printf("serial number is bad\n"); print_state(res); return 0; } printf("serial number is correct, calling foo()\n"); foo(); printf("done\n"); return 0; }
在没有调试信息的情况下编译程序,但在链接器设置中我们启用了 MAP 文件的创建——我们将需要它与 VMProtect 一起工作。运行程序后,我们应该看到以下文本:
serial number is bad state = SERIAL_STATE_FLAG_INVALID
目前,许可系统仍在测试模式下运行,因为该文件未经过 VMProtect 处理,并且其中不包含许可模块。在下一步中,我们将创建一个 VMProtect 项目并尝试保护我们的应用程序。
现在,当我们的测试应用程序准备就绪、编译并在同一文件夹中分配了一个 MAP 文件时,我们可以运行 VMProtect Ultimate 并打开可执行文件。我们需要向项目添加两个函数:_main(这是 Visual Studio 重命名我们的 main() 的方式)和 foo()。这两个函数都可以在 VMProtect 的“函数”部分的函数列表中看到。
许可系统已初始化,让我们尝试编译 VMProtect 项目并运行受保护的文件。从命令行运行它后,我们将收到以下消息:
C:\test>dummy_app.vmp.exe serial number is bad state = SERIAL_STATE_FLAG_INVALID
如果你运行 depends.exe 可以看到我们受保护的可执行文件不再使用 VMProtectSDK.dll。这意味着许可模块已经内置到程序中。您还可以从 VMProtect 查看已用 DLL 的列表,在“详细信息 | 进口”部分。
我们的受保护程序从 serial.txt 文件中读取序列号。由于还没有这样的文件,许可模块收到一个被解释为不正确的空序列号。现在我们切换到“许可证”部分并生成一个序列号。此处详细描述了此过程,现在我们仅创建一个简单的序列号,没有任何限制。
然后,我们复制序列号(在许可证属性中选择“序列号”字段并按下 Ctrl+C),在与受保护应用程序相同的文件夹中创建一个名为 serial.txt 的文件,并将复制的序列号粘贴到那里。现在,如果我们运行我们的应用程序,我们将看到:
C:\test>dummy_app.vmp.exe serial number is correct, calling foo() I'm foo done
许可系统检查了序列号并发现它是正确的。在下一步中,我们将尝试应用一些限制并观察结果。
序列号有效期
让我们创建另一个具有特定到期日期的序列号。例如,2005。这个日期已经过去了,因此我们的序列号一定是不正确的。切换到“许可证”部分,然后单击工具栏上的“添加许可证”按钮。在“Add license”对话框窗口中启用“Expiration date”选项并指定 2005 年 9 月 30 日。创建序列号,将其复制并粘贴到 serial.txt,然后运行程序:
C:\test>dummy_app.vmp.exe serial number is bad state = SERIAL_STATE_FLAG_DATE_EXPIRED
许可模块返回“序列号已过期”标志。现在,将工作序列号放回 serial.txt 文件并确保许可模块完全接受它。
C:\test>dummy_app.vmp.exe serial number is correct, calling foo() I'm foo done
将序列号加入黑名单
让我们想象一下,我们的“好”序列号已经泄露到 Internet 并且现在已被泄露。我们需要阻止它,以便它在程序的未来版本中不起作用。为此,请在列表中选择序列号并将主面板中的“已阻止”属性设置为“是”。目前序列号还没有被屏蔽,但是当你再次保护文件时,应用程序将不再接受这个序列号。让我们确保这是真的。如果我们现在运行我们的程序,它应该毫无问题地接受被阻止的序列号,因为这是对被阻止的号码一无所知的旧版本:
C:\test>dummy_app.vmp.exe serial number is correct, calling foo() I'm foo done
现在我们复制我们的程序并将其命名为“dummy_app1.vmp.exe”,然后打开 VMProtect 并再次保护应用程序。然后运行这个新版本:
C:\test>dummy_app.vmp.exe serial number is bad state = SERIAL_STATE_FLAG_BLACKLISTED
又是旧版本,为了比较:
C:\test>dummy_app1.vmp.exe serial number is correct, calling foo() I'm foo done
旧版本不知道被阻止的序列号并且像以前一样工作。
在下一步中,我们将尝试将代码锁定到序列号。但在我们继续之前,取消阻止序列号并在 VMProtect 中重新对应用程序应用保护,使其再次接受该序列号。或者只是创建一个新的许可证。
破解程序最常见的方法之一是定位检查序列号的地方和紧随其后的条件跳转。如果序列号是正确的,程序将以一种方式执行,如果不正确,则以另一种方式执行。黑客找到了这个跳转,并将其替换为“正确”方式的跳转。让我们使用这种技术“破解”我们的测试程序。当然,直接在源代码中。让我们“关闭”我们的条件跳转:
char *serial = read_serial("serial.txt"); int res = VMProtectSetSerialNumber(serial); delete [] serial; if (false && res) {
现在,我们的程序接受任何序列号并正常工作。当然,如果文件被 VMProtect 保护,即使是经验丰富的黑客也会像我们一样花费数月的时间来定位和修改条件跳转。并且考虑到程序会在不同条件下多次检查序列号,即使是这种简单的检查也是相当安全的。但让我们更进一步。
将代码锁定到序列号
重要的!VMProtect 的演示版本对处理函数的数量有限制:只处理一个函数。所以如果你使用演示版,你应该只在项目中包含 foo() 函数,否则 VMProtect 的演示版可以选择 main() 函数并且锁定到序列号将不起作用。
VMProtect 的许可系统允许您将一个或多个功能的代码锁定到一个序列号,这样,如果没有提供正确的序列号,它们将无法工作。函数体被虚拟化,然后加密,只能用正确的序列号解密。这意味着,即使黑客发现并修复了序列号检查中的条件跳转,锁定到序列号的功能仍然无法使用。让我们试试这个。在“Functions”部分选择foo()函数,然后在右侧面板中将“Lock to Serial Number”选项更改为“Yes”。
然后,保护应用程序。因为,我们已经“破解”了它,将任意文本放入 serial.txt 文件并运行应用程序。控制台中出现以下文本
C:\test>dummy_app.vmp.exe serial number is correct, calling foo()
这意味着,黑客“修复”了条件跳转,程序以“正确”的方式运行。但是当调用foo()时,程序会显示一条消息:
由于我们将foo()函数锁定到序列号,而黑客并没有它,因此试图解密该函数的代码导致出现故障,无法继续执行程序。当按下“确定”时,程序关闭并且“完成”消息永远不会显示在控制台中。
什么应该锁定到一个序列号?
将只应在程序的注册版本中运行的功能锁定到序列号是有意义的。由于锁定需要虚拟化,因此您应该考虑到一些性能损失。例如,如果文本编辑器不允许在演示版本中保存结果,则可以将保存文档功能锁定为序列号。如果该函数在其运行期间调用其他函数,则没有必要也将它们锁定,因为如果没有 main 函数,它们将没有任何用处。
您还应该记住,在没有序列号的情况下调用锁定功能会导致程序关闭,并且没有机会保存工作结果。这就是为什么您应该彻底测试应用程序以确保它不会在试用模式下调用此类功能。在上面的示例中,文本编辑器必须在演示模式下禁用“保存”命令,并且不响应 Ctrl+S 快捷键。当然,它也不应该要求在退出时保存文档。如果您不注意这一点,用户可能会对您的“错误”演示版本感到失望。
锁定序列号和无效序列号
调用VMProtectSetSerialNumber()函数时,许可模块会检查传递给该函数的序列号。只有在检查时序列号绝对正确时,才会执行加密的代码片段——未列入黑名单、具有正确的硬件标识符、未过期等。在这种情况下,将执行所有加密过程,直到应用程序关闭,或再次调用VMProtectSetSerialNumber() 。
一些限制可能会在程序执行过程中“触发”:例如,程序的运行时间可能到期或序列号到期日期到来。在这种情况下,许可模块仍然加密并执行锁定到序列号的功能。之所以如此,是因为受保护的应用程序很难检测到这些限制触发的时刻并相应地更改行为(阻止相应的菜单项等)。如果许可模块突然停止执行锁定序列号的代码片段,则极有可能导致应用程序出现故障。这就是为什么在设置序列号时做出决定,并选择相应的执行模式。
附加信息
所有位标志、结构格式和函数调用参数的值都可以在该帮助文件的许可系统 API部分找到。使用此部分作为参考,而上面提供的步骤有助于轻松实现典型的即用型保护。
以上便是本篇文章的分享,如果您有任何疑问或者想获取更多产品试用/授权/价格信息,可以咨询我们的了解~
本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@pclwef.cn