.NET单机软件的保护有两大难点,分别是:
-
编译的结果不是机器码,而是IL代码,反编译非常容易。
-
不联网的软件没听说过不能破解的。
所以,我们的保护策略也不是说绝对的安全,只是能防多少是多少。
首先对于第一点,我们先看一个例子,以产生直观的感受。假设我们写了一个简单的软件,代码如下所示:
static void Main(string[] args)
{
if (!CheckVerify("my_password"))
{
return;
}
int result = Calculate();
Console.WriteLine("计算结果是:" + result);
}
//软件认证
static bool CheckVerify(string password)
{
return password == "secret";
}
//需要保护的核心代码
static int Calculate()
{
return 100;
}
编译成exe之后,我们使用ILSpy这款工具对其进行反编译,得到的结果如下图所示:
可以看到,这跟源码几乎没有区别!就算CheckVerify函数用了极其复杂的算法,我把它都注释掉,改成return true再重新编译,不就破解了吗?而且,要保护的算法让人一览无遗,还谈什么专利、什么研究结果。
为此,我们需要对软件进行一些代码保护。在保护之后,上述的程序在反编译之后,得到如下的结果:
可以看出,原来一个简单的类,变成了多个类,而且一个类里面有大量方法,每个方法不知所云。当然,既然程序能正常运行,说明代码的逻辑是通的,只要有恒心,迟早能看懂这些代码,不过就需要比较高的成本了。
对于源代码的保护,有以下的方法:
-
强命名:强命名是把源程序进行数字签名,当程序被改动之后,就会无法运行。例如我们在上述例子中的CheckVerify里插入一句return true(在IL代码层修改),程序就会报错,无法运行。当然,强命名也是很容易破解的,只要反编译,把签名信息去掉即可。
-
代码混淆:就是把代码里原来正儿八经的命名改成乱七八糟的字符,甚至是不可见的乱码。就比如把CheckVerify这个函数名,改成n7R4Xm3qfd03J,可读性差了一大截。当代码多了以后,要理清原代码的思路就会很困难。
-
控制流程混淆:打乱原代码中的流程,插入大量不相关的代码,降低代码可读性。例如我们要执行num=3这么简单的代码,如果改成:
a=4 if(a!=4) num=2 else num=3
这样,可读性就会差很多了。
-
字符串加密:在上面的CheckVerify函数里面,有一句if(password=="secret"),那谁都知道secret这个密码了。字符串加密会对代码里的字符串进行加密,在用的时候再进行解密。
-
防调试:当软件检测到用户在调试代码时,退出程序。检测的一种方法是检测时间,例如两句代码明明很近,但到达代码的时间用了1秒,那很可能是被调试了,可以退出程序。
-
加壳:加壳是把我们原来整个软件进行加密,然后在外面加一层壳,这个壳是另外一个程序,用于对原软件进行解密和运行。这样的话,原软件的代码只会出现在内存中,破解难度会增加。
源代码保护的方法还有很多,在此不赘述。如果想要自己对软件做上面的保护,那需要对PE文件、对IL代码有非常深刻的认识,不是一般人可以做的(我也不行)。一般我们使用一些工具辅助,我用过的软件有:
-
ConfuserEx:免费开源的,比较好,但几年前就没更新了,只支持.NET Framework。
-
.NET Reactor:收费,比较好,一直有更新。
-
Dotfuscator:收费,比较难用,中间还会出错。
-
Smart Assembly:收费,只有代码混淆,效果不好。