简介:一个五子棋注册程序的分析,和另类注册机
Crack By: linf
【使用工具】 w32dasm,Ollday,Hedit等...
【破解平台】 Win9x/XP
【软件名称】 五子棋
【软件简介】:
――支持人机对战。而且智商还不差。
――支持双人模式。
――支持局域网和互连网。可以两个人在网上对战,还可以三个人同时玩“三足鼎立”。
――支持多语种。如果你的操作系统是中文win95/98/nt,那么游戏的菜单及提示都是中文;如果你的操作系统是英文win95/98/nt,那么游戏的菜单及提示都是英文。
――不支持禁手和长连。
――有四首背景音乐可供您慢慢欣赏。
――人机对战时可以存取游戏进度。
――有类似Windows中扫雷游戏的五子排行榜。
【软件大小】 671 K
【破解原因】 大家共同学习一下。这个程序以前用修改过,使得悔棋的步数可以自由确定。(请参考:http://bbs.pediy.com/showthread.php?s=&threadid=7784)那个时候也简单的追过注册码,不过因功力&时间不足,最终放了下来。近来工作轻闲,花了几天时间完成了作品。小生初学难免有错误,还请各位指出,小生有礼了。
【破解过程】
首先把原程序复制>4份,分别改名为oll.exe;w32.exe;back.exe;tes1.exe;...;tesn.exe。我的习惯是使用oll动态调试,trw2000和softice是命令行方式的,有些麻烦,在windows xp中也不可以用;不到万不得以不使用,w32das用来查看代码的跳转并复制代码更顺手;back用来备份;其他的用来修改测试。:-P多亏程序小,不然硬盘不够用了。q-:
使用各种工具装载程序。在w32dasm中查找注册错误的提示,见如下代码:
**#*#*#*#*#*#*#*#**
注意:本文汇编代码全部摘自w32dasm中,与ollydbg中摘出的反汇编会有些不同。
oll中:
00475938 |. 8D4D 9B LEA ECX,DWORD PTR SS:[EBP-65]
0047593B |. 8D9D D1FEFFFF LEA EBX,DWORD PTR SS:[EBP-12F]
w32中:
:00475938 8D4D9B lea ecx, dword ptr [ebp-65]
:0047593B 8D9DD1FEFFFF lea ebx, dword ptr [ebp+FFFFFED1]
但肯定都是指向了一个地址:EBP-12F=ebp+FFFFFED1
**#*#*#*#*#*#*#*#**
:00475A80 53 push ebx
:00475A81 8BD8 mov ebx, eax
:00475A83 803D48D3470001 cmp byte ptr [0047D348], 01 <-- 程序启动关于对话框时,经过这里。相等就表示已经注册了,然后自动隐藏填写注册码的地方。
:00475A8A 753B jne 00475AC7
:00475A8C 33D2 xor edx, edx
:00475A8E 8B83DC020000 mov eax, dword ptr [ebx+000002DC]
:00475A94 E86F65FBFF call 0042C008
:00475A99 B201 mov dl, 01
... ...
* Possible StringData Ref from Code Obj ->"Register"
|
:00475B81 BA005C4700 mov edx, 00475C00
:00475B86 A14CD34700 mov eax, dword ptr [0047D34C]
:00475B8B 8B18 mov ebx, dword ptr [eax]
:00475B8D FF5304 call [ebx+04]
:00475B90 E8CBFCFFFF call 00475860 <-- 关键call
:00475B95 803D48D3470000 cmp byte ptr [0047D348], 00
:00475B9C 750C jne 00475BAA
* Possible StringData Ref from Code Obj ->“名字和注册号不匹配!请重新输入!”
|
:00475B9E B8245C4700 mov eax, 00475C24
:00475BA3 E828A8FDFF call 004503D0
:00475BA8 EB14 jmp 00475BBE
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475B9C(C)
* Possible StringData Ref from Code Obj ->"恭喜你已经成功注册!"
|
:00475BAA B8505C4700 mov eax, 00475C50
:00475BAF E81CA8FDFF call 004503D0
:00475BB4 A144D34700 mov eax, dword ptr [0047D344]
很明显修改00475B9C 的“jne”为“jmp”就爆破了。先试验一下,打开程序随便输入。果然注册成功了,真是简单呀!真的是太简单了吧?再试验一下被限制的功能,果然不可以使用,还是让你去注册。看来它还要向硬盘中写点什么东西或是在内存中驻留一个未注册的标志,以便随时检查是否真的已经注册。
于是再用oll载入,在00475B90处下断点;运行;没有出现界面就被中断了,看来在启动时就检查是否有正确的注册信息。先不看它,按F9跳过;输入姓名:“guxinghe”;注册码:“cqmygysds”(床前明月光疑是地上);点击注册;再次中断。这次进去看看:
* Referenced by a CALL at Addresses:
|:00475B90 , :00475C9E <--有两处调用!
|
:00475860 55 push ebp
:00475861 8BEC mov ebp, esp
:00475863 81C4C8FDFFFF add esp, FFFFFDC8
:00475869 53 push ebx
:0047586A 56 push esi
:0047586B 57 push edi
:0047586C 33C0 xor eax, eax
:0047586E 8985C8FDFFFF mov dword ptr [ebp+FFFFFDC8], eax
:00475874 8985CCFDFFFF mov dword ptr [ebp+FFFFFDCC], eax
:0047587A 33C0 xor eax, eax
:0047587C 55 push ebp
:0047587D 683D5A4700 push 00475A3D
:00475882 64FF30 push dword ptr fs:[eax]
:00475885 648920 mov dword ptr fs:[eax], esp
:00475888 6A00 push 00000000
:0047588A 8D85CCFDFFFF lea eax, dword ptr [ebp+FFFFFDCC]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475821(C)
<--可能是把注册信息存到文件中。
:00475890 50 push eax
* Possible StringData Ref from Code Obj ->"Name"
|
:00475891 B9545A4700 mov ecx, 00475A54
* Possible StringData Ref from Code Obj ->"register"
|
:00475896 BA645A4700 mov edx, 00475A64
:0047589B A14CD34700 mov eax, dword ptr [0047D34C]
:004758A0 8B18 mov ebx, dword ptr [eax]
:004758A2 FF13 call dword ptr [ebx]
:004758A4 8B95CCFDFFFF mov edx, dword ptr [ebp+FFFFFDCC]
:004758AA 8D85D0FDFFFF lea eax, dword ptr [ebp+FFFFFDD0]
:004758B0 B9FF000000 mov ecx, 000000FF
:004758B5 E8DEE4F8FF call 00403D98
:004758BA 8D95D0FDFFFF lea edx, dword ptr [ebp+FFFFFDD0]
:004758C0 8D459B lea eax, dword ptr [ebp-65]
:004758C3 B164 mov cl, 64
:004758C5 E842D1F8FF call 00402A0C
:004758CA 6A00 push 00000000
:004758CC 8D85C8FDFFFF lea eax, dword ptr [ebp+FFFFFDC8]
:004758D2 50 push eax
* Possible StringData Ref from Code Obj ->"RegNo"
|
:004758D3 B9785A4700 mov ecx, 00475A78
* Possible StringData Ref from Code Obj ->"register"
|
:004758D8 BA645A4700 mov edx, 00475A64
:004758DD A14CD34700 mov eax, dword ptr [0047D34C]
:004758E2 8B18 mov ebx, dword ptr [eax]
:004758E4 FF13 call dword ptr [ebx]
:004758E6 8B95C8FDFFFF mov edx, dword ptr [ebp+FFFFFDC8] <--假码
:004758EC 8D85D0FDFFFF lea eax, dword ptr [ebp+FFFFFDD0] <--姓名
:004758F2 B9FF000000 mov ecx, 000000FF
:004758F7 E89CE4F8FF call 00403D98
:004758FC 8D95D0FDFFFF lea edx, dword ptr [ebp+FFFFFDD0]
:00475902 8D8536FFFFFF lea eax, dword ptr [ebp+FFFFFF36]
:00475908 B164 mov cl, 64
:0047590A E8FDD0F8FF call 00402A0C
:0047590F 8A459B mov al, byte ptr [ebp-65] <-- 姓名的长度
:00475912 84C0 test al, al
:00475914 7409 je 0047591F <-- 长度为零就直接错误
:00475916 80BD36FFFFFF06 cmp byte ptr [ebp+FFFFFF36], 06 <-- 测试注册码的长度
:0047591D 740C je 0047592B <-- 不等于6位,执行:0047591F行,跟到这里出错了,再来一次,输入6位注册码。第二次这里顺利通过。
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475914(C)
|
:0047591F C60548D3470000 mov byte ptr [0047D348], 00
<-- 写入00,与00475B95 行相呼应
:00475926 E9F4000000 jmp 00475A1F
<-- 跳过去就完了!
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0047591D(C)
:0047592B 8BF0 mov esi, eax <-- 姓名的长度地址到esi
:0047592D 81E6FF000000 and esi, 000000FF
:00475933 85F6 test esi, esi
:00475935 7C22 jl 00475959
:00475937 46 inc esi
:00475938 8D4D9B lea ecx, dword ptr [ebp-65]
我这里的dword ptr[ebp-65]的内容是: 08677578696e676865... ...(g67,u75,x78,i69,n6e,g67,h68,e65)此时设姓名长度为K=S0姓名(ASCII值)为S1,S2,S3,...,S(k-1),S(k),设这个字串为S(n),则本例中S0=08,S1=67,... ...S(k-1)=68,S(k)=65 ...
:0047593B 8D9DD1FEFFFF lea ebx, dword ptr [ebp+FFFFFED1]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475957(C)
开始计算第一步:
:00475941 33C0 xor eax, eax <-- 置零
:00475943 8A01 mov al, byte ptr [ecx] <-- S(n)送入al,n=0~9
:00475945 BF0A000000 mov edi, 0000000A <-- 送常数0a
:0047594A 33D2 xor edx, edx <-- 置零
:0047594C F7F7 div edi <-- eax=eax/edi,edx=eax mod edi
:0047594E 80C230 add dl, 30 <-- 余数+30
:00475951 42 inc edx <-- ?,为何不直接加31?
:00475952 8813 mov byte ptr [ebx],dl <-- dl 保存起来,设为D(n),n=1~9
:00475954 43 inc ebx <-- 为下一次保存指定空间
:00475955 41 inc ecx <-- +1,字左移,Sn=S(n+1)
:00475956 4E dec esi <-- -1,为零时变换结束
:00475957 75E8 jne 00475941
这个过程通过S(n),得到一组数据D(n):D1,D2,...D(k),D(k+1)
D(n)=S(n-1) mod oa + 31 。例如本例中: D5=S(5-1) mod 0a + 31=36
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475935(C)
|
:00475959 33C9 xor ecx, ecx
:0047595B 8A4D9B mov cl, byte ptr [ebp-65]
:0047595E 83F901 cmp ecx, 00000001
:00475961 7C1A jl 0047597D
:00475963 8D840DD1FEFFFF lea eax, dword ptr [ebp+ecx-0000012F] <-- 这个地址就是刚才最后计算出的Dn,因为有dec eax所以取数据是从后向前。
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0047597B(C)
| 开始计算第二步:
:0047596A 8A559B mov dl, byte ptr [ebp-65]
:0047596D 2AD1 sub dl, cl <-- cl=k
:0047596F 03D2 add edx, edx <-- 相当于乘以2
:00475971 8A18 mov bl, byte ptr [eax] <-- D(n),(n=9~1)
:00475973 02D3 add dl, bl
:00475975 8810 mov byte ptr [eax], dl <-- 设此时dl=R(n),得到R(n),(n=1~8)
:00475977 49 dec ecx
:00475978 48 dec eax
:00475979 85C9 test ecx, ecx
:0047597B 75ED jne 0047596A
这个过程通过Dn,得到Rk,(k=1~9)=(姓名长度递增)*2+Dn,<n=9~1>从0递增,递增到(长度-1)结束。
**#*#*#*#*#*#*#*#**
1. n=9~1,等指定了范围的值是我这里实际的标注,是为了读起来方便。
2. Dn,(n=9~1)表示Dn依次为d9,d8,d7,... ...d2,d1。 Sn,(n=1~9)表示Tn依次为T1,T2,T3,... ...T8,T9。其余的依此类推。
**#*#*#*#*#*#*#*#**
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475961(C)
|
:0047597D 8A459B mov al, byte ptr [ebp-65]
:00475980 3C06 cmp al, 06 <-- 姓名长度是否等于6个,不够6个,下面有办法,嘿嘿,自己研究吧,反正我的姓名大于6个!嘿嘿!
:00475982 7330 jnb 004759B4
:00475984 33C9 xor ecx, ecx
:00475986 8AC8 mov cl, al
:00475988 41 inc ecx
:00475989 83F906 cmp ecx, 00000006
:0047598C 7F26 jg 004759B4
:0047598E 8D9C0DD0FEFFFF lea ebx, dword ptr [ebp+ecx-00000130]
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004759B2(C)
| 姓名长度少于6个时的解决方法:自己研究吧!
:00475995 33C0 xor eax, eax
:00475997 8A03 mov al, byte ptr [ebx]
:00475999 83C002 add eax, 00000002
:0047599C 83E830 sub eax, 00000030
:0047599F BE0A000000 mov esi, 0000000A
:004759A4 99 cdq
:004759A5 F7FE idiv esi
:004759A7 80C230 add dl, 30
:004759AA 885301 mov byte ptr [ebx+01], dl
:004759AD 41 inc ecx
:004759AE 43 inc ebx
:004759AF 83F907 cmp ecx, 00000007
:004759B2 75E1 jne 00475995
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00475982(C), :0047598C(C)
| 姓名长度等于多于6个了:
:004759B4 B906000000 mov ecx, 00000006
:004759B9 8D85D7FEFFFF lea eax, dword ptr [ebp+FFFFFED7] <-- 取后6位,就是r3~r8
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004759EA(C)
| 开始计算第三步:
:004759BF B206 mov dl, 06
:004759C1 2AD1 sub dl, cl <-- 004759B4行使得cl=6
:004759C3 03D2 add edx, edx <-- edx=(06-cl)*2
:004759C5 8D1452 lea edx, dword ptr [edx+2*edx] <-- 这行复杂一些,我给大家分解一下:
edx=(06-cl)*2+[(06-cl)*2]*2
edx=(06-cl)(2+2+2)
edx=6*(6-cl)
因此edx依次=0,6,12,18,24,30(要转换成十六进制)
:004759C8 8A18 mov bl, byte ptr [eax] <-- 就是r3~r8
:004759CA 02D3 add dl, bl <-- edx=6*(6-cl)+r
:004759CC 8810 mov byte ptr [eax], dl
:004759CE 8A10 mov dl, byte ptr [eax]
:004759D0 80FA7E cmp dl, 7E <-- 计算出的值是否有无效的字符(有效的ASCII值20h-7eh,我想是一键就可以用键盘输入的常用字符的ASCII值)
:004759D3 7605 jbe 004759DA <-- 小于或等于转移
:004759D5 80EA78 sub dl, 78 <-- 有就修正
:004759D8 8810 mov byte ptr [eax], dl
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004759D3(C)
|
:004759DA 8A10 mov dl, byte ptr [eax]
:004759DC 80FA21 cmp dl, 21 <-- 计算出的值是否有无效的字符
:004759DF 7305 jnb 004759E6 <-- 大于或等于转移
:004759E1 80C221 add dl, 21 <-- 有就修正
:004759E4 8810 mov byte ptr [eax], dl
上个过程如果正常(不进行修正),应该得到如下字串。r3,6+r4,c+r5,12+r6,18+r7,1e+r8。又计算出了6个数,是什么呢?看下面吧!
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004759DF(C)
|
:004759E6 49 dec ecx
:004759E7 48 dec eax
:004759E8 85C9 test ecx, ecx
:004759EA 75D3 jne 004759BF
:004759EC C685D8FEFFFF00 mov byte ptr [ebp+FFFFFED8], 00
:004759F3 C60548D3470001 mov byte ptr [0047D348], 01
:004759FA B906000000 mov ecx, 00000006
:004759FF 8D85D2FEFFFF lea eax, dword ptr [ebp+FFFFFED2]
<-- 在[eax]可看见真码,[eax]是指向堆栈中的地址。
:00475A05 8D9537FFFFFF lea edx, dword ptr [ebp+FFFFFF37]
比较在下面:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475A1D(C)
逐个注册码进行比较:
:00475A0B 8A18 mov bl, byte ptr [eax] <-- 取真码
:00475A0D 3A1A cmp bl, byte ptr [edx] <-- bl与假注册码字符比较
:00475A0F 7409 je 00475A1A <-- 不相等就不跳,注册就结束了。
:00475A11 C60548D3470000 mov byte ptr [0047D348], 00 <-- 向[0047D348]写入比较结果,与00475B95 行相呼应
:00475A18 EB05 jmp 00475A1F <-- 注册出错了
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475A0F(C)
| 比较相同时来到这里:为下一步作准备
:00475A1A 42 inc edx <-- 假码左移一个字符
:00475A1B 40 inc eax <-- 真码左移一个字符
:00475A1C 49 dec ecx <-- 注册码长度-1
:00475A1D 75EC jne 00475A0B <-- 还有注册码就再比较
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00475926(U), :00475A18(U)
| 比较结束了
:00475A1F 33C0 xor eax, eax
:00475A21 5A pop edx
:00475A22 59 pop ecx
:00475A23 59 pop ecx
:00475A24 648910 mov dword ptr fs:[eax], edx
:00475A27 68445A4700 push 00475A44
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00475A42(U)
|
:00475A2C 8D85C8FDFFFF lea eax, dword ptr [ebp+FFFFFDC8]
:00475A32 BA02000000 mov edx, 00000002
:00475A37 E824E1F8FF call 00403B60
:00475A3C C3 ret
... ... ... ...
下面是我计算出的注册信息:
姓名:“guxinghe”;注册码:“`\MJ**”(因为一些原因,后两位用“*”代替).
注册信息不管正确与否,都保存在c:\windows\five99。ini中。删除其中的内容可以重新注册。
最后总结一下注册码的计算方式:
第一步:设k=姓名长度=S0(中文姓名注意长度);姓名(ASCII值)为S1,S2,S3,...S(k-1),S(k),组成一组数字串:S0,S1,S2,...,S(k-1),S(k),(只讨论6≤k≤64,除此,程序给出了变换的方法)
第二步:计算出中间值:D(n),D(n)=S(n-1) mod oa +31即以下字串:
D1=S(1-1) mod oa +31,
D2=S(2-1) mod oa +31,
D3=S(3-1) mod oa +31,
... ...
D(n-1)=S[(n-2)-1] mod oa +31,
D(n) =S[(n-1)-1] mod oa +31,
D(n+1)=S(n) mod oa +31.
第三步:计算出中间值:R(n),R(n)=(n-1)*2+D(k-n+2),k=姓名长度,即以下字串:
R1=(1-1)*2+D(k-1+2),
R2=(2-1)*2+D(k-2+2),
R3=(3-1)*2+D(k-3+2),
... ...
R(n-2)=[(n-2)-1]*2+D[k-(n-2)+2],
R(n-1)=[(n-1)-1]*2+D[k-(n-1)+2],
Rn=(n-1)*2+D(k-n+2).
第四步:得到每个注册码Z:
Z1=1E+R(k-0),(写成R(k-0)是为了看着工整一些)
Z2=18+R(k-1),
Z3=12+R(k-2),
Z4=0c+R(k-3),
Z5=06+R(k-4),
Z6=00+R(k-5).(21≤Z≤7e,如不在范围内按照-78,+21修正。)
:-)数学不好,表达式不会写了,好在数目少还可以列举出来.要是一个keyfile,非累吐血不可.
第五步:将上面的ASCII值转换成字符,就是注册码了。
要想爆破可以修改:
mov byte ptr [0047D348], 00,为:
mov byte ptr [0047D348], 01,共两处。
**#*#*#*#*#*#*#*#**
小试验 用编辑工具修改:
00475A83 803D48D3470001 cmp byte ptr [0047D348], 01 为:
00475A83 803D48D3470000 cmp byte ptr [0047D348], 00,看一看会发生什么情况! <--你不是注册的版本,但是也没有输入注册码的地方。
**#*#*#*#*#*#*#*#**
把上面的公式花了两个半天的时间最后总结为:
Z(m)=55+2k-8k+S(m) mod 0a, 21<Z(m)<7e,不在此范围需要修正一下.
解释一下:Z(m)是要计算的第几个注册码的ASCII值。(1≤m≤6),k是姓名长度,S(m)是第m个姓名ASCII值.由此可见,注册码的计算只用到前6个姓名的字符,(不足6个的,程序给出了变换的方法,不在这里讨论);还与整个姓名长度有关.只要前6个姓名的字符相同,长度相等的注册名计算出的注册码是相同的!
我想:作者在源代码的计算方法中,肯定不是这个样子!--- ---这个程序好多年了--- ---
注册机不会写!!!上面的公式顶替一个吧!!!(这样的注册机装在兜里就可以拿走)!!!
附件five.rar
linf 的第一篇算法分析。
【版权声明】 本文纯属技术交流,转载请注明作者和看雪学院,并保持文章的完整,谢谢!附件:five.rar
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)