zoukankan      html  css  js  c++  java
  • [转]软件保护之注册算法篇

    原文地址:http://blog.csdn.net/fangle6688/article/details/1023699

    软件保护之注册算法篇
    fangle 2003

    一、前言

    通常地,软件保护可分为三大环节:反调试、抗分析、防爆破

    1、反调试,给你的软件引入非常规的运行机制,例如加壳,来限制调试工具对你的软件的掌控
       能力。如今调试工具越来越强大,但调试工具的设计目标毕竟是光明正大的,大多不会专门
       防备反调试技术。所以反调试技术还是相当重要的:它能够大大减少你的潜在对手。毕竟,
       如果调试工具能够很轻易地支配你的软件,则任何一个程序员都可能成为cracker,而普通程
       序员的队伍何其庞大?所以,反调试是软件保护的第一道防线。它已基本实现标准化:加壳
       工具已经满天飞了。

    2、抗分析,假如你不幸遇到对win32应用环境有足够了解的对手,以至于你的软件最终还是被凶
       悍的调试器任意蹂躏,你也还远没有被打败,你还有第二道防线可守——抗分析。在这道防
       线里,你有很多办法可以限制cracker掌握你的注册算法,从而阻止注册机的出现:注册机的
       出现意味着你被彻底打败,所以第二道防线是你必须固守的。

    3、防爆破,如果你的第二道防线足够坚固,往往会导致你的对手放弃注册,他们会试图直接修
       改你的可执行文件,强行改变它的执行流程,比如让它不注册也能正常运行。所以你还需要
       部署第三道防线,教训这些爆破者。防范爆破的手段很多,而且事实上你的防爆能力与你的
       第二道防线的架构直接相关,所以总体来说,第二道防线还是最重要的。

    本文针对的是第二道防线,目的在于阻止cracker掌握你的注册算法。

    注册算法的目的是只向合法用户提供完整的功能,通常采用注册码验证的方式实现:

    1、用户向软件作者提交用户码U,申请注册。
    2、软件作者计算出注册码R=f(U),回复给合法用户。
    3、用户在软件注册界面输入U和R。
    4、软件验证F(U,R)=0来判定用户的合法性。

    其中一些常用术语说明如下:

    1、用户码U:用于区别用户身份。它可能仅仅是用户自定义的一个用户名,这种形式的用户码与
       用户身份弱相关,假如一对合法的用户码、注册码被公开,则任何人都可以用来对软件进行
       注册;它也可能是用户机器的硬件特征码,这种形式的用户码与用户身份强相关,可以有效
       防止一次注册,多人享用的局面出现,但对于合法用户而言很不方便,一旦更换或升级机器,
       就必须重新申清注册码。

    2、注册码R:用于验证用户身份。它可能与用户码具有惟一对应关系,也可能同一个用户码有若
       干个注册码相对应,也可能同一个注册码有若干个用户码相对应。如果用户码和注册码的取
       值空间非常大,即使用户码与注册码不是惟一对应关系,非法用户“碰巧”得到一对合法的
       用户码、注册码的概率也是微乎其微。

    3、注册机:我们把R=f(U)中的小f称为注册机,掌握了注册机就有能力针对任何用户码计算出
       相应的注册码。

    4、验证机:我们把F(U,R)中的大F称为验证机,软件使用验证机验证注册码的合法性,即,
       当且仅当R=f(U)成立时,F(U,R)=0。

    在软件注册保护的“初级阶段”,验证机与注册机没有本质区别,即: F(U,R)=f(U)-R。所以
    cracker甚至可以直接让你的软件担当注册机的作用,根本不需要关心算法过程,也就是所谓的
    “内存注册机”。

    改进的做法是先求出f的反函数f',使:U=f'(R),然后令F(U,R)=f'(R)-U。这样做安全了许多,
    软件本身不包含注册机f,破解者必须在充分了解f'算法过程的墓础上才能正确推导出注册机f。

    更进一步的做法是构造f、f',使得通过f'求f在计算上不可行,或构造F,使F与f、f'都不直接
    相关。下面我们将一起来研究如何运用“堡垒战术”、“游击战术”和“陷阱战术”三大战术来
    实现以上手段。

    二、堡垒战术

    事实上,在通信领域人们很早就开始了身份验证的研究,并发展出了一系列优秀的密码学算法,
    其中MD5算法和RSA算法非常适合在软件注册算法中运用。

    MD5散列算法的特点是:

    1、任意长度的信息都可变换成一个固定长度(128位)的“信息摘要”。
    2、不同信息造成相同信息摘要的情况称为“冲突”,冲突肯定存在,但鲜有实例发现。
    3、由信息摘要无法逆推信息。

    MD5算法并不适合直接被用来做注册机,假如使用R=MD5(U)做注册机,由于MD5不存在反函数,验
    证机将不得不包含注册机。但是我们用以下办法使用MD5算法:

    1、设注册机:R=f(U)
    2、设MD5(a)=b
    3、令验证机为:F(U,R)=MD5(f'(R)-U+a)-b

    例如:U=f'(R)=R*5+19,a=7,则F(U,R)=MD5(R*5+26-U),a和f'融为一体。由于MD5算法不可逆,
    破解者无法通过b求出a,就无法求出f',更无法获得注册机。

    实际上利用MD5算法还有很多方法可以构造F,让破解者根本看不出F和f、f'的关系,相信读者完
    全能够进行精彩的发挥。当然,M D5算法也有它的缺陷,正因为MD5不可逆,所以a必须为常数,
    一旦破解者获得了一对合法的U、R,他们就可以跟踪到合法的a、b值,从而获得f',并进一步推
    导出注朋机f,但至少,他必须先获得一对合法的U、R。

    RSA非对称算法的原理是:

    1、选择两个互不相同的素数p、q,令n=p*q ,m=(p-1)*(q-1)
    2、选择素数e<n
    3、计算d满足e^d mod m=1
    4、对于任意A<n,若B=A^e mod n,则A=B^d mod n
    5、由e推算d的前提是因式分解n得到p、q,当n巨大,例如1024位时,分解n属于世界级数学难题。

    RSA 算法很容易在软件保护中进行应用:

    1、使用R=U^d mod n作为注册机
    2、使用U=R^e mod n作为验证机

    破解者即使跟踪软件运行的全过程,也得不到d,无法写出注册机。基本上使用RSA算法进行保护
    的软件可以说是建立了一座坚不可摧的堡垒,但是我们都知道马其顿防线的故事,由于RSA算法众
    所周知,再加上RSA算法实现起来并不容易,使用者通常会采用公共函数库,所以使用RSA算法也
    存在一些风险:

    1、RSA算法本身虽然足够坚固,但使用者往往全盘采用第三方代码。这些代码可能含有漏洞,而
       全世界有大量的资深破解者在研究这些代码的漏洞,一旦发现漏洞,软件的安全性就可能成
       为陪葬品,因为每一种公开的固定代码都必然包含一些特征串,通过搜索特征串可以轻松获
       知哪些软件使用了含有缺陷的代码。

    2、RSA算法的使用者往往并不了解算法细节,可能因错误使用RSA而在不知不觉之中遭遇非常规
       手段的攻击,例如在不同的软件作品中使用不同的e、d但使用相同的n而遭到“公共模组攻击”,
       或在电子邮件等通信加密领域使用e、d、n而遭到“选择密文攻击”等等。

    3、RSA算法源于通信领域,用于防止窃听者在获取B、e、n后推算出d从而使用A=B^d mod n获取
       A,但实际在解密端通常并不使用A=B^d mod n解密,而会使用“中国剩余定理”加速,而中
       国剩余定理中包含对p、q的引用。因此用于通信领域的RSA函数库并不适合软件注册算法,一
       旦软件作者选择了错误的RSA库函数,在验证函数中使用了中国剩余定理,则会导致RSA 防线
       如同虚设。

    虽然对软件实施RSA保护存在一定的风险,但是毕竟RSA算法非常复杂,可以乐观地估计,RSA足
    以让90%以上的破解者面对晦涩的汇编代码好一阵子找不到北。事实上密码学知识在软件保护上
    大有用武之地,各位如果潜心加以研究,一定还会发现“堡垒”其实还有很多种建造方法。

    三、游击战术

    游击战第一宗旨:化整为零。对付强大的对手非常有效。而软件保护中的游击战术就是将验证函
    数F“肢解”成多个互不相同的Fi ,然后将这些 Fi 尽可能藏到隐蔽的程序角落。通过任意一个
    Fi的验证都只是注册码合法的必要条件,而非充分条件,真正合法的注册码能够通过所有的Fi的
    验证。破解者找到Fi其中的任一个或任意几个,只要不能将所有的Fi一网打尽,他就无法一睹F的
    全貌,无法得到注册机。当然,将F分解成一系列必要非充分的Fi也不是一件容易的事情,需要较
    专业的数学知识,但是我们至少可以使用分段函数来简单地实现这一目标:

    1、将R切分成多段Ri
    2、构造不同的f算法使得:Ri=fi(U)
    3、令Fi=fi反函数fi'

    这样做虽然有点麻烦,但绝对是值得的。例如我们可以让F1使用MD5算法,F2使用RSA算法,F3使
    用自定义不知名算法,在用户输人注册码后仅仅使用F1进行验证,如果验证通过(不妨假定破解
    者总是有办法让它通过的)就恭喜注册成功。另外两个验证函数藏起来,只有使用者执行特定的
    操作时才被调用,例如在用户进行存档操作或使用某些高级功能的时候将注册码读出来再次验证,
    笔者甚至碰到过一个软件在软件被关闭时响应窗口Destory消息来调用一个验证函数。一旦任一个
    验证函数发现注册码非法,就将软件恢复为未注册状态,甚至可以更极端地选择“自杀”。

    游击战第二宗旨:虚虚实实。对于破解者来说,遇到游击战术会非常被动,除非他找到的验证函
    数已经能够将U 、R形成一对一的对应关系,否则永远不能确定软件中是否还埋藏着其他的验证函
    数,而事实上软件作者根本没有必要让U、R形成一对一的对应关系,验证函数个数的不确定性的
    确很容易让试图制作注册机的破解者懊恼不已。假如运用一点简单的线性代数的知识,我们可以
    将Ri的其中几个(注意只是其中几个,而不是全部)和 Fi关联起来:例如设:

    R1=3U
    R2=5U
    R3=7U

    则:

    F1=7R1+11R2+5R3-111U
    F2=11R1+7R2+3R3-89U
    F3=5R1+3R2+11R3-107U

    这样破解者找到F1、F2、F3中的任意一个,甚至无法求出R哪怕是小小的一段。一个更好的主意是
    让参与到线性方程组中的Ri的个数稍稍大于使用线性方程的验证函数的个数,软件作者手里持有线
    性方程的某一组特定解作为注册机,而破解者则无法了解验证函数到底有几个,就像鬼子永远搞不
    清楚八路军到底有多少,以至于最后歇斯底里地见人就认为是“八路的千活”。

    如果将一对U、R作为纵横坐标,看作平面上的一点,将注册机f看作由合法U、R连成的一段平面曲
    线,我们还可以构造多个空间曲面方程作为验证函数F,条件是f落在这些空间曲面之上,如果稍稍
    了解空间解析几何的知识,相信各位可以构造出无数个曲面方程作为验证函数,甚至还可以考虑使
    用参数方程,这样即使破解者获得了所有的Fi,也要有精深的解几功力才能求出f。

    我们必须反复强调数学知识的重要性,不管是数论、代数、线代、几何、解几,还是微积分(个人
    认为利用博立叶变换作验证函数将会非常有趣)、概率论,都可以拿来作为软件保护的武器。请相
    信,如果你其备了3成的数学功力,就足以将6成功力以下的对手折磨致死,因为你和他掌握着不对
    称的信息量。

    游击战第三宗旨:战略转移。游击队也经常会被鬼子扫荡,因为游击队的行踪经常会被汉奸告密。
    我们的游击战术的致命弱点在于,每一个验证函数都必须访问注册码,而注册码的源头只有一个:
    用户从注册界面输人的那一个。破解者会跟踪程序从注册界面读人注册码的过程,并监控存放注册
    码的内存地址,一旦验证函数访问这一地址就会泄漏行踪,这样注册码买际上成为了破解者寻找验
    证函数的一把钥匙。理论上他只要牢牢地抓着这把钥匙不放,就一定会找到所有的验证函数。应对
    的办法就是大规模的转移,游击战嘛,就是要能“跑”,将鬼子拖死、累死,软件必须不停的将注
    册码“搬家”,搬家的方法要多样化:

    1、内存拷贝,这种常规做法容易被破解者的内存监控断点识破。
    2、写入注册表或文件,然后在另一处代码中再读入到另一个内存地址,这种办法会被破解者的注册
       表、文件监视工具识破。
    3、一次将注册码拷贝到多个地址,让破解者无法确定哪一个地址是注册码的新家,当然如果敌人坚
       忍不拔,个个都追,至少也可以消耗敌人大量的精力。
    4、分批偷运,一次偷运一个字节,敌人难以察觉。
    5、在反复使用同一个函数搬家后突然使用另一个前半部分代码相同而后半部分不同的函数进行搬家。
       这种方法很容易让疲惫的敌人一不小心就将注册码跟丢。
    6、将以上方法反复使用,如果仅仅靠copy & paste就可以将对手逼成神经病,何乐而不为呢?

    事实上主动权永远掌握在你的手里,可能您会一不小心遇上一个精力过人孜孜不倦神勇无比的对手最
    终将您的游击队一一肃清,但是请相信,只要您认真贯彻了游击战术的三大宗旨,灵活结合使用,一
    定会让90%以上的破解者像无头苍蝇一样乱转一气之后恼羞成怒关机投降。

    四、陷阱战术

    所谓“陷阱”,是要让破解者误人歧途,陷入困惑之中无法自拔,我们不应该一味试图编制让破解者
    读不懂的代码,那不是一件容易的事情,而且抱着这样的目标很容易犯自以为是、低估对手的错误。
    我们的任务最好是引诱对手犯自以为是的错误,让他在并没有完全弄懂验证过程时却自以为清楚了。
    当然,将要介绍的“陷阱”,只是俺个人的一些研究思路,或许只能博高手一笑,但希望至少能够对
    各位有所启发,起到抛砖引玉的作用。

    第一个陷阱俺称之为“随机陷阱”,原理是谁备多个验证函数,程序每次运行时仅仅随机调用其中的
    某一个。其中随机数的产生可以放在程序头,由于应用程序通常会有大量的数据初始化工作,再加上
    随机数产生函数代码本身也比较晦涩难懂,夹杂在其中生成一个随机数应该是很隐蔽的。这样破解者
    在跟踪程序之后通常会自以为程序只有一个验证函数,于是放心地发布了注册机,而其他人使用该注
    册机进行注册时,由于程序生成了不同的随机数,调用了不同的验证函数,注册自然失败。不幸的是
    您无法判断您的对手的功力和习惯,有些功力一般的菜鸟(像俺这样的)或者是某些有特殊习惯的高
    手往往会将您的程序跟踪好几遍.甚至几十遍,您的随机陷阱就会被发现。所以笔者强烈建议您不妨
    将其中一个验证函数被随机调用的概率控制在5%以下。这样做的坏处是会出现一些并不完整的注册机,
    却能够让您的软件在注册状态下运行多次。好处是根据心理学家的研究,人类总是对自己关系越深却
    又无法控制的东西最感兴趣,一旦某人千方百计找来一个不完整的注册机注册了您的软件、他会认为
    “哈哈,现在这东西是我的了!”那种感觉当然会很好,他和您的软件的关系在心理上的亲密程度一
    般绝不会比他掏腰包买来的软件差。而一旦哪天他突然发现您的软件识破了他的非法身份,他会很失
    落,那感觉肯定会很不好,极有可能他会忍不住将“自己的东西”拿回来的冲动,哪怕是掏腰包。这
    样的话,恭喜您获得了一个额外的注册用户。您还会获得附带的收获:您的对手,破解者的声誉受到
    了打击,事实上众多的破解高手并不靠破解生活,他们在乎的其实就是所谓的声誉,您的这一次打击
    很可能会让他灰心丧气,失去了继续破解的兴趣,这样的话,恭喜您除掉了一个难缠的敌人。

    第二个陷阱俺称之为“整数陷阱”,由于通常验证函数都将用户码与注册码转换成整数进行运算,我
    们可以利用破解者的这一经验作一些手脚。例如我们将用户码拆分成两个整数U1、U2,注册码也拆
    分成两个整数R1、R2,注册机为:

    R1=U1+115
    R2=U2+351

    验证机为:

    F1:((R1-U1)^2+(R2-U2)^2)^(1/2)=369
    F2:(R2-U2)/(R1-U1)=3

    我们将 F1暴露,将F2隐藏。这样破解者找到F1后首先会发现验证过程包含了U、R的完整信息,而且F1
    显然是一个勾股方程,查表可得其唯一整数解为:

    R1-Ul=81
    R2-U2=360

    很容易自以为是的认为注册机为:

    R1=U1+81
    R2=U2+360

    然而偏偏我们使用的不是F1的整数解,却在计算机的计算下恰好满足F1。其实可以将U看作平面坐标系
    上的一个点,满足F1的是以U为圆心,369为半径的圆;满足F2的是经过U且斜率为3的直线; F1和F2的
    交点才是合法的R。我们所学习的数学函数绝大多数都是连续函数,而计算机处理的却是离散值,所以
    这一类的陷阱很容易构造,但是破解者要识破,就不见得有那么容易了。

    五、结语

    结合以上三大战术,请相信,你完全有能力阻止注册机的传播。

  • 相关阅读:
    常见sql注入的防范总结
    Hadoop各个组件与端口
    Jenkins HA高可用参考
    zookeeper的主要应用
    Jenkins常见REST API(便于将Jenkins集成到其他系统)
    使用pscp/pslurp批量并发分发/回收文件
    kv数据库对比总结
    /usr/bin/curl: Argument list too long的解决方法
    优秀的开源监控系统梳理
    Linux socat轻松实现TCP/UDP端口转发
  • 原文地址:https://www.cnblogs.com/xwgli/p/8311021.html
Copyright © 2011-2022 走看看