AfterPack 把它的各项保护分为几类。每一类以不同的方式提高还原的成本。知道你代码的某个区域由哪一类覆盖,你就知道攻击者读它时能恢复出什么。Protection Map 会按复杂度分数为每个词元着色,并标出你所选代码所属的类别。
静态攻击者的定义
这些是静态攻击者类别。它们描述的是读取字节的人所面对的情况:grep、AST 工具、一遍去混淆器处理,或者一个不运行代码就解释代码的 LLM。在插桩环境下运行产物的攻击者是另一种更强的情形,参见威胁模型。
各个类别
| 类别 | 静态攻击者能恢复什么 | 层级 |
|---|---|---|
| 重命名与编码(多项式单射) | 结构,需要花些功夫。名称、字面量、扁平化的控制流和融合的恒等式,都需要真正的模式识别工作。而这项工作在每次构建上都必须重做一遍。 | Free |
| 扁平化与融合(多项式非单射) | 一个行为相同的等价程序。逻辑被重写,使多个不同的原始计算坍缩为同一个输出,能映射回你究竟写的是哪一个的线索已经不存在了。目前没有任何已发布的变换能达到这一类别。 | — |
Free 层级在你的整个 bundle 上产生重命名与编码类别(完整矩阵见套餐与层级)。更高的预设会在这一类别内部推得更远。默认预设是 light(复杂度目标 5);更强的预设,例如 --preset=hard,推得更远,同时会使整个 bundle 的页面启动时间成倍增加(加载代价)。从 medium 起,引擎会在整个 bundle 上统一加入控制流扁平化、恒等式融合和常量破坏,这些都在重命名与编码类别之内。
目前不会产生扁平化与融合和破坏与融合类别
没有任何构建会达到它们,也从来没有任何区域被归为“扁平化与融合”或“破坏与融合”。/* @afterpack preset=extreme */ 会把一个区域提升到复杂度目标 25 并开启字符串下限,这是重命名与编码类别内部真实、可衡量的保护。
恢复一个类别能让攻击者得到什么
一个类别能让攻击者得到什么,取决于他们的目标是什么。
- 一个密钥(API 密钥、许可证阈值、硬编码的表):两个类别本身都无法隐藏它。任何与你的程序行为相同的程序都会暴露出密钥,包括从受保护区域恢复出的程序。保护密钥需要另一种机制。参见保护密钥。
- 理解并克隆你的代码:静态阅读者可以恢复出一个可运行、可读的等价程序,但前提是完成重命名与编码类别所要求的真正的模式识别工作,而这项工作每次构建都必须重做。这一特性的局限参见 AfterPack 不能替代什么。
- 定位并修补一处校验(关掉付费墙、把某个
return true翻转):这两个类别单独都帮不上太多忙。攻击者只需要找到那一个节点。重新找到它要付出一些代价,而这份代价会在每次发布时重置。自完整性检查(transforms.selfIntegrity.enabled)拦不住这种修补:它只检测对它自身校验表的修改。关于轮换,参见最佳实践。
在你自己的构建上验证
Protection Map 展示逐词元的证据:你的原始源代码,按每个词元的复杂度分数着色,悬停时还能看到它所属的类别以及其背后的变换栈。打开在线演示,或者用 npx afterpack@latest 生成你自己的。
下一步
- 威胁模型:这些类别所针对的对手阶梯。
- Protection Map:你自己构建的逐词元视图。
- 最佳实践:该标记什么,以及为什么。
- 指令:让某个区域在类别之间移动的标记。
- 预设:阶梯,以及每一级设定的复杂度目标。