本帖最后由 痛恨一生 于 2015-6-13 23:29 编辑
附录3代码与数据结合技术《于破J过招,保护你的共享软件》
共享软件是软件业目前世界上比较热门的话题,国内更是如此。成千上万的中国程序员以极大的热情投入到这个领域来,都憧憬着用辛勤的劳动来获得丰厚的回报;但,实际并非如此,绝大多数的人都弑羽而归。值得注意的是:除了选题和技术上的原因外,最大的原因就是共享软件被破J(Crack)了。
破J见得多了,不免有些麻木。大部分作者都是新软件发布一个星期左右甚至一天之内就会在网上发现注册机或者被修改过的软件(行话称之为“爆破”)。破J者制作了英文、中文、俄文、德文等语种的注册机大肆发散不说,还常常给作者寄一份,外加一封挖苦辱骂的信。唉!我们得罪了谁?没日没夜地熬夜编码,难道得到的就是这连绵的挖苦和不尽的羞辱吗?
不!决不!我们有理由也有能力保护自己的劳动成果!但问题是:如何保护?关注国内,网上关于破J资料和教程俯拾皆是,而关于软件保护方面的资料则是凤毛麟角(大多都关系到什么技术垄断),这种畸形现状就导致了相当一部分朋友的加密非常脆弱甚至可以称得上是“弱智”!要知道,你要面对的是已经形成团伙的众多破J高手呀,国内的什么CCG、BCG,国外的eGis、King、Core、TNT、DAMN和TMG,皆为水平一流的破J组织。全球盗版软件不少于80%都是由他们的破J的,技术实力连大软件公司都不可小视。
看到这里,你是否已经已经灰心了?别怕,虽然我们理论上无法完全避免被破J,但如果能够有效地拖延被破J的时间,并充分打击破J者的自信心,是可以让破J者无法忍受这种折磨从而最终放弃的。
破J,通常的做法有两种——暴力破J(爆破)和写注册机。下面我就来依次讲解每种破J方法的原理和应对方法,这些都是鄙人积累的一些共享软件保护经验,某些关键地方还有例程讲解(Delphi代码,使用C++和VB的朋友可以自己稍微修改一下),希望能对新手们有些帮助,能够更有效地保护自己的劳动成果。
§暴力破J(爆破)
这是最常见,也是最简单的破J的方法。该法最适合于对付没有CRC效验的软件,破J新手乐于采用。
大凡共享软件,验证是否注册大多数要采用if条件语句来进行判断,即使你采用了什么RSA或ECC等强力加密算法,也免不了使用if条件语句。呵呵,这里就是共享软件最为危险的地方哦,当然也是爆破手孜孜不倦所寻求的目标呀!
例如,你的注册子程序易语言代码类似如下:
.如果 (签名验证 (到字节集 (编辑框_硬件号.内容), 编辑框_注册码.内容, 公匙, 公共模数) =真)
信息框 (“注册成功!”, 0, )
.否则
信息框 (“注册失败!”, 0, )
.如果结束
这个注册函数即使使用了强劲的RSA算法进行注册码验证,可是依然很容易被破J,我们只要把这里修改为:
.如果 (签名验证 (到字节集 (编辑框_硬件号.内容), 编辑框_注册码.内容, 公匙, 公共模数) =假)
或
.如果 (取反(签名验证 (到字节集 (编辑框_硬件号.内容), 编辑框_注册码.内容, 公匙, 公共模数) ) =真))
就可以破J成功了,这时戏剧性的结果会产生:随便输入任何注册码都可以注册通过,相反输入正确的注册码却无法通过注册。其具体操作是先反汇编或者跟踪你的程序,找到判断注册码的cmp、test等汇编指令后的关键跳转指令处,通常是je、jz之类的汇编指令,把它们修改为jne或jnz即可,这样常常只需要修改一个字节就可以完美破J之。
令人遗憾的是,目前大部分共享软件都是这样进行判断的,这也是为什么网上被破J的软件铺天盖地的主要原因。因为这样破J实在是太简单了……
难道没有什么可以防止的方法吗?当然有啊!只要把软件的关键代码嵌入到注册码或者注册文件中就可以充分防止破J。但现在问题是,怎么嵌入呢?
最简单的方法就是把关键代码(你的软件功能限制部分最关键而且最简单的一个函数)做成一个小Dll(动态链接库),用强力对称算法加密(密匙可以是主程序某一固定不变的部分或壳的特征值)后生成一个注册文件(License文件,呵呵,格式只有你知道哦!),或者Base64编码后生成一个注册表文件,用户可以双击导入注册表内。
当然,你可以保护得更好一些:可以不采用临时Dll,而把解密后的关键代码用WriteProcessMemory这个API函数写入到主可执行文件自己进程被提交(Committed)的内存页面的指定位置去。这样由于磁盘上没有解密后的临时文件,破J更加困难。事实上,目前世界上最强劲的专业保护软件Armadillo就是用的这种方法。而且这种方法可以充分防止被调试器Dump。但实现起来比较困难,尤其是在WinNT 5以后的操作系统中。
//你的加密算法,使用了Blowfish(对称算法)和MD5(Hash算法)
由于这种方法将注册文件和受限制代码唯一关联,爆破手拿到你的软件也只有干瞪眼。建议大家都给共享软件加上功能限制,这样比时间和次数限制更加安全。
§写注册机
顾名思义,这种方法就是模仿你的注册码生成算法或者逆向注册码验证算法而写出来的和你一模一样的注册机。这玩意威胁极大,被爆破了还可以升级。如果被写出注册机,呵呵,你的软件只好免费了。或者你必须更换算法,但以前注册过的合法用户都得被迫更换注册码了,累死你!呵呵……
上面的方法虽然可以避免爆破,但注册机的威胁还是存在的。Cracker要写注册机必须详细研究你软件的验证模块,这必须先将你的软件脱壳,再反汇编或者用调试器跟踪。市面上许多加壳和保护软件都吹嘘不可能被脱壳,令人可惜的是到目前为止没有一个软件兑现了它们的诺言。由于CPU最终执行的都是有效指令,所以等你的程序自解压完成后再从内存中Dump出来就可以实现脱壳。因此不要在壳上面花很多功夫,因为没有这个必要。
反汇编是和调试器跟踪也都是不可能防止的,因为所有的Win32程序都是必须通过API来调用Windows系统中的关键Dll的(如Kernel32.dll、GDI32.dll等),然而API是可以Hook的。我们只能从自己的代码着手来保护我们的劳动果实了。
为了自己调试和以后维护的方便,我们一般采用有意义的名字给我们的函数命名,可这给了Cracker可乘之机。例如这样的函数是什么意思大家应该是一目了然吧?IsRegistered(), IsLicensed(), LicenseVerify(), CheckReg()...这样Cracker就可以轻松地从数千个函数中找到他的目标---你的注册码效验函数!而且破JDelphi编写的软件还有一件TMG小组的破J利器---DeDe,它可以轻松看到你软件里的Form、Unit和函数名,还可以反汇编一部分代码,更是可以和Win32DASM合作反汇编更多的代码,对Delphi软件威胁极大。
为了不给Cracker创造温馨舒适的破J环境,我们要混乱(Obfuscate)我们的代码,将软件中所有的函数名全部替换成随机生成的函数名。例如Func_3dfsa_fs32zlfv()这个函数是什么意思?恐怕只有天知道了。网上有现成的代码混乱器,你按你使用的编程语言的种类可以找到一些。但注意,只有当你要发布软件时才使用之,而且一定注意备份源代码。否则当你看不懂你自己的代码时可别怪我呀!
另外一定要使用公开密匙算法保护你的软件,RSA、DSA和El Gamal之类的算法都可以从网上找到。但注意:将你算法单元中的所有涉及到算法名称的字符串全部改名。避免被Cracker发现你用的算法而模仿写出注册机来!你还可以张冠李戴,明明用的DSA,将名字全部替换成RSA,呵呵,让他模仿去吧!
其它算法如对称算法和Hash算法都也要注意改名,否则这样:
EncryptedCode = Blowfish(MD5(UserName), MD5(Key));
//你的加密算法,使用了Blowfish(对称算法)和MD5(Hash算法)虽然我不了解Blowfish和MD5算法的原理,也不会逆向它们,但我了解你的效验算法的流程和算法名,我马上就可以从网上找到类似的Blowfish和MD5算法包,从而模拟你的软件仿造出注册机,啊?!真是……$&*&($#%@! 如果你用的什么其它不常见的算法(如Skipjack(NASA美国航天局标准算法), LOKI,3-WAY, Safer之类不出名但强度很高的算法),并且全部改名,就让他们去研究软件中成堆的如下代码是什么加密算法吧!:) 0167:005B9F70 MOV EAX,[EBP-10] 0167:005B9F73 CALL 00404000 0167:005B9F78 PUSH EAX 0167:005B9F79 MOV EAX,[EBP-10] 0167:005B9F7C CALL 004041C4 0167:005B9F81 LEA ECX,[EBP-14] 0167:005B9F84 POP EDX 0167:005B9F85 CALL 004B860C 当然,最好把Hash算法也全部改名,给会给他们制造更多的困难。但注意,MD5和SHA之类的Hash的初始值会被Cracker从内存中找到,这样他就知道了你用的Hash了。所有建议同时使用MD5的变形算法Ripe-MD(RMD)128或160和其它的Hash,如Tiger, Haval等算法。 另外,请注意要经常效验你的程序是否被修改(Hash效验),如果被修改则退出。但请注意,有些病毒会修改进程的句柄表和它指向的内核对象,这样病毒就可以直接修改运行中的PE文件而感染之了,另外还有网络传输错误的问题也会导致软件CRC出错。因此请不要认为可执行文件的CRC不符而此时程序已被脱壳了。 其实,程序被脱壳最明显的标志是其大小明显大于脱壳前。1M的PE文件被UPX、ASPack之类的软件压缩后通常只有400左右。如果你的软件在运行中发现自己的大小大于800K,我想你应该知道如何做了吧?呵呵...
|