防破解问答集
当发现自己为之工作了好几个月,甚至几年的程序被人破解的时候,所受的伤害是难以形容的。
我作为一个共享软件作者,并不是因为我在乎那几块钱而已(我不想在这里做具体的计算,那会使我更加难过...),不,我总是使我的程序尽可能的便宜,使包括学生和自由软件作者在内的每个人都能用得起。
然而,我知道破解软件的魅力(如果你对程序破解者(Cracker)和黑客(Hacker)抱绝对的不宽容态度,那么请原谅。但是我的一位同学是心理学家,我们一直在寻找其原因)。破解一个受限制的软件程序就像解一个(有时是很狡猾)的谜语,而你可能沉迷于这种解迷的感觉(当我发现我祖母在几个月里整天沉迷在玩字谜游戏时想起这些的)。问题是(我们现在涉及到这种“游戏”的非法部分):程序破解者不会仅仅满足于只让自己了解他的“天赋”。他必须传播消息,发布他的“破解” (Cracker)(看一下破解包,多数是由以下部分组成:1、破解工具;2、简短的说明;3、一个巨大的文件,包含有似乎作者不亚于世界上任何伟人,或者被破解的程序像所有其它程序一样无法用那脆弱的保护方式来阻止破解者破解程序的进程的信息)。
但是现在,玩笑彻底结束了。发布这些破解包(让我们公正些:“研究其可能性“)给其他人,散布到网站上,新闻组,邮件列表,匿名FTP,光盘,"abonnements"以及任何其它地方,他们明显地破坏了所有那些花时间和精力在他们软件产品上的收益。虽然没有人可以说,所有收到或者下载破解程序的人永远也不会购买。但是散布破解程序的确是犯罪,就像有人在商场分发你车钥匙的拷贝——他是否以此赚钱又有何分别呢?
早些日子,我没有真正去花时间保护我的产品以防止被破解,但发现周围有它们的数个破解之后。我对自己说:为什么让它如此简单? 作为一个程序员,我当然知道,不,决不!——程序不可能是破解不了的,而且我知道,每个有吸引力的程序都迟早会有破解(至少是盗版或非法复制品)出现,但至少,我可以避免最糟糕的错误。
多数的高级语言程序员不再懂得汇编语言了,所以,他们使用的“保护措施“多数是很脆弱的。我对汇编懂得也不多,所以我决定开始仔细收集防破解(Anti-Crack)的技巧。我也努力“从另一方面努力学习”,你在这里看到的许多技巧我都是从典型的破解技术中捉摸出来的,包括网上各种各样的破解指南和阅读甚至是由专业破解者给出程序保护技巧(他们给我们这些技巧以给自己更大的挑战),我希望自己学得足够好,也想把我的经验在此与大家分享,有些技巧可能已经在其它文章中有所提及,但在此列出的最为完整。许多技巧都是针对Windows的,但他们可以被移植到其它操作系统中去。
这篇问答集是全新的,如果你觉得我遗漏了一些要点或者有用的、一个典型的Delphi程序员可以简单的加入其程序,以加强对程序保护的技巧,请告诉我,如果你允许,我会把它加到这里,否则我会告诉你我对它的体验。
不要问我问题,我可能无法回答:
1) 我已经提到,我对底层的东西没有什么研究;
2) 我不会把范例文件发给你,因为我没有准备过发布任何东西,如果我有一些,那么都在这里了;
3) 最后,我不会提供任何人我发现这些技巧的连接,请记注,这是一个专注于编程的站点,而非提供可利用的破解程序。需要更多的保护程序的信息,可以看我的DELPHI技巧页。
--------------------------------------------------------------------------------
最后,这里是:
如何使破解你程序的人感到头疼
(技巧未按其重要性排列)
--------------------------------------------------------------------------------
不要用有意义的过程名字,例如: function RegistrationOK: Boolean;//译者按:注册确认
无论你在函数中编码多么奥妙和复杂,无论你相信于否一个有经验的破解者都会在10-20秒内删除之。
作为一种选择,你可以把程序中需要的一部分代码置入其内,如果破解者禁止了这个函数,你的程序将产生错误的结果。
不要用有意义的文件名比如License.Dat.。
用类似Asymetric(美国软件公司,代表软件Toolbook)的加密. 仅仅不寻常的文件名常常是不够的,好的加密(编码)可以让破解者忙几个月(如果他愿意)
加上长时间延迟,当发现被自身被损坏时,不要发出警告,随后开始等待,也许是一或两天(破解者讨厌者东西)。
加入短暂延迟,当输入口令或做其它检测时,停一到两秒钟,使穷举无法继续。简单易行,但用处不大。
使用相互检查,在DLL和EXE中互相检查,这远谈不上安全,但可以使破解变得更困难。
在软件中使用自我修复,你知道就像纠错Modem和容错硬盘,这技术已经有许多年了,怎么没人用在保护软件上?这方法最大的优点是:如果破解者使用反编译工具,它会见到一串无用的列表。
修补你的软件!把代码变为每次调用不同的确认程序段,用“其人之道还治其人之身”。
把序列号放在不寻常的地方,比如数据库字段的属性中,经常听到或读到,“用一个DLL的名字,然后把它放到系统目录里”,听的太多了,不要用哦:)
把序列号放到不同的地方。
不要依靠系统时间,从一些文件取得时间,比如SYSTEM.DAT,SYSTEM.DA0和BOOTLOG.TXT,把它们和系统时间进行比较,要求它们比上次运行的时间晚(但是记住,许多用户近来在捉千年虫)。
不要用明文字符串告诉用户:"对不起,不过...(或类似的东西)"这些是首先寻找的目标,动态建立字符串或者加密它们。
用伪造的程序调用和字符串来灌水。
不要使用一个确认函数,每次你确认用户,把确认代码写在当前过程中。这样只是让破解者做更多的破解工作。
使用“保留字”,当使用硬密钥或口令时,使它们看来像程序代码或者函数调用 (比如., "73AF" 或 "GetWindowText")。这确实工作的很不错,可以迷惑一些反编译工具。
没有“禁止”特征,如果你的程序有不保存数据版本(Crapware Version),不要包含“灰”色菜单项。没有保存项等于没有保存,就那么简单。
避免不必要的提示信息,唯一提醒用户他尚未注册的只是“关于”对话盒,此对话盒要动态建立,以便保密。这有两个原因:许多程序员有这样的观点:多余的提示信息会在他们的顾客中产生敌人,这是愚蠢的。一个可能是更重要的原因是:多余提示信息会引导对你代码的反向工程而且经常直接引导至你的程序保护代码。
经常更新,频繁的更新指:经常更换代码,典型(简单)的破解只修改你的硬字节代码位置,这可能当它还未面市已经过期了。并且保证上载到公共服务器,使你可以更好的控制程序,人们无法找到破解所针对的旧版本。是的,这样无法防止包括旧版本和破解包盗版,如果他们那么做了,你至少可以为把他们的硬盘挤满而做点贡献。
最后,花点时间考虑保护你自己的软件。它是否值得保护?它是否更应该改进你的软件?如果没人用你的软件,保护也就没有意义了,不要过高估计你软件“对世界的重要性”。
--------------------------------------------------------------------------------
你可以考虑到的更多技巧
--------------------------------------------------------------------------------
用一个连续的几KB长的数学公式使任何想破解它的人精神崩溃。这使用一个密码生成器几乎无效——阻止穷举攻击也是有效的。
小心运行时刻库!当写BETA版时,完全使用之,在正式版中重写部分函数,这至少可以使破解者的生活更艰苦些。
毁坏结果,毁坏结果有时是保护程序的有效措施。
例如:想象一个图表程序,或者类似的程序,只是禁止打印然后根据一些注册码恢复打印是最常见的毁坏结果的程序。允许你打印。当产生要打印数据结构时,用某种方式破坏之,在打印前根据注册码或其它东西恢复。甚至,让破坏更奥妙,假设你有一个饼形图要打印,不改变任何东西,但加入一些不大的随机数值在你的数据中——这样就破坏了。图表看上去“不是很糟”,但是无论如何将无法使用(比如,如果是按20%的随机次序改变的)发现这样的保护,如果这和注册码相关联,无疑会使破解需要更对的时间,一是必须深入你内部的数据结构并找到可怕的破坏和恢复数据代码。
陷阱,一个我不太肯定,但听说有程序使用的方法:用CRC校验你的EXE文件,如果它被改变了,不要显示典型错误信息,等一天,然后用意思含糊的错误代码通知用户,当他们和你联系并告之错误代码时,你知道,是因为被破解了。记注:这样的陷阱可能被病毒或下载错误所触发,当你谴责可能是你未来的客户前,先考虑所有可能性。
不要依赖EXE压缩程序,几乎任何EXE压缩程序(Shrinker, WWPack32, NeoLite ——和所有著名的压缩软件)都有反压缩程序,所以,压缩程序的保护能力至少支持可配置的编码。以上(和其它)压缩程序的反压缩软件并未广泛流传,但是,不要依赖那些软件作为你程序仅有的一个“保护”。
--------------------------------------------------------------------------------
高级技巧——从汇编来的奇想
--------------------------------------------------------------------------------
rcr/rcl 把戏
如果RCR/RCL执行某值,对于破解者来说是痛苦的——在不知道最初操作时转送标志(Carry Flag)的值的情况下,你无法逆转之或否定其作用,如果转送标志是由一些其它烦琐的关键操作所产生的话,那么你几乎胜利了。
到处贴条件转移
条件跳转对于反向工程来说并不有趣。没有循环,只是跳转,作为有条件的路障,包括你那些可爱的KEY处理代码。这样,没有简单的反向操作可以执行。
使用部分代码作为奇妙的数字表。
(更适合评论部分)如果你像多数的破解者或喜欢用SOFT-ICE(一个流行的破解工具)改东西,你无法想象这会有多恼人,
和破解者斗智
这部分很有趣:-) 贴上连续的NOP,就像你在做自我代码修改(天啊,什么乱七八糟的,NOP?哈!自我代码修改!白痴会花三年去捉摸那些本来该是什么东西)。混乱的注释代码。把代码分成小块,把它们遍布在可执行代码中,在它们中间使用(最好是有条件的)跳转。
提早发现SoftIce.。
现在搞垮计算机,你可以搞垮奔腾或奔腾MMX计算机甚至不需要VXD,可以用操作码: F0 0F C7 C8 (非法的带锁定前缀的 cmpxchg8b 指令). 处此之外, 我们必须采取真正的措施:使用VXD把CPU带出保护模式。Windows 不喜欢那样,奇迹?另一方面,不要浪费太多的时间去写破坏反汇编程序或调试的代码。那没用,相信我,有人写了那些东西,另一些人立刻会有办法绕过它,所以,把你的兴趣转移到更重要的地方——那些容易且很快见效的措施,就像上面的技巧。
--------------------------------------------------------------------------------
针对DELPHI控件的破解
--------------------------------------------------------------------------------
让我们了解一些关于Borland公司新开发工具的内核吧。这些知识会使我们加快破解的速度,当然也使那些共享软件程序员更小心的使用Delphi,轻易的把他们的“秘密”暴露在那些好奇的目光下。
VCL是指Visual Component Library(可视化组件库),它近来被Borland的可视化编程工具所使用,比如Delphi和C++Builder。
这些环境使用特有的资源格式,在Resource Workshop(Borland编辑资源的一个工具)中显示为“RCDATA'”。这些资源中含有Delphi所谓的窗体(Forms),窗体是程序的窗口(Windows)。所有对窗口的设计的信息都包含内,当一个典型的Delphi程序开始运行时,其初始化代码建立这种窗体,并从资源中读取所需要的信息。有时,这种读取会被推迟——不常用的窗体在需要时才被建立和删除。这样的机制是Delphi的最大优势也是其最大的缺点。它大大提高了程序设计的速度,但是对于整个应用程序,它减慢了程序被调用时的速度
这部分中真正有趣的是:例程(被用来响应用户界面的窗体的元素)的地址是按名字邦定的。所以只要知道这些名字,我们就可以知道所需要的地址。
如果你曾经破解过我的Delphi程序,你一定对那些烦琐的库之间的调用,比如API调用断点(breakpoint)和类似“do xx”的代码。
[讨论一个十分有名的用Delphi写的应用程序] 就像你将见到的那样,我彻底的破解了它,而且十分轻松。我第一次安装以后一个星期后,我发现了一条令人厌恶的信息——“你的测试已经过期”。
首先要做的是使用资源或窗体探测(SPY)工具收集有关目标EXE文件的有关信息。你也许会想到去查看TVALIDATORDLG——很明显,用户名和注册码是由此输入的。但是你会发现那只是一个简单的对话盒,真正的工作是由其调用者TSPLASHFORM来完成的。这是个恼人的窗口,它在程序关闭、按“关于”按钮和程序开始部分不断出现。
你可以选择TsplashForm并且以文本格式观察之。关于按钮(Button)和标签(Label)的许多信息都将被很清楚的显示。让我们注意以下部分(接近最后):
object RegButton: TButton
Left = 200
Top = 176
Width = 97
Height = 25
Caption = 'Register'
TabOrder = 1
OnClick = RegButtonClick
end
这是什么?这是带有“注册”标题的按钮。你可以看到它的大小,位置......和一个有启发性的名字——“OnClick”。 “OnClick”告诉我们当用户按注册按钮时所调用的例程,当我们有了名字(Name),我们可以搜索例程的地址。这是因为例程是和按钮以名字(Name)邦定的。
使用一个十六进制编辑器观察"RegButtonClick",我发现了两次,第二个是资源本身,第一个是在地址表(Address Table)中的。
000A4990 ____ ____ ____ BC57 4A00 0E52 6567 4275 ______.WJ..RegBu
000A49A0 7474 6F6E 436C 6963 6B__ ____ ____ ____ ttonClick_______
现在,观察名字(Name)前那写神奇的数字,有一个字节('0E')指出了"RegButtonClick" 的长度(14 个字符)。而且,在此之前有其地址:004ABC57。
有的反汇编程序会以为文件太长,并且不能正确反汇编这部分——然而,使用特殊的工具,我们可以在此停止,对了,就停在我们按按钮的部分。
这些会使你发现一个调用(CALL)。跟踪之,你会在44ECC8发现一个“标准栈框架(standard stack frame)”:
0044ECC8 55 push ebp
0044ECC9 8BEC mov ebp, esp
这是一种程序员所写的,在高级例程开始部分所需要的东西。我们已经避开了一长串由Windows通知(Notification)所产生的VCL库调用,来到了正确的位置。
在此,你可以方便的用设置断点的方法来测试一些调用——你会发现它们是用来显示要求输入用户名和口令的对话盒。然后,注册码是由用户名和用户的输入比较所产生的。你可以输入你所选择的用户名,和任何东西作为注册码,在BPX到44ED69之后,一个调用一个例程用来比较两个字符串。D EDX会显示你输入(冒牌)的注册码,EAX会显示正确的注册码,简单吧?初学者只需10分钟就可以完成,
如何避免被类似的程序破解?
阅读我以上的技巧。最基本的是不要用由双击按钮或有属性监视器(Object inspector.)生成默认的方法,在程序的其它部分写代码,最好是在另外的模板中,然后用类似以下的代码与按钮关联:
RegButton.OnClick := RegButtonClick;
当然你需要在窗体建立时(被调用前)执行这段代码,最好是被一些无关的例程所调用,当然这远远不能防止你的程序被破解,但至少不会像你刚才看到的那样容易。
--------------------------------------------------------------------------------
关于注册码的提示
(如果你无法避免它)
--------------------------------------------------------------------------------
在安全,可行性,可编程性和最终用户的头痛间寻求平衡。
太长的,无字母的注册码可能造成连续的输入错误。考虑要求输入确认域(多数是口令),或者至少提供一个“不固定”的注册码输入域,以便用户可以每次重写注册码,也许最后正确的输入了。许多人会仅仅“瞥一眼”以比较输入的注册码和他们所收到E-Mail里的注册好,他们最终认为,他们输入了正确的注册码。但是字体太小或者他们懒得注意到底“I”和“1”被交换了位置(就像'l83jjd_0)pH1lTe' )。
根据不同用户的反馈,注册码输入区必须无限制的接受任何长度的信息。不要让破解者了解你的注册码的类型——如果你采取“在线确认(online-verification)”并显示它有10个字符长或只接受大写字母将给予他们帮助——不要这样做!
计算潜在用户的数量!没有比这样的事更坏的了:你把用户数限制在9,999,你不希望有第10,000个用户,因为如此你就必须升级你的注册码以满足这第1,000个用户。
如果你的注册码有10位,可能有10^10个注册码。但是,你的应用程序可能只允许10^4(10,000)个用户,你必须采取某种算法使10^4个用户每人一个得到10^10个注册码中一个。这保护了用户和你的应用程序本身受到穷举攻击(就像一个使用VXD的宏播放器那样)。如果只有10^4个用户,而你定义了10^9个合法的注册码,那么平均每试10次,就会有一个“合法”的注册码。然而,在预期只有10^4的情况下,平均每10^6次才会成功。即使是使用高速的计算机和极快的宏播放器(击键模拟输入注册码),要在10^6中寻找到需要的注册码所花费的时间也是无法计算的。
从用户名(User name)到注册码(Registeration code)不应该只有简单的运算,它必须是有既精通数学又精通汇编语言的人用汇编语言实现(记注,Delphi仍然允许你直接使用汇编(ASM)代码)!然后,检查你的操作,绘制流程图,了解它是如何工作的。要彻底了解你自己的工作,特别是它的不足之处。
要有创新意识,不要用任何看起来简单、迅速、有效的东西,除非你信奉类似爱因斯坦的相对论的学说。你的方法的确是简单,也的确是迅速,但是绝对不是有效,的确是容易被破解。我十分抱歉,我并非天才,没有找到一种行之有效的保护方案能维持太长时间。
只是些想法:)
Richey