保护 Electron 应用的许可证校验免遭本地补丁

app.asar 一条命令就能解包,而本地的许可证或试用期校验通常只是一次比较。如何混淆 main、preload 和 renderer,让破解撑不过你的下一次发布。

桌面应用会把它的 JavaScript(包括许可证校验)发送到用户自己的磁盘上,在那里它可以被随意阅读和编辑。

app.asar 可以解包,许可证门控只需改一行

安装你的应用,就等于安装了你的源代码。app.asar 一条命令就能解包,之后 main、preload 和 renderer 就是磁盘上普通的可读文件。只在本地进行的许可证门控通常就是一次比较或一次提前返回,翻转它只需改一行。

这处修改不会一直是私下的。打过补丁的文件,或者应用同样修改的一个小脚本,会作为破解流传开来,并且对该版本的每一份副本都有效。

main、preload 和 renderer 共用一个种子

@afterpack/electron 在一次调用中混淆全部三个程序,并在它们之间共用一个 seed,因此 main、preload 和 renderer 的输出彼此一致。正是这种一致性,让 IPC 通道两端对同一个通道名保持一致。

每次构建都会抽取一个新的种子,因此每次发布的形态都不同。针对某一次发布编写的补丁或脚本,用不到你发布的下一个版本上,编写它的人必须在一个与他之前研究的版本已毫无相似之处的构建里,重新定位这处校验。

接入发布构建

Electron 页面介绍了如何安装插件并接入每一部分。对于发布构建,在一处提高整个应用的保护等级:

// electron.vite.config.ts
withAfterpack(config, { preset: "hard" });

当每一部分各自用单独的命令构建,而不是通过一次插件调用时,在这些命令之间固定 AFTERPACK_seed,让各部分仍然共享同一种形态。一个种子,三种方式说明了哪些工具链需要这样做。

让 Protection Map、备份和 source map 远离打包后的应用:.afterpack/、*.backup.* 和 *.map 都应该加入打包工具的排除规则,因为 app.asar 里的 .map 交还你原始源码的直接程度,与解包出来的 JavaScript 没什么两样。Electron 页面列出了插件会直接拒绝构建的情形。

检查每一部分都得到了保护

每一部分都会在 .afterpack/<leg>/ 下写出自己的 Protection Map,所以你可以分别打开 main、preload 和 renderer,确认包含许可证校验的那个文件被覆盖到了。在打包之前,对构建输出运行 afterpack verify,作为拦截保护未生效的构建的关口。

离线校验的极限在哪里

每次发布都换一种新形态,对想要绕过校验的人来说是实实在在的代价:针对某次构建的模式写出的破解到下一次构建就会失效,而你发布的每一次更新都会让上一版的破解失效。

离线校验仍然必须在用户的机器上正确运行,因此只要在某一个版本上花足够的时间,它就能被找到并打上补丁。对于任何关系到收入的东西,请用一次对你自己服务器的调用来为客户端校验兜底。

下一步

  • Electron:安装、接入、支持矩阵,以及插件拒绝构建的情形。
  • 指令:把某个区域(比如许可证校验)标记为比程序其余部分更高的保护强度(Pro)。
  • 威胁模型:运行你代码的人能触及什么。