1、目录摘要2第一章 需求分析31.1 设计需求31.2 设计目的31.3 功能设计3第二章 概念设计42.1 序列号保护机制42.2 如何攻击序列号保护52.2.1 数据约束性52.2.2 利用消息断点5第三章 逻辑设计63.1 破解方案63.2 破解步骤63.2.1 检查文件63.2.2 反汇编代码7第四章 编程114.1 OllyDbg跟踪114.2 生成序列号15第五章 测试175.1 测试结果17第六章 总结18第七章 参考文献1920摘要注册机分为内部注册机和外部注册机二种。内部注册机在使用时需导入至原程序文件安装目录下,点击后运行,完成破解原程序文件的注册信息。外部注册机在使用时,输
2、入注册名,注册机输出所要的注册信息。本次课程设计通过跟踪程序的代码,了解其运行过程,分析序列号的生成算法,最终完成该软件外部注册机的设计。 关键字:注册机,破解,OllyDbg软件第一章 需求分析1.1 设计需求由于一些软件涉及版权问题,要完全使用的话需要注册,或者有试用期一说,或者只有注册之后才可以享受全功能。注册机则解决了这一问题,注册机根据其相应解决的软件而有不同的形式,大部分在拿到注册机之后,可以得到相应的一些注册码或者其他相应的一些东西。这些都是破解该软件所需的,只要按照步骤添写那些注册码,受保护的软件就可以高枕无忧的使用了。1.2 设计目的注册机其实就是一个专门用于分析修改软件内部
3、程序信息的专用软件工具,它本身也是一种程序,一种高级程序。能够编写出该程序的人,应该是计算机软件领域里的高手中的高手。注册机分为内部注册机和外部注册机二种,它们破解软件注册信息的过程不尽相同,但结果是一样的。内部注册机在使用时需导入原程序文件安装目录下,点击后自动运行该注册机,完成破解原程序文件的注册信息,破解成功后,该软件中的被限制功能解除,可以象使用其他正式版软件一样,使用其全部功能。外部注册机在使用时,不需导入原程序文件的安装目录下,可以存放在硬盘任何位置。与内部注册机不同的是,外部注册机需要原程序文件安装后进行注册时自动给出的申请码,把该申请码再输入外部注册机中,注册机自动算出所要破解
4、的原程序文件的注册信息,即注册序列号或注册码,至此外部注册机的破解任务完成。再把注册机算出的序列号填入原程序文件注册序列号栏中,即完成注册。1.3 功能设计分析软件的运行流程,掌握在该过程中易爆破或者得到正确序列号的方法设计出注册机,使用户输入任意注册名可以得到正确的序列号,也能通过该过程改进系统的保护功能。内部注册机的版本必须与所要破解的原文件版相一致,否则不能起到破解作用。外部注册机有些是可以通用的(通用版)。本论文通过研究简单情况下的注册机,为以后软件保护提供一个借鉴和思路。第二章 概念设计2.1 序列号保护机制数学算法一向都是密码加密的核心,但在一般的软件加密中,它似乎并不太为人们关心
5、,因为大多数时候软件加密本身实现的都是一种编程的技巧。但近几年来随着序列号加密程序的普及,数学算法在软件加密中的比重似乎是越来越大了。以下是网络上注册的基本方式和流程:当用户从网络上下载某个shareware共享软件后,一般都有使用时间上的限制,当过了共享软件的试用期后,必须到这个软件的公司去注册后方能继续使用。注册过程一般是用户把自己的私人信息(一般主要指名字)连同信用卡号码告诉给软件公司,软件公司会根据用户的信息计算出一个序列码,在用户得到这个序列码后,按照注册需要的步骤在软件中输入注册信息和注册码,其注册信息的合法性由软件验证通过后,软件就会取消掉本身的各种限制,这种加密实现起来比较简单
6、,不需要额外的成本,用户购买也非常方便,在互联网上的软件80%都是以这种方式来保护的。注意到软件验证序列号的合法性过程,其实就是验证用户名和序列号之间的换算关系是否正确的过程。其验证最基本的有两种,一种是按用户输入的姓名来生成注册码,再同用户输入的注册码比较,公式表示如下(F指某特定的函数,下文公式中出现的字符皆指函数):序列号 = F(用户名)但这种方法等于在用户软件中再现了软件公司生成注册码的过程,实际上是非常不安全的,不论其换算过程多么复杂,解密者只需把换算过程从程序中提取出来就可以编制一个通用的注册程序。另外一种是通过注册码来验证用户名的正确性,公式表示如下:用户名称 = F-1(序列
7、号)这其实是软件公司注册码计算过程的反算法,如果正向算法与反向算法不是对称算法的话,对于解密者来说,的确有些困难,但这种算法相当不好设计。于是有人考虑到一下的算法:F1(用户名称) = F2(序列号)F1、F2是两种完全不同的的算法,但用户名通过F1算法的计算出的特征字等于序列号通过F2算法计算出的特征字,这种算法在设计上比较简单,保密性相对以上两种算法也要好的多。如果能够把F1、F2算法设计成不可逆算法的话,保密性相当的好;可一旦解密者找到其中之一的反算法的话,这种算法就不安全了。一元算法的设计看来再如何努力也很难有太大的突破,那么二元呢?特定值 = F(用户名,序列号)这个算法看上去相当不
8、错,用户名称与序列号之间的关系不再那么清晰了,但同时也失去了用户名于序列号的一一对应关系,软件开发者必须自己维护用户名称与序列号之间的唯一性,但这似乎不是难以办到的事,建个数据库就好了。当然也可以根据这一思路把用户名称和序列号分为几个部分来构造多元的算法。特定值 = F(用户名1,用户名2,序列号1,序列号2)现有的序列号加密算法大多是软件开发者自行设计的,大部分相当简单。而且有些算法作者虽然下了很大的功夫,效果却往往得不到它所希望的结果。其实现在有很多现成的加密算法可以用,如RSADES、MD4、MD5,只不过这些算法是为了加密密文或密码用的,与序列号加密多少有些不同。2.2 如何攻击序列号
9、保护要找到序列号,或者修改掉判断序列号之后的跳转指令,最重要的是要利用各种工具定位判断序列号的代码段。这些常用的API包括GetDlgItemInt, GetDlgItemTextA, GetTabbedTextExtentA, GetWindowTextA, Hmemcpy (仅仅Windows 9x), lstrcmp, lstrlen, memcpy (限于NT/2000)。2.2.1 数据约束性这个概念是+ORC提出的,只限于用明文比较注册码的那种保护方式。在大多数序列号保护的程序中,那个真正的、正确的注册码或密码(Password)会于某个时刻出现在内存中,当然它出现的位置是不定的,
10、但多数情况下它会在一个范围之内,即存放用户输入序列号的内存地址0X90字节的地方。这是由于加密者所用工具内部的一个Windows数据传输的约束条件决定的。2.2.2 利用消息断点在处理字串方面可以利用消息断点,例如Windows中的消息中的WM_GETTEXT和WM_COMMAND。WM_GETTEXT用来读取某个控件中的文本,比如拷贝编辑窗口中的序列号到程序提供的一个缓冲区里;WM_COMMAND则是用来通知某个控件的父窗口的。第三章 逻辑设计3.1 破解方案由于汇编语言和机器语言是一对一的对应关系,较易分析程序。本篇论文仅涉及用反汇编程序入手,可以更容易地了解程序运行过程。针对本篇论文出现
11、的程序,可分为如下步骤:检查文件是否加壳、反汇编代码、分析算法和注册机设计。3.2 破解步骤本篇论文所用测试程序用:序列号 = F(用户名)的公式,明码比较,所以破解可以从查找正确序列号在内存中出现的地址入手。以下是在Windows XP平台下设计外部注册机的详细演示步骤。3.2.1 检查文件有些程序员为了保护软件不被破解,会使用加壳等方法。所以在开始反汇编软件代码前要先检查软件是否被加壳。在这里使用软件PEiD。PEiD是一款著名的查壳工具,其功能强大,几乎可以侦测出所有的壳,其数量已超过470种PE文档的加壳类型和签名。检测文件为标准测试用例的软件ncrackme。ncrackme是以序列
12、号方式保护软件的程序。检测后如图3.1:图3.1 PEiD检测ncrackme结果3.2.2 反汇编代码OllyDbg,一个新的动态追踪工具,己成为当今最为流行的调试解密工具了。同时还支持插件扩展功能,是目前最强大的调试工具。代码窗口显示被调试程序的代码。如图3.2:图3.2 代码窗口图3.2中代码窗口有四个列(从左到右):地址/Address(虚拟地址)、HEX数据/HEX dump(机器码)、反编汇/Diassassembly(汇编代码)、注释/Comment(注释)。在代码窗口(地址行,不是列标题)双击时完成动作:地址(Address)列:显示相对被双击地址的地址,再次双击返回标准地址模
13、式;HEX数据(HEX dump)列:设置或取消无条件断点,按F2键也能设置断点;反编汇(Diassassembly)列:调试编辑器,可直接修改汇编代码;注释(Comment)列:允许增加或编辑注释。代码窗口允许浏览、分析、搜索和修改代码,保存改变到可执行文件,设置断点等。相关弹出式菜单包括100多项。数据窗口以十六进制或内存方式显示文件在内容中的数据,如图3.3:图3.3 数据窗口寄存器窗口显示CPU各寄存器的值,支持浮点(FPU)、MMX,3DNow!寄存器,可以单击鼠标右键切换。如图3.4:图3.4 寄存器窗口下面介绍下反汇编过程,用OllyDbg调试器载入ncrackme,运行程序,随
14、意输入一个注册码和序列号得图3.5:从图3.5可看出,随机输入注册名和注册码后,ncrackme会弹出注册失败的对话框。猜测在注册成功或者失败时都会有提示窗口出现。Windows中经常使用MessageBoxA()或者MessageBoxW()函数(功能是显示一个消息对话框)。OllyDbg带有插件CommandBar,可以在如图3.6位置:图3.6 Command命令条输入bp MessageBoxA和bp MessageBoxW即可在MessageBoxA和MessageBoxW分别设置断点。运行程序,程序停在77D507EA,如图3.7:图3.7 程序停止地址在代码窗口中按F8键可以单步
15、运行程序,并跳过call调用。一直按F8直到77D50830,回到地址004010A1,如图3.8:图3.8 返回时的地址在图3.8中可以看到,注释栏中标明004010740040107F是注册成功信息,0040108F0040109B是注册失败信息。所以只要00401072的“jnz short 0040108F”不执行即可注册成功。将00401072改为nop,如图3.9:图3.9 更改后的程序汇编代码再次运行程序得到图3.10:图3.10 注册成功软件显示注册成功。因此可以证明上面的猜测是正确的。第四章 编程4.1 OllyDbg跟踪地址00401072的代码是否执行,要看texst e
16、xa,exa的结果。如果exa是0,则结果为0,jnz short 0040108F就不会执行,最终显示注册成功;如果exa不为0,则结果不为0,jnz short 0040108F执行,最终显示注册失败。所以在其上面的地址00401064的子程序调用会将结果输入exa。在这里猜测exa的值是真假序列号比较后的结果,则在地址00401064的子程序调用很可能有正确序列号的计算公式。OllyDbg在代码窗口中按F8键可以单步运行程序,并跟踪步入call调用的子程序代码。在00401064按F7步入,子程序代码如下(子程序代码中“/”表示循环的开始,“|”表示循环的中间过程,“”表示循环的最后一句
17、):00401230/$8b0dbc564000movecx,dwordptrds:4056bc00401236|.83ec30subesp,30 00401239|.8d442400leaeax,dwordptrss:esp0040123d|.53pushebx0040123e|.56pushesi0040123f|.8b3594404000mov esi,dwordptrds:;user32.getdlgitemtexta00401245|.6a10push1000401247|.50pusheax00401248|.68e8030000push3e8 0040124d|.51pushec
18、x 0040124e|.33dbxorebx,ebx00401250|.Ffd6callesi ;调用getdlgitemtexta()得到注册名00401252|.83f803cmpeax,3 00401255|.730bjnbshortncrackme.0040126200401257|.5epopesi00401258|.B801000000moveax,10040125d|.5bpopebx0040125e|.83c430addesp,3000401261|.C3retn ;注册名长度小于3返回00401262|a1bc564000moveax,dwordptrds:4056bc004
19、01267|.8d542428leaedx,dwordptrss:esp+280040126b|.6a10push100040126d|.52pushedx0040126e|.68e9030000push3e900401273|.50pusheax00401274|.Ffd6callesi;再调用getdlgitemtexta() 得到序列号00401276|.0fbe442408movsxeax,byteptrss:esp+8;把名字的第1位,放入eax0040127b|.0fbe4c2409movsxecx,byteptrss:esp+9;把名字的第2位,放入ecx00401280|.99
20、cdq;把eax扩展成edx:eax的64位长00401281|.F7f9idivecx;把edx:eax除以ecx,余数放在edx00401283|.8bcamovecx,edx;余数在ecx中00401285|.83c8fforeax,ffffffff;eax=0xffffffff00401288|.0fbe54240amovsxedx,byteptrss:esp+a;把名字的第3位放入edx0040128d|.0fafcaimulecx,edx 00401290|.41incecx00401291|.33d2xoredx,edx00401293|.F7f1divecx;以0xffffff
21、ff除以ecx00401295|.50pusheax00401296|.e8a5000000callncrackme.00401340;调用子程序,首地址为004013400040129b|.83c404addesp,4 0040129e|.33f6xoresi,esi004012a0|e8a5000000/callncrackme.0040134a;调用子程序,首地址为0040134a004012a5|.99|cdq 004012a6|.b91a000000|movecx,1a;ecx=1a h004012ab|.f7f9|idivecx;edx:eax除以1a h004012ad|.80c
22、241|adddl,41;余数加41 h004012b0|.88543418|movbyteptrss:esp+esi+18,dl004012b4|.46|incesi 004012b5|.83fe0f|cmpesi,0f004012b8|.72e6jbshortncrackme.004012a0004012ba|.57pushedi004012bb|.8d7c240cleaedi,dwordptrss:esp+c;ss:esp+c是注册名位置004012bf|.83c9fforecx,ffffffff004012c2|.33c0xoreax,eax004012c4|.33f6xoresi,e
23、si004012c6|.F2:aerepnescasbyteptres:edi004012c8|.F7d1notecx 004012ca|.49dececx004012cb|.7459jeshortncrackme.00401326;如果ecx是0,结束004012cd|8a44340c/moval,byteptrss:esp+esi+c;把注册名字第0位,放入al004012d1|.C0f805|saral,5 ;向右方bitshift5位004012d4|.0fbec0|movsxeax,al 004012d7|.8d1480|leaedx,dwordptrds:eax+eax*4;edx
24、=eax+(eax*4)004012da|.8d04d0|leaeax,dwordptrds:eax+edx*8;eax=eax+(edx*8)004012dd|.8d0440|leaeax,dwordptrds:eax+eax*2;eax=eax+(eax*2)004012e0|.85c0|testeax,eax004012e2|.7e0a|jleshortncrackme.004012ee004012e4|.8bf8|movedi,eax004012e6|e85f000000|/callncrackme.0040134a 004012eb|.4f|decedi004012ec|.75f8|
25、jnzshortncrackme.004012e6004012ee|e857000000|callncrackme.0040134a 004012f3|.99|cdq 004012f4|.B91a000000|movecx,1a004012f9|.8d7c240c|leaedi,dwordptrss:esp+c;把注册名字的首地址放在edi004012fd|.F7f9|idivecx;edx:eax除以ecx004012ff|.0fbe4c342c|movsxecx,byteptrss:esp+esi+2c00401304|.80c241|adddl,41;除数的余值加4100401307|.
26、0fbec2|movsxeax,dl;把dl放入eax0040130a|.2bc1|subeax,ecx; 真假序列号相减0040130c|.8854341c|movbyteptrss:esp+esi+1c,dl00401310|.99|cdq00401311|.33c2|xoreax,edx 00401313|.83c9ff|orecx,ffffffff00401316|.2bc2|subeax,edx 00401318|.03d8|addebx,eax0040131a|.33c0|xoreax,eax0040131c|.46|incesi0040131d|.F2:ae|repnescasb
27、yteptres:edi0040131f|.F7d1|notecx00401321|.49|dececx;注册名字长度00401322|.3bf1|cmpesi,ecx00401324|.72a7jbshortncrackme.004012cd00401326|5fpopedi 00401327|.8bc3moveax,ebx 00401329|.5epopesi0040132a|.5bpopebx0040132b|.83c430addesp,300040132e.c3retn子程序结束,返回00401340/$8b442404moveax,dwordptrss:esp+4;00401296调
28、用开始地址00401344|.a3ac504000movdwordptrds:4050ac,eax;把值传给ds:4050ac00401349.c3retn;调用结束地址0040134a/$a1ac504000moveax,dwordptrds:4050ac;004012a0调用开始地址0040134f|.69c0fd430300imuleax,eax,343fd00401355|.05c39e2600addeax,269ec30040135a|.A3ac504000movdwordptrds:4050ac,eax;把结果放回ds:4050ac0040135f|.C1f810sareax,10
29、 ;右移10h位00401362|.25ff7f0000andeax,7fff00401367.c3retn;调用结束地址分析上述代码:0040134000401349将eax数值在4050ac处;0040134a00401367先进行一系列运算,再把结果保存在eax,4050ac也被重新赋值,记为函数call(long &num)。0040127600401293可变化成运算:eax = 0xffffffff/(1+(name0%name1*name2)004012a0004012b8可变化成for循环函数。004012cd00401324为一个循环函数,其中又嵌套了004012e60040
30、12ec的循环。4.2 生成序列号接下来,用VC+来实现序列号生成的代码如下:#include using namespace std;long call(long &num)long rCall = num;rCall *= 0x343fd;rCall += 0x269ec3;num = rCall;rCall = 0x10;rCall &= 0x7fff;return rCall;void main()int sp = 0;int rCall = 0;int esi = 0;int eax, edx;long num = 0;char name20;char series_num20;me
31、mset(series_num, 0, 20);coutPlease input the user name: name;num = 0xffffffff/(1+(name0%name1)*name2);for(int i = 0; i 15 ; i+)call(num);while(esi 5);edx = eax + (eax * 4);eax = eax + (edx * 8);eax = eax + (eax * 2) ;while(eax 0)rCall = call(num);eax-;rCall = call(num);series_numsp+ = (rCall % 0x1a)
32、+0x41;esi+;coutThe series numbers are:endl;for(int j = 0; j strlen(series_num) ; j+) coutseries_numj;coutendl;第五章 测试5.1 测试结果运行程序,将结果输入软件,点击注册按钮,出现如图5.1所示的对话框:由图5.1得到注册成功提示,将结果输入软件,点击注册按钮,出现如图5.2所示的对话框:图5.2 再次提示注册成功由图得到注册成功提示。2次输入的注册名和得到的序列号都能成功,由此可以证明算法正确。第六章 总结通过上述分析,ncrackme要求先输入注册名,然后根据注册名算出序列号,而
33、注册名没有改变。因此推断出ncrackme的序列号生成采用:序列号 = F(用户名)的公式,用明码比较,这让破解者更容易爆破或者找到序列号生成函数的位置。要想更好的保护软件,可以从以下几点入手:1、序列号生成公式采用非明码比较的公式,尽可能减小数据约束性。也可以用其他加密算法(例如:MD5、CRC32)加强序列号保护。2、反汇编技术在破解软件时有广阔的应用,本篇论文亦采用反汇编技术破解注册软件,所以可以用anti-debug阻止调试工具进行反汇编操作,加强软件序列号的保护。3、可用加壳工具对软件加壳。壳的种类有加密壳和压缩壳,都能很好地起到保护序列号的功能,使破解者用反汇编工具时无法得到正确的程序代码。