技术文档
.NET 加固推荐方案
.NET Framework 与 .NET 程序集的方法体加密、IL 混淆、反调试组合,以及 AOT/单文件 等发布形态的兼容性建议。
最后更新于 2025-06-06预计阅读 6 分钟
1. 适用范围
本文适用于 托管 .NET 程序集:
| 运行时 | 架构 | 典型产物 |
|---|---|---|
| .NET Framework 4.x | x86 / x64 / AnyCPU | .exe、.dll |
| .NET 5 - 10 | x86 / x64 / AnyCPU | .exe、.dll |
| ASP.NET Core | x64 | Web 应用与依赖程序集 |
不适用于:
- Unity IL2CPP 产物(已是 Native,见 Native 加固推荐方案)
- C++/CLI 混合模式程序集(同时包含原生代码,需要单独验证)
- NativeAOT 产物(已是原生二进制)
2. 可选能力
| 能力 | 作用 | 代价 | 建议 |
|---|---|---|---|
| 压缩保护 | 压缩程序集,减小体积 | 启动时解压 | 开 |
| JIT 加密 / 方法体加密 | 加密方法体,运行时解密后 JIT 编译 | 首次调用有解密开销 | 核心方法开 |
| 函数混淆 | 打乱 IL 控制流,增加反编译成本 | 小幅性能开销 | 业务逻辑开 |
| 反调试 | 检测调试器与诊断工具 | 与开发、排障冲突 | 发布版开 |
| 内存校验 | 检测运行时内存修改 | 少量开销 | 视场景 |
| 函数虚拟化 | — | — | 当前 .NET 不支持 |
.NET 的函数保护以“混淆”和“加密”为主。GUI 函数保护表中的“虚拟化”列对 .NET 不可勾选。
3. 三档推荐方案
3.1 基础档:内部工具 / 快速验证
| 选项 | 设置 |
|---|---|
| 压缩保护 | 开 |
| JIT 加密 | 关 |
| 反调试 | 关 |
| 内存校验 | 关 |
| 函数保护 | 5–10 个核心方法混淆 |
3.2 标准档:商业软件发布
| 选项 | 设置 |
|---|---|
| 压缩保护 | 开 |
| JIT 加密 | 开 |
| 反调试 | 开 |
| 内存校验 | 开 |
| 函数保护 | 授权、支付、协议方法加密;业务逻辑混淆 |
3.3 强化档:高价值授权逻辑
| 选项 | 设置 |
|---|---|
| 压缩保护 | 开 |
| JIT 加密 | 开 |
| 反调试 | 开 |
| 内存校验 | 开 |
| 函数保护 | 20–40 个敏感方法加密 + 关键业务方法混淆 |
加密的方法越多,首次调用和启动阶段的开销越明显。建议从授权、支付和协议签名方法开始。
4. 方法选择策略
4.1 优先加密
- 许可证解析、签名校验、试用期判断
- 支付回调验签、订单状态机
- 协议序列化 / 反序列化中的签名与密钥派生
- 核心业务算法、规则引擎
- 反调试与完整性校验自身的方法
4.2 优先混淆
- 普通业务逻辑、状态机、工作流
- 字符串和常量密集的配置解析
- 不希望暴露调用关系的模块
4.3 谨慎处理
- 程序入口、模块初始化、静态构造函数:会影响启动
- 高频调用的工具方法:加密后的首次 JIT 开销会放大
- 依赖反射、
dynamic、序列化特性的类型:混淆可能改变名称 async/await、迭代器、LINQ 表达式树:涉及编译器生成的状态机- P/Invoke、COM Interop 边界方法
- 第三方库、NuGet 包:优先保护自己的程序集
5. 发布形态与兼容性
5.1 ReadyToRun(R2R)
R2R 会把部分方法预编译为原生代码。方法体加密依赖运行时解密与 JIT,R2R 方法可能绕过该流程。
建议:
- 需要方法体加密时,发布包关闭 R2R,或只对未预编译的方法启用加密。
- 必须使用 R2R 时,先验证加密方法是否仍被正确解密。
5.2 NativeAOT
NativeAOT 产物是原生二进制,不再有 IL 和 JIT。应改用 Native 加固推荐方案,不要对 AOT 产物套用 .NET 方法加密。
5.3 单文件发布(Single-file)
单文件发布会把依赖打包进一个宿主。保护前建议:
- 先验证保护工具能否正确解析单文件结构。
- 优先保护主程序集,再考虑依赖包。
- 解包目录、
AppContext.BaseDirectory相关逻辑需要回归。
5.4 Trimming
裁剪会移除未引用的元数据和方法。保护可能引用这些元数据,导致运行时找不到方法。
建议:
- 保留裁剪描述文件(
ILLink.Descriptors.xml)中对保护相关类型的保留规则。 - 保护后执行完整功能回归,尤其是反射和序列化路径。
5.5 Unity
| 后端 | 处理方式 |
|---|---|
| IL2CPP | 按 Native 方案保护生成的 GameAssembly.dll / libil2cpp.so |
Unity 注意事项:
- 不要直接修改
Managed目录中由引擎生成且校验过的文件,先验证加载顺序。 - 资源与配置的保护与代码保护分开处理。
- Android / iOS 构建还需要处理对应平台的签名与包结构。
5.6 ASP.NET Core / 服务端
- 优先保护业务程序集,不要保护框架自带的
Microsoft.*程序集。 - 发布方式(框架依赖 / 自包含 / 单文件 / AOT)决定可用能力。
- 容器环境中反调试可能误判,建议通过配置或环境变量关闭。
5.7 强名称与签名
- 保护会改变程序集字节,强名称签名会失效。
- 需要强名称的场景,保护后重新签名,或在加载策略中处理验证。
- 发布包 Authenticode 签名应在保护之后执行。
6. 推荐发布流程
Release 构建(关闭 R2R,保留 PDB)
↓
功能自测(原始产物)
↓
保护(压缩 + 方法加密 + 混淆 + 反调试)
↓
重新签名(强名称 / Authenticode)
↓
打包安装程序
↓
干净环境回归测试
↓
灰度发布
配套建议:
- 保留每次发布的
.sugo配置和客户端版本号。 - 记录哪些方法被加密 / 混淆,便于崩溃定位。
- 崩溃日志中保留方法名或 token,避免全部脱敏。
7. 验证清单
- 原始程序集可正常运行
- 保护后启动、登录、核心功能正常
- 反射、序列化、依赖注入、插件加载正常
-
async/ 迭代器 / LINQ 路径正常 - 自动更新与增量升级正常
- 强名称 / Authenticode 签名有效
- 在干净机器上安装并运行
- 杀毒软件与 EDR 不误报
- 启动耗时与内存符合预期
- 已保留原始程序集与 PDB
8. 常见问题
8.1 保护后提示找不到方法或类型
可能原因:
- 混淆重命名了反射中使用的名称。
- 裁剪移除了保护依赖的元数据。
- 方法体加密与 R2R / AOT 冲突。
处理:
- 对反射使用的类型添加保留规则。
- 关闭 R2R 或减少加密方法范围。
- 先用原始程序集确认功能正常,再逐项开启保护定位。
8.2 保护后启动变慢
- 压缩会增加启动解压时间。
- 方法体加密会在首次调用时解密并 JIT。
- 减少加密方法数量,或把加密集中在非启动路径。
8.3 JIT 加密后程序直接退出
- 确认运行时版本与目标框架匹配。
- 确认没有使用 NativeAOT / R2R 预编译。
- 检查是否有其他混淆器、代码保护工具同时处理同一程序集。
- 用错误转储和日志定位具体方法。
8.4 强名称验证失败
- 保护后重新签名。
- 或在受控环境关闭强名称验证(不推荐用于生产)。
- 确认签名用的密钥与发布流程一致。
8.5 能否与第三方混淆器一起使用
不建议对同一程序集重复混淆。多个工具修改 IL 和元数据,容易产生不可预测的冲突。确需组合时,按“先混淆、后加密、最后签名”的顺序小范围验证。
9. 相关文档
这份文档有帮助吗?欢迎反馈文档问题或补充场景。提交文档反馈