.NET程序保护最佳实践
.NET程序是由C#、Visual Basic 等语言开发的基于公共语言运行时(CLR)的应用程序。.NET程序的源代码通常会被编译成中间语言(IL)或字节码(Bytecode),然后在运行时由CLR解释执行。这样的设计有利于跨平台和语言互操作,但也带来了一些安全风险,因为IL或字节码相对于源代码更容易被反编译或反汇编,从而暴露出程序的逻辑和数据。
.NET程序的安全性一直是开发者主要的关注点之一,Virbox Protector 对 .NET 程序的保护,包含了整体的加密、运行时反调试、名称混淆、函数级别的保护(包括代码加密,代码混淆,以及高强度的代码虚拟化等技术)。
对程序做好保护,并不是一件简单的事,需要结合具体的场景和安全需求,尤其是高强度的保护选项,往往伴随着使用限制或者性能问题,没有哪种加密策略可以保证安全性高又不影响性能,本文档将对保护的场景、流程(包括集成构建)以及加密选项进行指引。
#
支持范围.NET 程序最早只在由微软实现的 .NET Framework 环境下环境,随着 Mono 运行时的出现(尤其是 Unity 引擎的推广),以及 .NET Core 的推出和 .NET 5 及以上的推出,运行环境变得更复杂。
由于不同运行时环境对 .NET 标准和底层库的支持不同,而代码保护技术往往依赖一些底层技术,在不同环境下会存在兼容性差异。
#
运行环境运行环境 | 是否支持 | 说明 |
---|---|---|
.NET Framework 2.x ~ 4.x | ✔ | 完整支持 |
.NET Core 2 | ✔️ | 已废弃版本,部分功能不兼容 |
.NET Core 3 | ✔️ | 非Windows系统部分功能不支持 |
.NET 5 以上 | ✔️ | 非Windows系统部分功能不支持 |
Mono Runtime | ✖️ | 如果是Unity引擎,可以使用 Unity 加密方案 |
#
功能与兼容性保护选项 | 兼容性 | 备注 |
---|---|---|
导入表保护 | 支持Windows(x86/x64/arm32/arm64)、Linux(x64) | |
压缩 | 仅支持 Windows 系统;不支持In Memory Module ,比如使用Assembly.Load 加载的 dll | |
JIT加密 | 支持 Windows(x86/x64)、Linux(x64) | |
字符串加密 | 完整支持 | |
资源加密 | 完整支持 | |
附加数据加密 | 仅支持Windows | 部分打包工具会生成附加数据 |
调试器检测 | 完整支持 | |
名称混淆 | 完整支持 | |
[E] 代码加密 | 完整支持 | |
[M] 代码混淆 | 完整支持 | |
[V] 代码虚拟化 | 完整支持 |
#
功能介绍#
基础保护#
压缩压缩 .NET 程序中的 .text 节中的信息,也可以起到静态防反编译的功能。
该功能不支持
Assembly.Load
等内存加载 DLL的调用方式。
保护前
保护后
#
内存校验内存校验是指打包并加密程序中的代码和数据,在程序加载时校验自身完整性,如果发现程序被篡改,则会退出进程,可以防止被篡改。
#
JIT加密加密 .NET 程序中的所有方法的IL字节码,防止程序被静态反编译,仅在特定的函数 JIT 编译时解密一次,不会在内存中驻留明文,可以防止从内存中整体 dump 出 IL字节码。
JIT加密对程序性能影响小,是较为普遍的一种加密方式。
保护前
保护后
#
导入表保护将系统模块的函数调用转换为桩函数调用,降低代码可读性。
保护前
保护后
#
资源加密加密.NET 程序绑定的资源。
保护前
保护后
#
字符串加密加密代码中的敏感字符串,防止反编译工具直接搜索到相关的函数。
保护前
保护后
#
调试器检测程序运行时检测到被调试则退出进程。
#
附加数据加密某些播放器类程序包含附加数据,该功能可以加密 .NET 程序的附加数据。
#
函数级保护#
代码混淆对指定函数进程控制流混淆。
#
代码加密基于 .NET 动态方法技术,运行时动态生成方法。
#
代码虚拟化一种高安全性的保护方式,将 IL 字节法转换为自定义的虚拟机字节码,在自定义虚拟机中解释执行。
安全性高,但对性能影响较大,仅适合对核心的函数保护。
#
名称混淆将代码中的命名空间、类、方法、属性等修改为无意义的名称。
不能混淆被其它模块引用的类和方法,否则可能导致程序无法运行。
#
运行平台指定运行时系统架构。
.NET 的保护选项中,JIT 加密
功能使用了 Native 层的保护技术,对运行平台有影响,勾选需要的运行平台可以兼容指定的运行环境。
注:指定非 Windows平台请勿勾选
压缩
#
强名称签名.NET 强名称签名(Strong Name Signing)是 .NET Framework 中的一种机制,用于为程序集(Assembly)提供唯一性和完整性,强名称签名通过使用公钥加密技术,确保程序集的身份和版本控制。
注:若原文件中带有强签名,界面指定强名称密钥时和原文件中的强签名不一致时,加固时提示"强名称签名失败";
若原文件中不带有强签名,界面指定强名称密钥时,加固不会进行强签名,保护日志中有警告提示。
#
程序集合并将多个.NET程序集合并为一个,消除模块间的引用,合并后可以提升名称混淆的效果。
注:请勿合并保护后的程序集,可以先合并再保护。
使用界面合并程序集
打开 ”工具“ --> “程序集合并” ,设置要合并的程序集:
使用命令行合并程序集
virboxprotector_con -ilmerge <main_assembly> <other_assemblies ...> -o <output_file>
#
SDK标签Virbox Protector 还提供了类/方法标签的方式控制保护方式,C# 使用示例见Virbox Protector安装目录下<install_dir>/example/sdk/C#
。
SDK标签使用了微软提供了标准 Obfuscation 描述,使用时需要引入 System.Reflection
:
using System.Reflection;
以下函数级标签可以对 class 和 method 标记,如果对class标记则会标记该class下的所有方法。
#
代码混淆[Obfuscation(Feature = "Mutation", Exclude = false)]
#
代码加密[Obfuscation(Feature = "Encryption", Exclude = false)]
#
代码虚拟化[Obfuscation(Feature = "Virtualization", Exclude = false)]
#
名称混淆名称混淆白名单,保留该名称,不对其进行名称混淆。
[Obfuscation(Feature = "Renaming")]
#
保护指引#
普通的保护方式如果对安全性没有较高的要求,不需要函数级别的保护,只需要调整基础保护选项即可实现防反编译和防整体内存Dump。
保护选项 | 保护建议 | 说明 |
---|---|---|
压缩 | 如无特别需要不要勾选 | |
JIT加密 | Windows/Linux系统下勾选 | |
字符串加密 | 有敏感字符串则勾选 | |
资源加密 | 需要加密.NET资源则勾选 | |
附加数据加密 | 有附加数据则勾选 | 部分打包工具会生成附加数据 |
调试器检测 | 每个进程只需要保护一个模块(如主程序exe);如果模块做为SDK发布供第三方调用,则不勾选 | |
名称混淆 | 对主程序exe,选择"保留自定义名称";对dll,选择"只混淆私有成员" | |
[E] 代码加密 | Windows 平台使用默认选项(仅加密入口函数);非Windows平台由于不支持JIT加密,建议对必要的函数勾选 | |
[M] 代码混淆 | 无需选择 | |
[V] 代码虚拟化 | 无需选择 |
#
高安全性保护如果对安全性有较高要求,可以在 名称混淆
上加强,并对关键的函数进行代码虚拟化
。
自定义名称混淆
可以对命名空间、类、方法等名称进行混淆,由于模块间一般存在互相调用,对public属性的名称混淆会导致调用出错找不到方法,需要由开发者自定义配置。
对于模块间调用问题,也可以通过程序集合并
功能将多个模块合并为一个再进行保护。Virbox Protector 提供了免费工具 程序集合并
功能,可以在菜单栏 工具
--.NET程序集合并
中找到。virboxprotector_con 支持命令行选项 -ilmerge
进行合并,实现自动化。
举例:
# 将 test.exe 和其依赖的 test.dll 合并为一个 test.exevirboxprotector_con -ilmerge test.exe test.dll -o merged/test.exe
对关键的函数逻辑(如授权验证,关键的加密解密代码逻辑),推荐使用代码虚拟化
进行保护,代码虚拟化对运行性能有一定影响,不适合大面积使用。如需诊断性能问题,可以在函数选项
-添加函数
-性能分析
中运行程序查看函数的调用次数。
#
自动化集成#
命令行工具Virbox Protector
的命令行工具 virboxprotector_con
的默认路径位于:
Windows:C:\Program Files\senseshield\Virbox Protector 3\bin
Linux:/usr/share/virboxprotector/bin
macOS:/Applications/Virbox Protector 3.app/Contents/MacOS/bin
#
使用配置文件保护使用工具界面进行保护,在被保护的程序旁边会生成 .ssp
文件,然后调用virboxprotector_con
:
virboxprotector_con <input_file> -o <output_file>
virboxprotector_con
会自动查找 <input_file>.ssp 作为配置文件开始保护。
#
使用SDK标签保护使用界面工具虽然可以简单自由生成配置文件进行保护,但在开发过程中如果修改被保护的代码,配置文件就会失效。
见 SDK标签
#
命令行选项#
保护选项选项 | 命令行 | 默认选项 |
---|---|---|
导入表保护 | --imp-protect= | 0 |
内存校验 | --mem-check= | 1 |
压缩 | --pack= | 0 |
JIT 加密 | --jit-enc= | 1 |
导入表保护 | --imp-protect= | 0 |
字符串加密 | --str-enc= | 0 |
资源加密 | --res-enc= | 0 |
附加数据加密 | --overlay-enc= | 1 |
调试器检测 | --detect-dbg= | 0 |
名称混淆 | --rename= | exe 混淆所有名称2 ;dll 只混淆私有名称1 |
名称混淆保留规则 | --keep-rules= | "" |
#
函数选项选项 | 命令行 |
---|---|
忽略不支持的函数 | --ignore-unsupported=<value> (默认关闭:0) |
代码加密 | -e |
代码混淆 | -m |
代码虚拟化 | -v |
支持指定函数名称或规则保护,使用 ;
号隔开, 支持通配符 *
,举例:
-m "function1;function2" -v "function3;function4" -e "test*" --ignore-unsupported=1
建议使用SDK标签,更便于维护和自动化。
#
运行平台指定运行平台可以用 --platforms=
选项,举例如下:
指定Windows平台:
--platforms="windows-x86"
指定Windows和Linux 双平台:
--platforms="windows-x86;linux-x86"
#
命令行保护场景举例对主程序保护(混淆所有名称,开启反调试):
virboxprotector_con test.exe --pack=0 --jit-enc=1 --str-enc=1 --rename=2 --keep-rules="" -detect-dbg=1 -o protected/test.exe
对dll保护(混淆私有名称):
virboxprotector_con test.dll --pack=0 --jit-enc=1 --str-enc=1 --rename=1
对dll保护(保留自定义的名称):
virboxprotector_con test.dll --pack=0 --jit-enc=1 --str-enc=1 --rename=2 --keep-rules="MyNamespace.MyClass1.*;MyNamespace.MyClass2.*"
对需要在Linux平台dll保护:
virboxprotector_con test.dll --pack=0 --jit-enc=1 --detect-dbg=1 --str-enc=1 --rename=1 --platforms=windows-x86;linux-x86 -o protected/test.dll
注:根据自己的需求添加命令参数。