这是我在做CodePlus软件的注册模块时遇到的实际问题,现在还没有很好的解决。
.Net程序很容易被反编译出来,也可以通过混淆器来进行一些掩盖,也可以通过强名称来保证不被修改。这真是一个矛与盾的问题。
那么到底怎么最大限度的保证软件不被修改、注册器不容易被编写。可能真是一个头痛的问题。
一般的思路大致是:
一、在注册机制上采用非对称加密结合数字签名的方法(这个过程较为复杂,也是我目前没有弄出来的地方,不过我会努力解决这个问题的,也希望得到高手们的指点)
二、一定要强名称。关于如何使用强名称,这个比较容易:一般的步骤是:
1、在vs.net提供的dos命令窗口里 用 sn.exe -k 来生成一个钥匙对。并将之放在项目根目录下。 如c:\>sn -k c:\YourPrj.snk 然后将YourPrj.snk copy到你的project目录下。
2、修改AssemblyInfo.cs,
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("..\\..\\YourPrj.snk")]
[assembly: AssemblyKeyName("..\\..\\YourPrj.snk")]
3、rebuild it.这个时候就已经被强名称保护了。任何对生成的exe或dll进行的修改(无论是IL,或者二进制),都会导致程序将不再运行。
三、通过混淆器对生成的exe或dll进行混淆。推荐采用http://www.remotesoft.com/salamander/obfuscator/download.html 可以对强名称保护下的exe或dll进行混淆再重签名。也就是说,可以得到混淆和强名称的双重保护。当然,这里也就会提出一个问题。就是既然remotesoft的obfuscator可以对强名称保护下的文件进行修改(混淆就是大修改了)再重签到强名称,虽然它要求YourPrj.snk要保留在project的根目录下。但我想进行一下替换,应该也是有可能的。因此,从另一角度来思考,这二种方式其实到最后,对于高手来说,都不会是什么难以对付的问题。其保护功能也就很有限了。
再说关于非对称加密进行注册验证机制,由于对rsa算法的使用还不是太明白,所以一直没有测试成功。昨天在索克论坛上看到一则东西:
http://www.sorke.com/bbs/Announce/Announce.asp?BoardID=100&ID=6060
里面讲到XC#的验证代码是公开的。(这下子明白了,混淆和强名称直接都不用了。)但是我在想,要破解xc#的那位朋友,为什么不把这个验证代码去掉,然后再编译呢?不解了。或者xc#的程序是已经生成好了的,虽然提供部分源代码,也只是给你看看,而不是让你有重新编译的机会吧。那么,这个里面强名称肯定是用到了的。由于没有下下来的研究,不是太明白。先把那段有用的代码copy过来先,这是xc#进行验证的算法:
const string RsaKey = "
+owfTAAdjKzayIEjnTu1W2XMiDSfWfPcDaEpnoG3cWY1BhpTsUz8XxapVSHpRYovaaeA/1SY
fb0h7xbku1M4M9LgGdUwlab+iMc=
static bool IsValidPair(string name, string key, string rsaKey)
{
try
{
byte[] b = System.Convert.FromBase64String(key);
using (RSA rsa = new RSACryptoServiceProvider())
{
rsa.FromXmlString(rsaKey);
RSAPKCS1SignatureDeformatter f = new RSAPKCS1SignatureDeformatter(rsa);
f.SetHashAlgorithm("SHA1");
return (f.VerifySignature(new SHA1Managed().ComputeHash(System.Text.ASCIIEncoding.ASCII.GetBytes(name)), b));
}
}
catch { return false; }
}
再引用那位强人对这段代码的分析:
我仔细检查代码后才发现,检查的代码关键是RSAPKCS1SignatureDeformatter类,它是System.Security.Cryptography名称空间中的一个类,专门负责验证RSA加密签名的。
RSA就是不对称加密的算法。就是通过私钥加密的只能通过公钥解密。上面的静态变量RsaKey明显就是公钥。就是说必须提供他通过带有私钥的Key生成器生成的验证码,才可以使Hash验证通过。就是说,没有任何办法通过公钥获得验证码。
那么生成注册码的办法没有用,是否可以通过修改IL代码的方式破解呢?只要把返回值直接改成true,那么无论是不是验证通过都可以使用了。经过检查,这个办法还是不行。
因为这个dll程序不是由XC#主程序自己使用,而是要被vs.net调用的,所以只能注册为全局程序集。我发现验证逻辑实现的程序集XHCS.VisualStudio.dll已经被安装到了GAC中。
大家都知道,安装到GAC的所有程序集必须要加入强名称验证。如果我修改了dll的内容,那么这个强名称必须被删除,这个程序集也没有办法在GAC中了,导致vs.net无法使用。
好了,现在我想到的是,基于这个代码,是否可以用在我们自己的系统里面,那么,在加密签名这块要怎么来写,尝试用:
RSACryptoServiceProvider rsp = new RSACryptoServiceProvider();
rsp.FromXmlString(skey); //这里读进了公钥和私钥
bytes=enc.GetBytes(this.rtxtUserGiveSN.Text);
bytes = rsp.SignData(bytes,"sha1");
这样的代码来做,但失败了,还没有找到原因。说是给对象设置了空值。
再想想,如果有谁对这方面有经验,希望指导一下。
评论
# re: 如何建立有效的.Net软件注册保护机制
修改了代码,clr对strong name的检查会导致assembly不能载入,要解决这个问题,你提到可以去掉strong name。进一步考虑,能去掉strong name,当然也能加上cracker自己生成的strong name。所以,不会有什么不能加入gac的事情发生。
# re: 如何建立有效的.Net软件注册保护机制
是的,但是在Strong Name 和混淆二个同时保护之下,能够成功破解的人毕竟不是太多,而对于真正的高手,这二个保护的作用只是增加了破解的难度和工作量而已,但真正的高手未必会有兴趣做这种事啊:)现在不明白或者是没有搞定的是,怎么让客户端只有公钥,而能对用私钥生成的注册码进行校验。还在探索中...
# re: 如何建立有效的.Net软件注册保护机制
就以普通的方法加入强名称来说,可以直接反编译成IL之后,然后修改IL,再用sn工具生成一个强名称文件,用它进行编译。如果是混淆过的,在逻辑上,也许会让人觉得有些难度,但实际上,无论你如何混淆,代码中,始终会留下处理的方式,只要需跟踪程序的流程,很快就可以知道哪些是让你进行注册的代码。
因为你既然让用户注册,总会提供一个UI,那么使用了Form或Control是肯定的了,从这些地方入手,找到你的注册性的代码的地方,并不难。
只要先反编译,然后再对混淆后的代码进行分析,修改后,用自己的强名称文件重新编译,就可以达到破解的目的。
破解的入口点很多,无论你采用日期还是采用什么,肯定会留下气味。
正如你所说,采用RSA的加密方式,也很难解决问题,因为有心破解者,完全可以彻底把这段代码删除掉。
如果要进行有效的加密,最好结合native代码,这样起码可以在第一步防止代码被完全反编译。目前是否有有效的方式对付native代码,我并不清楚,但是,据我的实践而言,使用了native代码的软件,对破解上造成较大的困难。
最后,如果是加密的核心部分,不应该直接使用.net的语言来编写,这样很容易被反编译,只要有你的加密算法,破解其实也并不是一件难事。
以上三种方法,最多也只是增加破解的难度,对于同时精通IL和汇编的人来说,我觉得好像并没有什么有效的措施。
# re: 如何建立有效的.Net软件注册保护机制
使用native代码其实也一样有一个致命伤,因为有一个用Managed Code到Native Code的接口处,只要把这个接口给换了,native代码就一点点用也没有了。我和一个朋友以前闲来无事,把Syncfusion的那套控件给彻底破了(可惜在项目中没敢用:( ),syncfusion虽然没有采用native的方式,但在思路上和native很像了。他通过一个gac中的dll进行注册控制,在控件中只调用了那个dll里面的一个方法,我们做了一个同名的,又有相同方法,但直接返回true的dll,替换了他,就直接搞定了。hoho.现在的问题是,有没有人对不对称加密算法在注册机制中的使用有经验的啊,说说哈。
# re: 如何建立有效的.Net软件注册保护机制
从贴出的XC#的static bool IsValidPair(string name, string key, string rsaKey)看,算法直截了当,几乎就是把msdn中RSAPKCS1SignatureDeformatter.VerifySignature的sample抄了一遍想要自己写keygen根据任意name算serial number仅有一个难点那就是已知n = 0xA998F899BAF608E3415BE00108175D4920D37CA152CC341CF4B96D6775F397552BAC4D22C2B81043F35836BDE1EFAD3F26C05BE5E4D5FA8C1F4C001D8CACDAC881239D3BB55B65CC88349F59F3DC0DA1299E81B7716635061A53B14CFC5F16A95521E9458A2F69A780FF54987DBD21EF16E4BB533833D2E019D53095A6FE88C7(此例)算d
呵呵,1024bits的rsa,算d大约需要1012(mips·years),就是1012mips的cpu要算一年,目前算d是作keygen的唯一途径
楼上某仁兄提到“只要有你的加密算法,破解其实也并不是一件难事。”,此言差矣,rsa就是公开算法的,但是如同此例所示,1024bits-rsa还是不易破的。
试举rsa注册机制一例:
1、算出e、d、n,三者关系可查阅rsa算法,不再赘述。
2、release的软件:
验证f(name) ^ e mod n == g(serial number)
f、g函数自定义,比如常用的sha1、base64(楼主文中提到的例子,f为sha1,g为base64)
3、软件作者自留的注册机:
计算g(serial number) = f(name) ^ d mod n
这样的保护已过于简单,可以分布式计算破d
# re: 如何建立有效的.Net软件注册保护机制
写得快了,写错一点,需要更正一下,抱歉抱歉更正如下:
2、release的软件:
验证g(serial number) ^ e mod n == f(name)
f、g函数自定义,比如常用的sha1、base64(楼主文中提到的例子,f为sha1,g为base64)

浙公网安备 33010602011771号