破解实战-第十战(完结)
本帖最后由 我是用户 于 2013-6-23 13:46 编辑【软件名称】: LanHelper算法分析与注册机的编写
【作者邮箱】: 2714608453@qq.com
【下载地址】: 见附件
【加壳方式】: 无
【使用工具】: OD
【操作平台】: XP SP2
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
前言:
不知不觉一个系列就完成了,前后断断续续的快一个月,这几天一直在找适合做最后一战的软件,闲来无事翻了翻以前的代码,发现以前在黑X发过的一篇文章的存稿,好吧就是它了.
今天给大家带来的是LanHelper(局域网助手)的算法分析和注册机的编写。想必这个软件大家都用过,都干过不少坏事吧,嘿嘿~~!但是这个软件有30天的试用期,超过时间就会提示注册。网上关于这个程序的破解方法有很多,但大多数是爆破的,很少涉及到算法分析的,今天就让我们来分析一下这个程序的算法。
LanHelper这个软件的算法有以下三个特点:1。注册码分段验证;2。采用了大量的浮点计算和数学运算;3。使用SEH异常处理来代替关键跳,更加隐匿。
1.定位关键代码
拿到程序,第一件要做的事是什么?当然是查壳喽。把程序载入Peid.
如图片1
从Peid上我们知道,程序是Borland Delphi 6.0 - 7.0写的。打开程序,提示“您的30天试用期已到期“。点击“输入注册号码”,在编辑框内输入试练码“hzycy“,“123456789”,注册,弹出错误提示.
如图片2
把程序载入OD,字符串搜索“您输入的注册码有误”,很遗憾的是OD并没有找到这个字符串。但是别灰心,这个程序是Borland Delphi 6.0 - 7.0写的。我们把它载入DeDe,等待程序转储分析完毕,点击DeDe中的窗体选项,在模块名中找到TForm_Reg.
见图片3
右边显示出了窗口的信息,我们可以看到TButton有很多个,经过一些偿试,我们准确定位出关键代码004DCB40。在OD上下好CC断点。开始注册。
2.算法分析与注册机的编写
输入试练码,点击确定,程序成功断下来,这说明我们定位的关键代码是正确的。很多程序都会在验证注册码时核实用户名和假码是否为空,当然这个程序也不例外.
见代码1。
004DCB6D .E8 5616FAFF call0047E1C8 ;获得用户名
004DCB72 .8B45 B8 mov eax, dword ptr
004DCB75 .8D55 BC lea edx, dword ptr
004DCB78 .E8 47CCF2FF call004097C4
004DCB7D .837D BC 00 cmp dword ptr , 0
004DCB81 .74 22 je short 004DCBA5 ;判断用户名是否为空
004DCB83 .8D55 B0 lea edx, dword ptr
004DCB86 .8B45 FC mov eax, dword ptr
004DCB89 .8B80 04030000 mov eax, dword ptr
004DCB8F .E8 3416FAFF call0047E1C8 ;取假码
004DCB94 .8B45 B0 mov eax, dword ptr
004DCB97 .8D55 B4 lea edx, dword ptr
004DCB9A .E8 25CCF2FF call004097C4
004DCB9F .837D B4 00 cmp dword ptr , 0
004DCBA3 .75 44 jnz short 004DCBE9 ;判断假码是否为空
0047E1C8这个Call是用来取用用户名和假码的,004097C4是用来判断用户名和假码是否为空的。如果不为空,则进入真正的算法Call 004DF3B4中,这里面才是对我们有真正价值的地方。
因为这个程序大量运用了00430F74这个Call进行验证,所以分析算法之前,我们先来分析一下这个Call,为了方便,我们把这个Call记为Call1.
见代码2。
00430F74/$55 push ebp
................................................ ;省略代码为检测参数1,2是否为0
00430FC7|.EB 5A jmp short 00431023
00430FC9|>DB6D 08 fld tbyte ptr ;装入参数1到st(0)
00430FCC|.E8 4B1CFDFF call 00402C1C ;判断参数1的小数部分是否为0
00430FD1|.D81D 2C104300 fcomp dword ptr ;比较是否为0
00430FD7|.DFE0 fstsw ax
00430FD9|.9E sahf
00430FDA|.75 30 jnz short 0043100C ;通过参数1判断流程
00430FDC|.DB6D 08 fld tbyte ptr ;流程2
00430FDF|.D9E1 fabs
00430FE1|.DB2D 30104300 fld tbyte ptr
00430FE7|.DED9 fcompp
00430FE9|.DFE0 fstsw ax
00430FEB|.9E sahf
00430FEC|.72 1E jb short 0043100C
00430FEE|.66:8B45 1C mov ax, word ptr
00430FF2|.50 push eax
00430FF3|.FF75 18 push dword ptr
00430FF6|.FF75 14 push dword ptr
00430FF9|.DB6D 08 fld tbyte ptr
00430FFC|.E8 4B1CFDFF call 00402C4C
00431001|.E8 2AFFFFFF call 00430F30
00431006|.DB7D F0 fstp tbyte ptr
00431009|.9B wait
0043100A|.EB 17 jmp short 00431023
0043100C|>DB6D 14 fld tbyte ptr ;参数2(流程1)
0043100F|.D9ED fldln2 ;将Loge2装st(0)
00431011|.D9C9 fxch st(1) ;st(0)与st(1)交换
00431013|.D9F1 fyl2x ;计算以2为基数的对数
00431015|.DB6D 08 fld tbyte ptr ;将参数1装入st(0)
00431018|.DEC9 fmulp st(1), st ;st(1)与st(0)相乘
0043101A|.E8 D51BFDFF call 00402BF4 ;进入
0043101F|.DB7D F0 fstp tbyte ptr
00431022|.9B wait
00431023|>DB6D F0 fld tbyte ptr ;计算的结果装入st(0)
00431026|.8BE5 mov esp, ebp
00431028|.5D pop ebp
00431029\.C2 1800 retn 18
Call1有两个参数,参数1为浮点数,参数2由fstptbyte ptr 装入。通过代码2我们可知,Call1里有两个验证流程,分别记为流程1,流程2。并通过参数1的小数部分是否为0来判断流程。小数部分不为0则跳到流程1执行,为0刚跳到流程2。这里我们先分析流程1,进入流程1处的004028F4中.
见代码3。
00402BF4/$D9EA fldl2e ;将log2e装入st(0)
00402BF6|.DEC9 fmulp st(1), st ;st(1)与st(0)相乘
00402BF8|.D9C0 fld st ;装入st到st(0)
00402BFA|.D9FC frndint ;取整(四舍五入)
00402BFC|.DCE9 fsub st(1), st ;st(1)-st(0)
00402BFE|.D9C9 fxch st(1) ;st(0)与st(1)交换
00402C00|.D9F0 f2xm1 ;2的st(0)次幂-1
00402C02|.D9E8 fld1 ;将1装入(st)
00402C04|.DEC1 faddp st(1), st ;st(1)加上st(0)相加
00402C06|.D9FD fscale ;2的st(0)次方
00402C08|.DDD9 fstp st(1)
00402C0A\.C3 retn
可见Call1运用了大量的浮点运算和数学运算,是不是看的我们眼花呢?为了大家能更加直观的理解这个Call1的作用,我把它翻译成了c++代码(不包括流程2).
见图片4。
相信这样,大家就能看的懂了吧。因为决定流程2的参数是固定的,所以我们在遇见时再做具体分析。
好了,准备工作做完了,现在该进入正题了。
为了更好的说明这个软件的验证过程,我将对注册码进行分段的分析。
第一段(前6位)
见代码4。
004DF479.B2 01 movdl, 1 ;dl=1
004DF47B.BE 01000000 movesi, 1 ;esi=1
004DF480.B8 142C5B00 moveax, 005B2C14;005B2C14保存的是前6位的注册码
004DF485>8B4D F0 movecx, dword ptr ;假码给ecx
004DF488.0FB64C31 FF movzxecx, byte ptr ;依次取假码的前6位
004DF48D.3B08 cmp ecx, dword ptr ;与dword ptr 的值比较
004DF48F.74 02 je short 004DF493 ;相等就跳
004DF491.33D2 xor edx, edx ;如果不等edx=0
004DF493>46 inc esi ;esi+1
004DF494.83C0 04 add eax, 4 ;eax+4(取下一位)
004DF497.83FE 07 cmp esi, 7 ;比较是否取完
004DF49A.^ 75 E9 jnz short 004DF485
004DF49C.84D2 test dl, dl ;判断dl是否为0
004DF49E.0F84 F8030000 je 004DF89C ;跳向失败
由代码4可知,注册码的前6位是固定不变的。程序依次对假码的前六位进行验证,只要有一位与注册码的前6位不符,dl就会为0,导致程序验证失败。我们在数据窗口中按下CTRL+G,输入005B2C14.
见图片5。
可知注册码的前6位是LH4A8N。我们重启程序,输入LH4A8N123456,继续分析。
第二段(单独判断第9位)
见代码5。
004DF510 .8B45 F8 mov eax, dword ptr ;eax为假码的地址
004DF513 .8A58 08 mov bl, byte ptr ;bl为假码第九位
004DF516 .33C0 xor eax, eax ;eax=0
004DF518 .8AC3 mov al, bl ;al=bl
004DF51A .8945 A0 mov dword ptr , eax
004DF51D .DB45 A0 fild dword ptr ;装入整数到st(0)(假码第九位)
004DF520 .83C4 F4 add esp, -0C
004DF523 .DB3C24 fstp tbyte ptr ;参数2
004DF526 .9B wait
004DF527 .68 FA3F0000 push 3FFA
004DF52C .68 39052FA7 push A72F0539
004DF531 .68 C1CB2978 push 7829CBC1 ;参数1
004DF536 .E8 391AF5FF call 00430F74 ;关键Call1(重点分析)
004DF53B .DD5D E8 fstp qword ptr ;运算后的结果存入
004DF53E .9B wait
004DF53F .DB2D 08F94D00 fld tbyte ptr ;装入实数到st(0)
004DF545 .DC6D E8 fsubr qword ptr ;与运算后的结果相减
004DF548 .D9E1 fabs ;求绝对值
004DF54A .83C4 F4 add esp, -0C
004DF54D .DB3C24 fstp tbyte ptr ;参数2
004DF550 .9B wait
004DF551 .68 00400000 push 4000
004DF556 .68 F1290080 push 800029F1
004DF55B .68 D2C6116B push 6B11C6D2 ;参数1
004DF560 .E8 0F1AF5FF call 00430F74
004DF565 .DB2D 14F94D00 fld tbyte ptr ;装入实数到st(0)
004DF56B .DED9 fcompp ;CMP来比较2个无符号数
004DF56D .DFE0 fstsw ax
004DF56F .9E sahf
004DF570 .0F82 26030000 jb 004DF89C ;小于就跳向失败
通过代码5可知,程序通过两个Call1来验证注册码的第九位。第一个Call1的参数1为假码的第九位,参数2为0.0408163265306122432。因为参数2的小数部分不为0,所以跳到流程1处执行,计算后的结果记为A9。第二个Call1的参数1为A9,参数2为2.0000100000000000000,小数部分不为0,跳向流程1,计算后的结果记为A90。然后A90与tbyte ptr(也就是6.8999999999999989760e-07)比较,如果小于,就跳向失败。知道了这个,我们就可以用穷举法来找出第九位。我们通过查ASCII码可知,第九位的取值范围是33到126。我们用一个while循环来对在这个范围的值依次进行验证,如果满足条件,则跳出提示。
具体代码见图片6。
运行这段代码,弹出提示.
见图片7。
可见第九位为一定值“#”,将假码改为LH4A8N12#456,重启程序。
第三段:(判断第7,8,10位)
见代码6。
004DF4AC .8B45 F8 mov eax, dword ptr ;eax为假码的地址
004DF4AF .8A58 06 mov bl, byte ptr ;bl为假码第七位
004DF4B2 .33C0 xor eax, eax ;eax=0
004DF4B4 .8AC3 mov al, bl ;al=bl
004DF4B6 .8945 A0 mov dword ptr , eax
004DF4B9 .DB45 A0 fild dword ptr
004DF4BC .83C4 F4 add esp, -0C
004DF4BF .DB3C24 fstp tbyte ptr ;参数2为假码第七位
004DF4C2 .9B wait
004DF4C3 .68 FE3F0000 push 3FFE
004DF4C8 .68 BD529691 push 919652BD
004DF4CD .68 3411363C push 3C361134 ;参数1
004DF4D2 .E8 9D1AF5FF call 00430F74
004DF4D7 .DC45 E0 fadd qword ptr ;计算后的结果保存在
004DF4DA .DD5D E0 fstp qword ptr ;存入
004DF4DD .9B wait
004DF4DE .8B45 F8 mov eax, dword ptr ;eax为假码的地址
004DF4E1 .8A58 07 mov bl, byte ptr ;bl为假码第八位
004DF4E4 .33C0 xor eax, eax ;eax=0
004DF4E6 .8AC3 mov al, bl ;al=bl
004DF4E8 .8945 A0 mov dword ptr , eax
004DF4EB .DB45 A0 fild dword ptr
004DF4EE .83C4 F4 add esp, -0C
004DF4F1 .DB3C24 fstp tbyte ptr ;参数2为假码第八位
004DF4F4 .9B wait
004DF4F5 .68 FD3F0000 push 3FFD
004DF4FA .68 2F4CA6AA push AAA64C2F
004DF4FF .68 234A7B83 push 837B4A23 ;参数1
004DF504 .E8 6B1AF5FF call 00430F74 ;相同
004DF509 .DC45 E0 fadd qword ptr ;计算后的结果加上
004DF50C .DD5D E0 fstp qword ptr ;存入
004DF50F .9B wait
................. //省略代码为判断第九位
004DF576 .8B45 F8 mov eax, dword ptr ;eax为假码的地址
004DF579 .8A58 09 mov bl, byte ptr ;bl为假码第十位
004DF57C .33C0 xor eax, eax ;eax=0
004DF57E .8AC3 mov al, bl ;al=bl
004DF580 .8945 A0 mov dword ptr , eax
004DF583 .DB45 A0 fild dword ptr
004DF586 .83C4 F4 add esp, -0C
004DF589 .DB3C24 fstp tbyte ptr ;参数2为假码第十位
004DF58C .9B wait
004DF58D .68 FD3F0000 push 3FFD
004DF592 .68 92244992 push 92492492
004DF597 .68 49922449 push 49249249 ;参数1
004DF59C .E8 D319F5FF call 00430F74
004DF5A1 .DC45 E0 fadd qword ptr ;计算后的结果加上,并保存
004DF5A4 .DD5D E0 fstp qword ptr
004DF5A7 .9B wait
004DF5A8 .DD45 E0 fld qword ptr
004DF5AB .83C4 F4 add esp, -0C
004DF5AE .DB3C24 fstp tbyte ptr ;参数2为计算后的结果
004DF5B1 .9B wait
004DF5B2 .68 00400000 push 4000
004DF5B7 .68 133BB193 push 93B13B13
004DF5BC .68 B1133BB1 push B13B13B1 ;参数1
004DF5C1 .E8 AE19F5FF call 00430F74
004DF5C6 .DD5D E8 fstp qword ptr ;计算后的结果保存在中
004DF5C9 .9B wait
004DF5CA .DD45 E8 fld qword ptr
004DF5CD .D825 20F94D00 fsub dword ptr ;与dword ptr相减
004DF5D3 .D9E1 fabs ;取绝对值
004DF5D5 .DB2D 24F94D00 fld tbyte ptr ;装入tbyte ptr 到st(0)
004DF5DB .DED9 fcompp ;比较
004DF5DD .DFE0 fstsw ax
004DF5DF .9E sahf
004DF5E0 .0F82 B6020000 jb 004DF89C ;小于就跳向失败
通过代码6可知,程序通过了4个Call1来验证第7,8,10位。这四个Call1的参数1的小数部分都不为0,都跳到流程1执行。前三个Call1的参数2分别第7,8,10位的ASCII码,计算后的结果分别为A7,A8,A10,第四个Call1的参数2为A7+A8+10,计算后的结果记为A101。A101减去dword ptr (也就是685.5000),结果取绝对值。再与tbyte ptr (也就是0.8865299999999998976)比较,小于就跳向失败。和第二段一样,我们可以用4个for循环,对第7,8,10位进行验证。
具体代码见图片8
运行这段代码,弹出提示.
见图片9。
我们把假码改成LH4A8N'w#~1234567890,重启程序。
第四段:(判断第11,12,13,14位)(SEH异常验证)
见代码7。
004DF644 .8945 A0 mov dword ptr , eax ;第十一位
004DF647 .DB45 A0 fild dword ptr
004DF64A .83C4 F4 add esp, -0C
004DF64D .DB3C24 fstp tbyte ptr ;第十一位
004DF650 .9B wait
004DF651 .B8 09000000 mov eax, 9 ;eax=9
004DF656 .E8 0519F5FF call 00430F60 ;计算的eax次方,计算结果记为A11
004DF65B .33C0 xor eax, eax
004DF65D .8AC3 mov al, bl ;第十一位
004DF65F .F7E8 imul eax ;eax*eax
004DF661 .F7E8 imul eax ;eax*eax
004DF663 .66:8945 9C mov word ptr , ax ;取ax给ebp-64
004DF667 .DF45 9C fild word ptr ;将word ptr 装入st(0)
004DF66A .DEC9 fmulp st(1), st ;A11与st再乘
004DF66C .DC45 E0 fadd qword ptr ;加上(初始为0)
004DF66F .DD5D E0 fstp qword ptr ;保存在ebp-20中
004DF672 .9B wait
004DF673 .8B45 F8 mov eax, dword ptr ;eax为假码地址
004DF676 .8A58 0B mov bl, byte ptr ;bl为第十二位
004DF679 .33C0 xor eax, eax
004DF67B .8AC3 mov al, bl ;al=bl
004DF67D .83C0 0B add eax, 0B ;eax+0XB
004DF680 .71 05 jno short 004DF687 ;不溢出就跳
004DF682 .E8 7544F2FF call 00403AFC
004DF687 >8945 A0 mov dword ptr , eax
004DF68A .DB45 A0 fild dword ptr
004DF68D .D9FA fsqrt ;第十二位+0xB开方
004DF68F .E8 B835F2FF call 00402C4C ;取开方后的整数
004DF694 .8BF0 mov esi, eax ;esi=开方后的整数
004DF696 .C745 D8 01000>mov dword ptr , 1
004DF69D .C745 DC 00000>mov dword ptr , 0
004DF6A4 .83FE 01 cmp esi, 1 ;是否为1
004DF6A7 .76 2D jbe short 004DF6D6
004DF6A9 >8BC6 mov eax, esi ;循环开始(算阶乘)
004DF6AB .33D2 xor edx, edx
004DF6AD .52 push edx
004DF6AE .50 push eax
004DF6AF .8B45 D8 mov eax, dword ptr
004DF6B2 .8B55 DC mov edx, dword ptr
004DF6B5 .E8 2A65F2FF call 00405BE4 ;相乘
004DF6BA .71 05 jno short 004DF6C1
004DF6BC .E8 3B44F2FF call 00403AFC
004DF6C1 >8945 D8 mov dword ptr , eax
004DF6C4 .8955 DC mov dword ptr , edx
004DF6C7 .83EE 01 sub esi, 1
004DF6CA .73 05 jnb short 004DF6D1
004DF6CC .E8 2B44F2FF call 00403AFC
004DF6D1 >83FE 01 cmp esi, 1
004DF6D4 .^ 77 D3 ja short 004DF6A9 ;循环结束
004DF6D6 >FF75 DC push dword ptr
004DF6D9 .FF75 D8 push dword ptr ;阶乘后的值
004DF6DC .33C0 xor eax, eax ;eax=0
004DF6DE .8AC3 mov al, bl ;第十二位
004DF6E0 .F7E8 imul eax ;相乘
004DF6E2 .F7E8 imul eax ;相乘
004DF6E4 .0FBFC0 movsx eax, ax ;取ax
004DF6E7 .33D2 xor edx, edx ;EDX=0
004DF6E9 .8AD3 mov dl, bl ;第十二位
004DF6EB .F7EA imul edx ;相乘
004DF6ED .71 05 jno short 004DF6F4
004DF6EF .E8 0844F2FF call 00403AFC
004DF6F4 >99 cdq ;护展成四字节
004DF6F5 .E8 EA64F2FF call 00405BE4 ;相乘
004DF6FA .71 05 jno short 004DF701 ;与前面的结果相乘
004DF6FC .E8 FB43F2FF call 00403AFC
004DF701 >8945 94 mov dword ptr , eax
004DF704 .8955 98 mov dword ptr , edx
004DF707 .DF6D 94 fild qword ptr ;装入第十二位运算结果
004DF70A .DC45 E0 fadd qword ptr ;与相加
004DF70D .DD5D E0 fstp qword ptr
004DF710 .9B wait
004DF711 .8B45 F8 mov eax, dword ptr ;eax为假码地址
004DF714 .8A58 0C mov bl, byte ptr ;第十三位
004DF717 .33C0 xor eax, eax
004DF719 .8AC3 mov al, bl ;al=bl
004DF71B .8945 A0 mov dword ptr , eax
004DF71E .DB45 A0 fild dword ptr
004DF721 .83C4 F4 add esp, -0C
004DF724 .DB3C24 fstp tbyte ptr ;参数2为假码第十三位
004DF727 .9B wait
004DF728 .68 01400000 push 4001
004DF72D .68 000000C0 push C0000000
004DF732 .6A 00 push 0 ;参数1(此时进入流程2)
004DF734 .E8 3B18F5FF call 00430F74
004DF739 .33C0 xor eax, eax
004DF73B .8AC3 mov al, bl
004DF73D .8945 A0 mov dword ptr , eax
004DF740 .DB45 A0 fild dword ptr
004DF743 .DEC9 fmulp st(1), st ;相乘
004DF745 .DC45 E0 fadd qword ptr ;与相加
004DF748 .DD5D E0 fstp qword ptr
004DF74B .9B wait
004DF74C .8B45 F8 mov eax, dword ptr
004DF74F .8A58 0D mov bl, byte ptr ;第十四位
004DF752 .8BC3 mov eax, ebx
004DF754 .25 FF000000 and eax, 0FF
004DF759 .33D2 xor edx, edx
004DF75B .8945 D8 mov dword ptr , eax
004DF75E .8955 DC mov dword ptr , edx
004DF761 .DF6D D8 fild qword ptr
004DF764 .83C4 F4 add esp, -0C
004DF767 .DB3C24 fstp tbyte ptr ;参数2为假码第十四位
004DF76A .9B wait
004DF76B .68 00400000 push 4000
004DF770 .68 00000080 push 80000000 ;2(进入流程2)
004DF775 .6A 00 push 0
004DF777 .E8 F817F5FF call 00430F74
004DF77C .8B45 F8 mov eax, dword ptr
004DF77F .0FB640 0A movzx eax, byte ptr ;第十一位
004DF783 .F7E8 imul eax ;eax*eax
004DF785 .66:8945 9C mov word ptr , ax
004DF789 .DF45 9C fild word ptr
004DF78C .DEC9 fmulp st(1), st ;相乘
004DF78E .8B45 F8 mov eax, dword ptr
004DF791 .8A40 0B mov al, byte ptr ;第十二位
004DF794 .25 FF000000 and eax, 0FF
004DF799 .8945 A0 mov dword ptr , eax
004DF79C .DB45 A0 fild dword ptr
004DF79F .DEC9 fmulp st(1), st ;相乘
004DF7A1 .DC45 E0 fadd qword ptr ;与相加
004DF7A4 .DD5D E0 fstp qword ptr
004DF7A7 .9B wait
004DF7A8 .33C0 xor eax, eax
004DF7AA .55 push ebp
004DF7AB .68 FEF74D00 push 004DF7FE
004DF7B0 .64:FF30 push dword ptr fs:
004DF7B3 .64:8920 mov dword ptr fs:, esp ;安装SEH异常
004DF7B6 .DD45 E0 fld qword ptr ;压入第十一位到第十四位的运算结果
004DF7B9 .E8 8E34F2FF call 00402C4C
004DF7BE 2D 8063E7EA sub eax, EAE76380 ;1026143249280
004DF7C3 81DA EE000000 sbb edx, 0EE
004DF7C9 .71 05 jno short 004DF7D0
004DF7CB .E8 2C43F2FF call 00403AFC
004DF7D0 >8945 94 mov dword ptr , eax
004DF7D3 .8955 98 mov dword ptr , edx
004DF7D6 .DF6D 94 fild qword ptr
004DF7D9 .DB7D 88 fstp tbyte ptr
004DF7DC .9B wait
004DF7DD .E8 06CAF2FF call 0040C1E8 ;通过GetLocalTime计算出一个随机数,无用
004DF7E2 .DB6D 88 fld tbyte ptr
004DF7E5 .DEF9 fdivp st(1), st ;st(1)%st(0)给st(1)(主要在st0)
004DF7E7 .DDD8 fstp st ;st为0,触发异常
004DF7E9 .33C0 xor eax, eax
004DF7EB .5A pop edx
004DF7EC .59 pop ecx
004DF7ED .59 pop ecx
004DF7EE .64:8910 mov dword ptr fs:, edx
004DF7F1 .E9 A6000000 jmp 004DF89C ;如果没有触发异常,这里必跳
通过代码7可知,程序在对第13,14位进行Call1运算时,参数1分别为6.000000和2.000000。所以进入了流程2,我们写注册机时就不能利用先前写好的Call1。不过,别担心,流程2要比流程1简单.
见代码8。
00430FDC|.DB6D 08 fld tbyte ptr ;装入参数1到st(0)(流程2)
00430FDF|.D9E1 fabs ;取绝对值
00430FE1|.DB2D 30104300 fld tbyte ptr ;装入0x7FFFFFFF到st(0)
00430FE7|.DED9 fcompp ;比较
00430FE9|.DFE0 fstsw ax
00430FEB|.9E sahf
00430FEC|.72 1E jb short 0043100C ;小于就跳(这里死都不跳--!)
00430FEE|.66:8B45 1C mov ax, word ptr
00430FF2|.50 push eax
00430FF3|.FF75 18 push dword ptr
00430FF6|.FF75 14 push dword ptr
00430FF9|.DB6D 08 fld tbyte ptr ;装入参数1到st(0)
00430FFC|.E8 4B1CFDFF call 00402C4C ;将参数1取整后装入eax
00431001|.E8 2AFFFFFF call 00430F30 ;进入
00431006|.DB7D F0 fstp tbyte ptr
00431009|.9B wait
0043100A|.EB 17 jmp short 00431023
由代码8可知,流程2的关键处在于Call 00430F30中,我们进入这个Call.
见代码9。
00430F30/$55 push ebp
00430F31|.8BEC mov ebp, esp
00430F33|.89C1 mov ecx, eax
00430F35|.99 cdq ;扩展成四字节
00430F36|.D9E8 fld1 ;装入1到st(0)
00430F38|.31D0 xor eax, edx
00430F3A|.29D0 sub eax, edx
00430F3C|.74 1A je short 00430F58
00430F3E|.DB6D 08 fld tbyte ptr ;装入参数2
00430F41|.EB 02 jmp short 00430F45
00430F43|>D8C8 /fmul st, st ;相乘
00430F45|>D1E8 shr eax, 1 ;eax右移位,高位补0,低位进入CF,CF=0就跳
00430F47|.^ 73 FA |jnb short 00430F43 ;不小于就跳
00430F49|.DCC9 |fmul st(1), st ;st(1)*st(0)
00430F4B|.^ 75 F6 \jnz short 00430F43 ;不等就跳
00430F4D|.DDD8 fstp st
00430F4F|.83F9 00 cmp ecx, 0
00430F52|.7D 04 jge short 00430F58
00430F54|.D9E8 fld1
00430F56|.DEF1 fdivrpst(1), st
00430F58|>9B wait
00430F59|.5D pop ebp
00430F5A\.C2 0C00 retn 0C
代码9的关键在于中间那个循环。shr为逻辑右移,shr eax,1的意思是将eax向右移到1位,高位补0,低位进入CF,当CF=0时,jnb跳转实现。shr eax,1也可以理解为将 eax除以2,eax为商,edx为余数。当eax不为0时,ZF=0,jnz跳转实现。是不是有点晕啊!不用担心,因为这个循环的次数是根据参数1来决定的,参数1只有两个值,分别是6和2。因此我们没有必要还原这个循环,我们只需对这两个值分别进行特定的计算就行。当参数1为6时.
c++代码见图片10。
当参数1为2时,c++代码见图片11。
Num13,Num14分别代表假码第13,14位的ASCII码,sd3,sd4分别代表最终的计算结果。是不是很简单啊!分析完流程2,我们继续看代码7,发现程序依旧还是利用Call1和四则运算分别对第11,12,13,14位的假码进行运算,然后相加得出一个具体的值(记为A14)来判断是否通过验证。只不过这种验证更加的隐蔽,它运用了SEH异常处理,我们不能像第二段或第三段直接找出这个值要满足的范围。我们现在从004DF7AA这里看起。程序在004DF7B3处安装了SEH异常,此时我们看堆栈窗口.
见图片12。
在代码窗口按下CTRL+G,输入004DF7FE,下好CC断点,继续我们的分析。在004DF7B3处,程序将A14装入st(0)中,而00402C4C这个Call的作用就是把A14取整,然后转换成16进制。因为A14之前是64位的,也就是double类型。一个寄存器是32位的,所以这里用edx和eax一起保存。然后eax减去EAE76380,edx减去0EE,然后再将eax,edx还原成一个double数(A140)。然后通过Call 0040C1E8,利用GetLocalTime计算出一个随机数。然后用这个随机数来除以A140。如果我们输入的不是正确的注册码,程序正常运行下去,并跳到004DF89C处,将全局注册标志清0。所以在这里我们必须触发了这个异常,才能进行下一步的验证。那么这个异常是什么呢?我们仔细看下代码,发现004DF7E5的代码为fdivp st(1),st。我们自然联想到了除0异常,将st(0)的值赋予0,发现程序出现异常,并跳到004DF7FE处执行,也就是我们之前下的CC断点处。限于篇幅,SEH的处理过程我就不多了,黑X论坛上有说明的贴子,大家可以上去看看,不懂的话,也可以在论坛联系我。我们在004DF803处下好CC断点,然后按下F9,发现出现异常后的程序成功的断了下来,并进行下一步验证。好了,我们现在返回去看,将004DF7BE和004DF7C3处的eax和edx组合起来就是0xEEEAE76380,转换成十进制就是1026143249280。也就是说,当最终的计算结果A14为1026143249280时,则通过验证。分析到这里是不是有点累了!没关系,加油,马上就要到达终点了。知道了验证过程,我们就可以用c++来算出这段注册码。
见图片13和图片14。
代码中的Call2(9,Num11)是用来计算Num11的9次方的,Call3是用来计算阶乘的。运行这段代码,跳出提示.
见图片15。
可知第11-14位分别为J&4T。且是定值。我们把假码改成LH4A8N'w#~J&4T12345。重启程序,进行最后一处验证。
第五段(第15,16,17,18,19位的验证)
见代码10。
004DF803 .E8 FC4BF2FF call 00404404 ;跳到这里执行了
004DF808 .A1 BC355B00 mov eax, dword ptr ;XL[
004DF80D .8B00 mov eax, dword ptr
004DF80F .83C0 04 add eax, 4
004DF812 .50 push eax
004DF813 .B9 02000000 mov ecx, 2
004DF818 .BA 14000000 mov edx, 14
004DF81D .8B45 F8 mov eax, dword ptr ;eax为假码地址
004DF820 .E8 E756F2FF call 00404F0C
004DF825 .8B45 F8 mov eax, dword ptr
004DF828 .8A40 12 mov al, byte ptr ;第十九位
004DF82B .25 FF000000 and eax, 0FF ;取ax低位
004DF830 .33D2 xor edx, edx
004DF832 .8945 B0 mov dword ptr , eax
004DF835 .8955 B4 mov dword ptr , edx
004DF838 .6A 00 push 0
004DF83A .6A 1A push 1A ;除数1A
004DF83C .8B45 D0 mov eax, dword ptr ;第十五位
004DF83F .8B55 D4 mov edx, dword ptr
004DF842 .0345 C8 add eax, dword ptr ;第十五位+第十六位
004DF845 .1355 CC adc edx, dword ptr
004DF848 .71 05 jno short 004DF84F
004DF84A .E8 AD42F2FF call 00403AFC
004DF84F >0345 C0 add eax, dword ptr ;再加第十七位
004DF852 .1355 C4 adc edx, dword ptr
004DF855 .71 05 jno short 004DF85C
004DF857 .E8 A042F2FF call 00403AFC
004DF85C >0345 B8 add eax, dword ptr ;再加第十八位
004DF85F .1355 BC adc edx, dword ptr
004DF862 .71 05 jno short 004DF869
004DF864 .E8 9342F2FF call 00403AFC
004DF869 >E8 CA64F2FF call 00405D38 ;第15至18位的和除以1A,并取余数.
004DF86E .71 05 jno short 004DF875
004DF870 .E8 8742F2FF call 00403AFC
004DF875 >83C0 41 add eax, 41 ;加上0x41
004DF878 .83D2 00 adc edx, 0
004DF87B .71 05 jno short 004DF882
004DF87D .E8 7A42F2FF call 00403AFC
004DF882 >8945 A8 mov dword ptr , eax ;计算后的结果保存在中
004DF885 .8955 AC mov dword ptr , edx
004DF888 .8B45 B0 mov eax, dword ptr ;eax为第十九位
004DF88B .8B55 B4 mov edx, dword ptr
004DF88E .3B55 AC cmp edx, dword ptr ;必为0
004DF891 .75 03 jnz short 004DF896
004DF893 .3B45 A8 cmp eax, dword ptr ;第十五到第十八位的计算结果要等于第十九位
004DF896 >75 04 jnz short 004DF89C ;不等就跳
004DF898 .C645 F7 01 mov byte ptr , 1 ;全局注册标志
004DF89C >33C0 xor eax, eax
004DF89E .5A pop edx
004DF89F .59 pop ecx
004DF8A0 .59 pop ecx
由代码10可知,程序先累加第15位到18位的ASCII码,累加之和记为A19。然后用A19除以0X1A,并取其余数。结果再加上0X41,并与第十九位比较。如果相等,则在004DF898处注册全局标志。如果我们要爆破的话,可以在这里改。写注册机时,依旧是用5个for循环进行穷举.
具体代码见图片16。
运行这段代码,跳出提示.
见图17。
小结:
所以我们用注册机最后得出的注册码是LH4A8N'w#~J&4T!!!9A。重启程序,输入LH4A8N'w#~J&4T!!!9A,弹出成功注册提示.
见图片18。
由图片18可知,程序还有重启验证。当注册码通过以上验证的时候,并会将其保存在注册表中,这在一定程序上阻止了爆破。但是在整个验算过程中,用户名并没有参与验算,而且也并没有取机器特征码,导致了多机一码的情况出现,我认为这是一个失败之处。不过这个程序的用来练习算法,考验耐心是最适合不过的了,--!。至此文章就写的差不多了,由于第四段验证的代码太长,所以只说明了主要部分,详细的分析我都注释在代码上了,大家可以去看看。
C++注册机源码如下:
void CMyDlg::OnButton1()
{
// TODO: Add your control notification handler code here
//Call1 代表00430F74
//Call2 代表00430F60
//Call3 代表00405BE4 阶乘
CString Key="LH4A8N",Key0; //注册码
double Para={0.0} ; //定义参数数组并初始化为0
Para=0.5686999999999998976; //第七位
Para=0.3332999999999999488; //第八位
Para=0.0408163265306122432; //第九位
Para=2.0000100000000000000; //第九位计算结果
Para=0.2857142857142856704; //第十位
Para=2.3076923076923074560; //第七,八,十位计算的结果
Para=6.0000000000000000000; //第十三位
Para=2.0000000000000000000; //第十四位
double Para1=1.1561553999999997440; //tbyte ptr
double Para2=6.8999999999999989760e-07; //tbyte ptr
double Para3=685.5000; //dword ptr
double Para4=0.8865299999999998976; //tbyte ptr
double Para5=2147483647.0000000000; //tbyte ptr
double temp=0.0,ebp20=0.0,ebp18=0.0;
DWORD eax=0;
int Num7=0,Num8=0,Num9=0,Num10=0,Num11=0,Num12=0,Num13=0,Num14=0,Num15=0,Num16=0,Num17=0,Num18=0,Num19=0;
CString temp9="第九位为",temp7="第七位为",temp8="第八位为",temp10="第十位为",temp0="是否取其它值",
temp11="第十一位为",temp12="第十二位为",temp13="第十三位为",temp14="第十四位为",temp15="第十五位为",
temp16="第十六位为",temp17="第十七位为",temp18="第十八位为",temp19="第十九位为";
double sd1,sd2,sd3,sd4,sd;
char p;
//第九位
Num9=33;
while (Num9<127)
{
temp=Call1(Para,Num9);
temp=fabs(Para1-temp);
sd=Call1(Para,temp);
if (Para2>=sd)
{
p=(char)Num9;
temp9+=p;
AfxMessageBox(temp9);
}
Num9++;
}
//第九位
//第7位 第8位 第10位
int n=1;
for (Num7=33;Num7<127;Num7++)
{
sd1=Call1(Para,Num7);
ebp20+=sd1;
for (Num8=33;Num8<127;Num8++)
{
sd2=Call1(Para,Num8);
ebp20+=sd2;
for (Num10=33;Num10<127;Num10++)
{
sd3=Call1(Para,Num10);
ebp20+=sd3;
sd4=Call1(Para,ebp20);
temp=fabs(sd4-Para3);
if (Para4>temp)
{
p=(char)Num7;
Key0=p;
temp7+=p;
p=(char)Num8;
Key0+=p;
Key0+="#";
temp8+=p;
p=(char)Num10;
Key0+=p;
temp10+=p;
temp0=temp7+"\n"+temp8+"\n"+temp10+"\n"+"是否取其它值";
n=AfxMessageBox(temp0,MB_OKCANCEL,NULL);
if(n!=1)
{
Key+=Key0;
break;
}
temp7="第七位为";
temp8="第八位为";
temp10="第十位为";
}
ebp20-=sd3;
if(n!=1)
{
break;
}
}
ebp20-=sd2;
if(n!=1)
{
break;
}
}
ebp20-=sd1;
if(n!=1)
{
break;
}
}
ebp20=0; //清零
//第7位 第8位 第10位
//第11,12,13,14位
LONG64 eax1,eax2;
signed short ax;
signed long eax3;
double as;
for (Num11=33;Num11<127;Num11++)
{
eax=Num11*Num11;
ax=eax*eax;
as=double(ax);
sd1=as*Call2(9,Num11);
ebp20+=sd1;
for (Num12=33;Num12<127;Num12++)
{
eax=Num12+0x0B;
eax=(int)sqrt(eax);
temp=Call3(eax);
eax=Num12*Num12;
eax=eax*eax;
ax=eax;
eax=0;
eax3=ax;
eax3=eax3*Num12;
eax2=eax3*temp;
ebp20+=eax2;
for(Num13=33;Num13<127;Num13++)
{
sd3=Num13*Num13;
sd3=sd3*sd3;
temp=Num13*Num13;
sd3=sd3*temp;
sd3=sd3*Num13;
ebp20+=sd3;
for (Num14=33;Num14<127;Num14++)
{
sd4=Num14*Num14;
eax1=Num11*Num11;
eax1=eax1*sd4*Num12;
ebp20+=eax1;
if(INT64(ebp20)-1026143249280==0)
{
p=char(Num11);
temp11=temp11+p;
p=char(Num12);
temp12=temp12+p;
p=char(Num13);
temp13=temp13+p;
p=char(Num14);
temp14=temp14+p;
temp14=temp11+"\n"+temp12+"\n"+temp13+"\n"+temp14;
AfxMessageBox(temp14);
}
ebp20-=eax1;
}
ebp20-=sd3;
}
ebp20-=eax2;
}
ebp20-=sd1;
}
//第11,12,13,14位
//第15,16,17,18,19位
int Num=0,Num0;
n=1;
for (Num19=33;Num19<127;Num19++)
{
for (Num15=33;Num15<127;Num15++)
{
Num+=Num15;
for (Num16=33;Num16<127;Num16++)
{
Num+=Num16;
for (Num17=33;Num17<127;Num17++)
{
Num+=Num17;
for (Num18=33;Num18<127;Num18++)
{
Num+=Num18;
Num0=Num % 0x1A;
Num0+=0x41;
if (Num0==Num19)
{
p=(char)Num15;
Key0=p;
temp15+=p;
p=(char)Num16;
Key0+=p;
temp16+=p;
p=(char)Num17;
Key0+=p;
temp17+=p;
p=(char)Num18;
Key0+=p;
temp18+=p;
p=(char)Num19;
Key0+=p;
temp19+=p;
temp0=temp15+"\n"+temp16+"\n"+temp17+"\n"+temp18+"\n"+temp19+"\n"+"是否取其它值";
n=AfxMessageBox(temp0,MB_OKCANCEL,NULL);
if(n!=1)
{
Key+="J&4T";
Key+=Key0;
break;
}
temp15="第十五位为";
temp16="第十六位为";
temp17="第十七位为";
temp18="第十八位为";
temp19="第十九位为";
}
Num-=Num18;
if(n!=1)
{
break;
}
}
Num-=Num17;
if(n!=1)
{
break;
}
}
Num-=Num16;
if(n!=1)
{
break;
}
}
Num-=Num15;
if(n!=1)
{
break;
}
}
if(n!=1)
{
break;
}
}
m_Key.SetWindowText(Key);
}
//temp=(log(假码第七位l)/log(2)*Key*参数8*log(2)(e))-int(log(假码第七位l)/log(2)*Key*参数8*log(2)(e));
//temp=2的temp次方-1+1
//2的temp次方
double CMyDlg::Call1(double x, double y)
{
//x为参数1,y为参数2,dTemp1为运算的结果
double dTemp1,dTemp2;
dTemp1=log(y)/log(2)*loge2*x*log2e;
dTemp2=int(dTemp1);
dTemp1=dTemp1-dTemp2;
dTemp1=pow(2,dTemp1);
dTemp1=pow(2,dTemp2)*dTemp1;
return dTemp1;
}
double CMyDlg::Call2(double x, double y)
{
double dTemp3;
dTemp3=y*pow(2,x);
return dTemp3;
}
void CMyDlg::OnButton2()
{
// TODO: Add your control notification handler code here
}
DWORD CMyDlg::Call3(int x)
{
DWORD j=1;
for (int i=1;i<=x;i++)
{
j=i*j;
}
return j;
}
后记:
感谢支持我教程的朋友们,小菜是第一次做系列教程,有些地方表达不太清楚请见谅,因为本系列教程定位需要有一定基础的,所以下一步小菜也希望能出一些新手教程来帮助大家进步,具体下一系列的教程还没定位,希望大家能提些意见,也希望大家继续支持我,谢谢大家{:301_975:}
=================================================================
传送门:
破解实战-第一战:http://www.52pojie.cn/thread-197281-1-1.html
破解实战-第二战:http://www.52pojie.cn/thread-197598-1-1.html
破解实战-第三站:http://www.52pojie.cn/thread-197957-1-1.html
破解实战-第四站:http://www.52pojie.cn/thread-198203-1-1.html
破解实战-第五战:http://www.52pojie.cn/thread-198365-1-1.html
破解实战-第六战:http://www.52pojie.cn/thread-198930-1-1.html
破解实战-第七战:http://www.52pojie.cn/thread-199459-1-1.html
破解实战-第八战:http://www.52pojie.cn/thread-199834-1-1.html
破解实战-第九战:http://www.52pojie.cn/thread-200655-1-1.html
破解实战-第十战:http://www.52pojie.cn/thread-200798-1-1.html
这个程序是Borland Delphi 6.0 - 7.0写的。我们把它载入DeDe,等待程序转储分析完毕,点击DeDe中的窗体选项,在模块名中找到TForm_Reg
DeDe是啥? 前排支持楼主。。 我来支持楼主了第一次顶楼主的帖子我会翻出前边的教程慢慢学 算法看了我头疼 支持,不错的教程,谢谢 太高级了,太深奥了,看不懂,不过还是谢谢楼主了 看不懂,不过我是不干坏事好银,谢谢大神分享! 支持楼主,很有借鉴! 暂时先收藏,等水平高了,在慢慢研究。 最后这个真是重量级的,膜拜了!!!