无思

本BLOG只用于个人资料收藏,内容如非注明,均为转贴资料,无意侵犯版权,特此声明!

  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 :: 管理 ::

这是我在做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  = "qZj4mbr2CONBW+ABCBddSSDTfKFSzDQc9LltZ3Xzl1UrrE0iwrgQQ/NYNr3h760/JsBb5eTV
+owfTAAdjKzayIEjnTu1W2XMiDSfWfPcDaEpnoG3cWY1BhpTsUz8XxapVSHpRYovaaeA/1SY
fb0h7xbku1M4M9LgGdUwlab+iMc=AQAB";

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的事情发生。
2004-08-25 18:14 | rIPPER

# re: 如何建立有效的.Net软件注册保护机制   

是的,但是在Strong Name 和混淆二个同时保护之下,能够成功破解的人毕竟不是太多,而对于真正的高手,这二个保护的作用只是增加了破解的难度和工作量而已,但真正的高手未必会有兴趣做这种事啊:)

现在不明白或者是没有搞定的是,怎么让客户端只有公钥,而能对用私钥生成的注册码进行校验。还在探索中...
2004-08-25 18:38 | Wintle

# re: 如何建立有效的.Net软件注册保护机制   

就以普通的方法加入强名称来说,可以直接反编译成IL之后,然后修改IL,再用sn工具生成一个强名称文件,用它进行编译。

如果是混淆过的,在逻辑上,也许会让人觉得有些难度,但实际上,无论你如何混淆,代码中,始终会留下处理的方式,只要需跟踪程序的流程,很快就可以知道哪些是让你进行注册的代码。
因为你既然让用户注册,总会提供一个UI,那么使用了Form或Control是肯定的了,从这些地方入手,找到你的注册性的代码的地方,并不难。

只要先反编译,然后再对混淆后的代码进行分析,修改后,用自己的强名称文件重新编译,就可以达到破解的目的。

破解的入口点很多,无论你采用日期还是采用什么,肯定会留下气味。

正如你所说,采用RSA的加密方式,也很难解决问题,因为有心破解者,完全可以彻底把这段代码删除掉。

如果要进行有效的加密,最好结合native代码,这样起码可以在第一步防止代码被完全反编译。目前是否有有效的方式对付native代码,我并不清楚,但是,据我的实践而言,使用了native代码的软件,对破解上造成较大的困难。

最后,如果是加密的核心部分,不应该直接使用.net的语言来编写,这样很容易被反编译,只要有你的加密算法,破解其实也并不是一件难事。

以上三种方法,最多也只是增加破解的难度,对于同时精通IL和汇编的人来说,我觉得好像并没有什么有效的措施。
2004-08-25 23:24 | 寒枫天伤

# re: 如何建立有效的.Net软件注册保护机制   

使用native代码其实也一样有一个致命伤,因为有一个用Managed Code到Native Code的接口处,只要把这个接口给换了,native代码就一点点用也没有了。我和一个朋友以前闲来无事,把Syncfusion的那套控件给彻底破了(可惜在项目中没敢用:( ),syncfusion虽然没有采用native的方式,但在思路上和native很像了。他通过一个gac中的dll进行注册控制,在控件中只调用了那个dll里面的一个方法,我们做了一个同名的,又有相同方法,但直接返回true的dll,替换了他,就直接搞定了。hoho.

现在的问题是,有没有人对不对称加密算法在注册机制中的使用有经验的啊,说说哈。

2004-08-25 23:38 | Wintle

# 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
2004-08-26 02:01 | ydaye

# re: 如何建立有效的.Net软件注册保护机制   

写得快了,写错一点,需要更正一下,抱歉抱歉

更正如下:
2、release的软件:
验证g(serial number) ^ e mod n == f(name)
f、g函数自定义,比如常用的sha1、base64(楼主文中提到的例子,f为sha1,g为base64)
2004-08-26 02:23 | ydaye

# re: 如何建立有效的.Net软件注册保护机制   

混合Native的意思被误解了。实际上混合Native的意思就是用VC++写一个混合了Native代码和Managed代码的东西,这个东西比如说是一个exe,并且你强命名了,那么就没有办法通过Reflector之类的东西进行完整的反编译——Native的部分是没有办法反编译出来的。这样的话,简单的反编译——修改代码——更换签名密钥——再编译的破解就会被阻止在第一步。比如说你有办法修改mscorlib吗?反正我是没有办法,因为里面有一些Native的代码存在。
2004-08-26 08:20 | sumtec

# re: 如何建立有效的.Net软件注册保护机制   

这个办法不错 ;)
2004-08-26 08:54 | rIPPER

# re: 如何建立有效的.Net软件注册保护机制   

能否把ms控件破解了。 
 
posted on 2004-12-22 18:00  kavenmo  阅读(1360)  评论(0)    收藏  举报