老文章档案 · 2003 年

中级实例教程—之疯狂单词v1.70 算法分析

破解教程 · 算法分析 · 2003-07-15 · 作者:afanty 人气: · 出处:赢政天下整理收藏)

前言:

俺学破解其实也只有不到半个月的时间,之所以敢到这里班门弄斧写“中级”教程,主要是发现各种教程大多在重复入门知识,广大菜鸟缺乏进一步的指导,抱着“俺不挨臭鸡蛋谁挨臭鸡蛋”的信念,希望能够给菜鸟同志们深入学习带来一点点启发,如此足矣。

一、研究对象简介:

《疯狂单词》是俺收集到的最好的背单词共享软件,我想任何想钻研技术的人都会发现具备读懂 e文资料的能力是相当重要的,而学好 e文必须过词汇关,这也是俺要拿《疯狂单词》开刀的初衷,目前最新的版本是v1.7x,用google 可以找到很多提供下载的地方,建议下载语音压缩版,20兆,当然如果带宽不是问题,110 兆的语音清晰版发音会更好一些。

《疯狂单词》需要重新启动程序来验证注册码,好像是从“utraledit”学来的,这种方式对“爆破一族”有一定的防范能力,因为程序不再局限于在“请输入注册码”和“恭喜注册成功”之间对注册码进行验证,它可以在任何地方对注册码进行突击检查,一旦发现注册码非法,随时取消已注册状态。所以即使爆破注册成功,爆破者总是无法判断程序中是否还有地方埋有不知道什么时候会引爆的“地雷”。

《疯狂单词》不是用 注册码 = f(用户名) 的方式将正确的注册码算出来,再与输入的注册码做比较,而是构造了一个隐函数算法,程序总是判断 F(注册码,用户名)的值是否合法,这样,正确的注册码永远不会在程序的计算过程中出现,对于掌握了在寄存器和内存中找注册码这种初级技术的菜鸟有很好的防范作用。

《疯狂单词》运用了一些反跟踪的手段,特别是针对 bpx +静态地址的断点和 bpm断点,比如你跟踪到了验证注册码的函数的地址:0040c660,下 bpx 0040c660, 会发现下一次中断时0040c660处的代码变成了“INVALID”; 再比如你跟踪到存放假码的地址:024ab100,下 bpm 024ab100, 会发现程序会用各种方法反复将该地址里的值转移到其他地址去,甚至会分批几个字节几个字节地偷运,或者写进注册表然后再读进另一个地址,等等。

二、总体破解思路:

不管程序如何验证注册码,在验证之前它总要知道你输入的假码是什么,所以在验证之前它必然会完成两个动作:

a) 从注册界面读入假码

b) 对假码进行预处理,可能只是简单的格式验证也可能是做一个变换:变码 = f(假码),然后将处理过后的假码或变码放到一个内存地址,注册码验证函数一定会访问这个地址。

所以跟踪假码就成为了找到验证函数的一把钥匙:

bpx getwindowtexta (或 bpx getdlgitemtexta) do "d esp->8"

中断后esp存放的是函数执行完毕后的返回地址,因为32位系统中每个地址占4个字节空间,所以此时esp->4是传递给函数的第 1个参数,也就是调用函数前最后一个压栈的参数的地址,同理esp->8是第二个参数,如此类推。函数getwindowtexta的第二个参数就是函数取得的字符串的存放地址,也就是我们要跟踪的假码的地址,d esp->8可以得到这一地址,这个地址不是固定的,假设为xxxxxxxx。

bpmd xxxxxxxx rw

密切关注程序对这一地址的任何读写操作,如果程序将该地址中的假码复制到另一地址,马上 bpmd 另一地址,如果发现程序调用regsetvalueexa函数将该地址中的假码写入注册表,记下调用之前倒数第二个压栈的值(第二个参数),假设为aaaa(dword,四字节),马上下 bpx regqueryvalueex if *(esp->8)==aaaa do "d esp->14",如果程序从注册表将假码读入另一地址,则马上 bpmd 另一地址。这样你肯定会在假码的带领下找到程序的注册码验证函数!

由于程序往往会将 F(假码,用户名)分解成不止一个二元方程,即:

f1(假码) = f(用户名)

f2(假码) = f(用户名)

......

fn(假码) = f(用户名)

所以第一次跟踪到的验证函数一般只包含大F 算法的一部分,所以只能初步了解该算法。程序在完成第一次验证之后可能会告诉你“注册码正确,请重新启动程序完成注册”,请千万不要被它迷惑,你必须继续让“钥匙”带领你去找其他的验证函数,防止程序“偷偷地”做第二次、第三次甚至第n 次验证。即使关闭程序也要坚决搞清楚它将钥匙放到哪里保存起来了,防止程序重新运行后再次验证。在跟踪假码时程序可能会反复对假码进行变换,每一次变换后都要继续跟踪变码,即使程序将变码写入注册表,也要看看它会不会再读出来再做验证,即使程序什么事都不做了等着你关闭程序,你也要看看在你点击了“X” 按钮之后程序有没有处理OnDestroy 消息响应函数再次变换或验证变码,总之要对假码及假码的一切变码 bpmd 到底!

程序重启时要采用前面提到的 bpx regqueryvalyeexa 加 bpm 手段继续跟踪“钥匙”,直到你对大F 算法有了足够的了解,能够推算出正确的注册码甚至写出注册机为止。

三、验证算法分析:

跟踪到第一次验证函数为0040b880,代码分析如下:

:u 0040b880 L 271

001B:0040B880 PUSH FF-----------------------------------------------------

001B:0040B882 PUSH 00467DFB

001B:0040B887 MOV EAX,FS:[00000000]

001B:0040B88D PUSH EAX

001B:0040B88E MOV FS:[00000000],ESP 这一段是函数对调用环境的

保存,不用深究

001B:0040B895 SUB ESP,000001A4

001B:0040B89B PUSH EBX

001B:0040B89C PUSH EBP

001B:0040B89D PUSH ESI

001B:0040B89E PUSH EDI----------------------------------------------------

001B:0040B89F MOV ESI,00000008<---------循环控制变量,从假码的第9位开始

验证

001B:0040B8A4 MOV DWORD PTR [ESP+1C],00000000

001B:0040B8AC MOV EBX,00000001<---------循环控制变量,验证到第20位时回

到第2位继续验证

001B:0040B8B1 PUSH 0049141C-----------------------------------------------

001B:0040B8B6 LEA ECX,[ESP+14]

001B:0040B8BA CALL 00449D4A

001B:0040B8BF MOV ECX,00000064 将[ESP+24]之后的100个dword地址

001B:0040B8C4 MOV EAX,EBX 赋值为“1”

001B:0040B8C6 LEA EDI,[ESP+24] 将[ESP+24]这一地址记入EDI,这

001B:0040B8CA XOR EBP,EBP 一地址存放输入的注册码(假码)

001B:0040B8CC REPZ STOSD 的各位数字值,是程序在验证函数

之前做的

001B:0040B8CE MOV EDI,[ESP+000001C8] 再将从[ESP+28]到[ESP+64]这16

001B:0040B8D5 MOV EAX,00000008 个dword地址依次赋值为

001B:0040B8DA MOV ECX,00000005 “1975121885116918”,估计与

001B:0040B8DF MOV [ESP+40],EAX 作者生日有关,将用来作为加密

001B:0040B8E3 MOV [ESP+44],EAX 算法的一个密码表

001B:0040B8E7 MOV [ESP+60],EAX

001B:0040B8EB LEA EAX,[EDI+04]

001B:0040B8EE MOV EDX,00000009

001B:0040B8F3 MOV [ESP+30],ECX

001B:0040B8F7 MOV [ESP+48],ECX

001B:0040B8FB PUSH EAX 注意这期间进行过一次压栈操作,所

以ESP的值发生过变化,对后面的

001B:0040B8FC LEA ECX,[ESP+14] [ESP+xx]的地址有所影响

001B:0040B900 MOV [ESP+000001C0],EBP

001B:0040B907 MOV [ESP+28],EBX

001B:0040B90B MOV [ESP+2C],EDX

001B:0040B90F MOV DWORD PTR [ESP+30],00000007

001B:0040B917 MOV [ESP+38],EBX

001B:0040B91B MOV DWORD PTR [ESP+3C],00000002

001B:0040B923 MOV [ESP+40],EBX

001B:0040B927 MOV [ESP+50],EBX

001B:0040B92B MOV [ESP+54],EBX

001B:0040B92F MOV DWORD PTR [ESP+58],00000006----------------------------

001B:0040B937 MOV [ESP+5C],EDX-------------------------------------------

001B:0040B93B MOV [ESP+60],EBX

001B:0040B93F CALL 0044A0F4

001B:0040B944 LEA EAX,[EDI+08]

001B:0040B947 LEA ECX,[ESP+10]

001B:0040B94B PUSH EAX

001B:0040B94C CALL 0044A0F4

001B:0040B951 LEA EAX,[EDI+14]

001B:0040B954 LEA ECX,[ESP+10]

001B:0040B958 PUSH EAX

001B:0040B959 CALL 0044A0F4

001B:0040B95E LEA EAX,[EDI+0C]

001B:0040B961 LEA ECX,[ESP+10] 这一段是将你输入的用户名、信箱

001B:0040B965 PUSH EAX 地址与软件的名称、版本号合并成

001B:0040B966 CALL 0044A0F4 为一个目标串,放入[ESP+10],

001B:0040B96B LEA EAX,[EDI+10] 程序实际上验证 F(注册码,目标串)

001B:0040B96E LEA ECX,[ESP+10] 目标串形如

001B:0040B972 PUSH EAX yourmail+wordslover+yourname+1.60

001B:0040B973 CALL 0044A0F4 看来作者对这一算法非常满意,从

001B:0040B978 LEA EAX,[EDI+1C] 1.6版沿用至今

001B:0040B97B LEA ECX,[ESP+10]

001B:0040B97F PUSH EAX

001B:0040B980 CALL 0044A0F4

001B:0040B985 LEA EAX,[EDI+20]

001B:0040B988 LEA ECX,[ESP+10]

001B:0040B98C PUSH EAX

001B:0040B98D CALL 0044A0F4

001B:0040B992 LEA EAX,[EDI+24]

001B:0040B995 LEA ECX,[ESP+10]

001B:0040B999 PUSH EAX

001B:0040B99A CALL 0044A0F4-----------------------------------------------

001B:0040B99F MOV ECX,[ESP+10]<---------将目标串地址写入ECX

001B:0040B9A3 XOR EAX,EAX<--------------EAX清零

001B:0040B9A5 MOV ECX,[ECX-08]<---------取目标串的长度

001B:0040B9A8 CMP ECX,EAX<--------------长度为零则跳转(目标串包含

wordslover1.60,长度怎么可

能是零?有病!)

001B:0040B9AA JLE 0040B9F1

001B:0040B9AC MOV EDX,EDI<--------------将假码数字表地址写入EDX

001B:0040B9AE MOV [ESP+18],EAX<---------将[ESP+18]作为从零开始的一个

临时变量

001B:0040B9B2 MOV EDX,[ESI*4+EDX+30]<---将假码当前位(ESI)的数字值

写入EDX

001B:0040B9B6 MOV [ESP+20],EDX<---------将该值写入[ESP+20]保存

--------------------------------------------------------------------------------

001B:0040B9BA MOV EDX,[ESP+10]<---------将目标串地址写入EDX

001B:0040B9BE MOV EDI,[ESP+18]<---------将变量[ESP+18]的值写入EDI

001B:0040B9C2 AND EDI,8000000F<---------变量[ESP+18] 除以16的余数

001B:0040B9C8 MOV DL,[EDX+EAX]<---------将目标串的当前位的ASC码写入DL

001B:0040B9CB JNS 0040B9D2<-------------若余数不为负值则跳转?[ESP+18]

的所有赋值动作都在掌握之内,怎

么可能大于80000000?有病!

001B:0040B9CD DEC EDI

001B:0040B9CE OR EDI,-10

001B:0040B9D1 INC EDI

001B:0040B9D2 MOV EDI,[EDI*4+ESP+24]<---根据变量[ESP+18]的值读入密码表

(19751218...)的相应位的数值写

入EDI

001B:0040B9D6 MOVSX EDX,DL<---------------将DL中的目标串当前位的ASC码值写

入EDX

001B:0040B9D9 IMUL EDI,EDX<--------------当前密码值与目标串当前ASC码值的

乘积

001B:0040B9DC ADD EDI,[ESP+20]<---------再加上当前假码数值

001B:0040B9E0 ADD EBP,EDI<--------------在EBP中累计这一结果

001B:0040B9E2 MOV EDI,[ESP+18]<---------将变量[ESP+18]的值写入EDI

001B:0040B9E6 INC EAX<------------------目标串的当前位+1

001B:0040B9E7 ADD EDI,ESI<--------------变量[ESP+18]+假码的当前位

001B:0040B9E9 CMP EAX,ECX<--------------目标串的当前位是否等于目标串长度

001B:0040B9EB MOV [ESP+18],EDI<---------更新变量[ESP+18]

001B:0040B9EF JL 0040B9BA<-------------循环直至目标串结束

这一段循环是针对假码的每一位p[i]计算一个值A[i] = f(p[i],目标串),(其中i=ESI)

设目标串为d,密码表为c,则 A[i] = {

int num = 0;

for(int j=0; j<d.lenth(); j++)

num = num + d[j]*c[j*i%16]+p[i];

return num;

}

--------------------------------------------------------------------------------

001B:0040B9F1 MOV EAX,ESI

001B:0040B9F3 AND EAX,80000007<---------EAX等于假码当前位除以8的余数

001B:0040B9F8 JNS 0040B9FF

001B:0040B9FA DEC EAX

001B:0040B9FB OR EAX,-08

001B:0040B9FE INC EAX

001B:0040B9FF JNZ 0040BA61<-------------余数不为零则跳转

001B:0040BA01 CMP ESI,13

001B:0040BA04 JNZ 0040BA10<-------------不为19则跳转,除以8余0怎么可能

等于19?有病!

001B:0040BA06 MOV DWORD PTR [ESP+18],00000000

001B:0040BA0E JMP 0040BA17

--------------------------------------------------------------------------------

001B:0040BA10 LEA ECX,[ESI+01]<---------ECX等于假码当前位+1

001B:0040BA13 MOV [ESP+18],ECX<---------存到临时变量[ESP+18]

001B:0040BA17 MOV EDX,[ESP+000001C4]<---字符串形式的假码的地址的指针写

入EDX

001B:0040BA1E MOV EAX,ESI<--------------EAX等于假码当前位

001B:0040BA20 MOV EDI,[EDX]<------------字符串形式的假码的地址写入EDI

001B:0040BA22 CDQ<----------------------------EDX清零

001B:0040BA23 MOV ECX,[EDI-08]<---------假码长度写入ECX

001B:0040BA26 IDIV ECX<------------------在EDX里得到假码当前位(EAX)除

以假码长度(ECX)的余数,还不就

是假码的当前位?毛病!

001B:0040BA28 MOV AL,[EDI+EDX]<---------将假码当前位的ASC码值写入AL

001B:0040BA2B MOV EDI,0000000A<---------EDI等于10

001B:0040BA30 MOV [ESP+17],AL<----------将假码当前位的ASC码值写入临时变

量[ESP+17]

001B:0040BA34 MOV EAX,EBP<--------------当前A值,参见前述

A[i]=f(p[i],目标串)

001B:0040BA36 CDQ<----------------------------EDX清零

001B:0040BA37 IDIV EDI<------------------在EDX里得到当前A值(EAX)除以10

的余数

001B:0040BA39 MOV EAX,[ESP+18]<---------EAX=假码当前位+1

001B:0040BA3D MOV EDI,[ESP+000001C8]<---ESP+1c8+30是数值形式的假码的地址

001B:0040BA44 SUB DL,[EAX*4+EDI+30]<----将DL里的余数减去假码下一位的ASC

码值

001B:0040BA48 MOV AL,[ESP+17]<----------AL=假码当前位的ASC码值

001B:0040BA4C ADD DL,AL<----------------余数-假码下一位在加上当前位后的

结果

001B:0040BA4E MOV EAX,ESI

001B:0040BA50 PUSH EDX

001B:0040BA51 CDQ

001B:0040BA52 IDIV ECX 无用功,老毛病!

001B:0040BA54 MOV ECX,[ESP+000001C8]

001B:0040BA5B PUSH EDX

001B:0040BA5C CALL 0044A234<-------------将结果替换字符串形式假码的当前位

这一段是将假码的第9、17位做变换:

p[8] = p[9] + A[8]%10

p[16] = p[17] + A[16]%10

--------------------------------------------------------------------------------

001B:0040BA61 MOV EAX,ESI

001B:0040BA63 MOV ECX,00000007

001B:0040BA68 CDQ

001B:0040BA69 IDIV ECX

001B:0040BA6B TEST EDX,EDX

001B:0040BA6D JNZ 0040BA96

001B:0040BA6F CMP ESI,13

001B:0040BA72 JNZ 0040BA78

001B:0040BA74 XOR ECX,ECX

001B:0040BA76 JMP 0040BA7B

001B:0040BA78 LEA ECX,[ESI+01]

001B:0040BA7B MOV EAX,EBP

001B:0040BA7D MOV EDI,0000000A

001B:0040BA82 CDQ

001B:0040BA83 IDIV EDI

001B:0040BA85 MOV EAX,[ESP+000001C8]

001B:0040BA8C SUB EDX,[ECX*4+EAX+30]

001B:0040BA90 JNZ 0040BADA

累死了!这一段就不祥加注释了,反正就是验证第8、15位(i%7==0)的A值必须为零,

否则就JNZ 0040bada,验证失败!

--------------------------------------------------------------------------------

001B:0040BA92 INC DWORD PTR [ESP+1C]<---若A值为零则标志变量[ESP+14]+1

001B:0040BA96 INC ESI<------------------假码当前位+1

001B:0040BA97 CMP ESI,13

001B:0040BA9A JLE 0040BA9E

001B:0040BA9C MOV ESI,EBX<--------------若当前位为19(从0开始计算,19是

第20位),则当前位回到1

001B:0040BA9E LEA ECX,[ESP+10]

001B:0040BAA2 MOV DWORD PTR [ESP+000001BC],FFFFFFFF

001B:0040BAAD CALL 00449CDC

001B:0040BAB2 CMP DWORD PTR [ESP+1C],14

001B:0040BAB7 JL 0040B8B1<-------------循环直至标志为等于20,相当于把

所有工序重复10遍,因为ESI每从1

到19一次,仅有两次机会修改标志位

001B:0040BABD MOV AL,BL<----------------函数返回值置1,表示验证成功

001B:0040BABF MOV ECX,[ESP+000001B4]

001B:0040BAC6 POP EDI

001B:0040BAC7 POP ESI

001B:0040BAC8 POP EBP

001B:0040BAC9 POP EBX

001B:0040BACA MOV FS:[00000000],ECX

001B:0040BAD1 ADD ESP,000001B0

001B:0040BAD7 RET 0008

001B:0040BADA LEA ECX,[ESP+10]

001B:0040BADE MOV DWORD PTR [ESP+000001BC],FFFFFFFF

001B:0040BAE9 CALL 00449CDC

001B:0040BAEE XOR AL,AL<---------------函数返回值置0,表示验证失败

001B:0040BAF0 JMP 0040BABF

这一验证函数仅仅验证了假码的两位,然后又将假码的另两位做了10次变换,然后就恭喜你注册成功!所以必须继续跟踪变码,发现果然在你下达了关闭程序的指令后它在程序窗口从屏幕上消失的同时偷偷又调用了一个类似的验证函数,这次它用同样的方法验证变码的其中一位而将变码的另六位做了20次变换!重新启动程序后你会发现它在很多各地方再次进行验证和变换,经过推理可知如果你输入的注册码的每一位的A值都等于0,而且每一位在变换后都可以保持原值,则不管程序如何变换,该注册码永远都能通过验证!

所以。。。俺的注册机可以诞生了!

结语:

疯狂单词构造了一个巧妙的算法,要求注册码满足:

注册码 = F(注册码,用户名)

然后他每次验证注册码之后,只有正确的注册码可以保持不变,并通过下一次的验证。

但不足的是该作者的离散数学功底太差,他将大F分解成多个小f时仅仅采用了分段函

数的形势,函数表达式本身是一样的,仅仅是作用域不同而已(验证不同的位),假如能

够将大F分解成多个函数表达式不同的小f,通过跟踪小f分析其大F的算法将是非常困难的!

当然,衷心希望此文不要被疯狂单词作者看见,即使看见,也衷心希望他不会马上疯狂:)

预告:俺的下一篇教程将是《楚汉棋缘》,据说是很不错的象棋共享软件,敬请关注!

(出处:赢政天下整理收藏)

相关文章:

·语音界面2.0算法分析及注册机源码!

·侠客系统修改器1.21破解手记--算法分析

·中文拨号上网计时计费器 V4.12注册算法分析

·ClockWise 3.22e注册码算法分析

·CoolClock V1.02注册算法分析

·Directory Scanner v1.5 注册算法分析

·Disk Chief 1.2 简单注册算法分析

·EffeTech HTTP Sniffer 3.2注册算法分析

·eLib2.01算法分析

·EZ MP3 Recorder 1.15 注册算法分析

相关软件:

→ 特别推荐

→ 热点TOP10

我为人人《《==》》人人为我

关于我们 |

帮助(?)

| 广告服务 |

版权声明 |

商业合作 |

发展历程 |

加盟我们 |

联系我们

Copyright © 1999-2003

winzheng.com All Rights Reserved.

繁体版:由华琦数码技术提供

Powered

by:MeSky.Net

2003-2004 年,宽带刚刚普及,下载站的文章区是许多人学电脑的第一课堂。
🕰 → 🤖 从那时到现在

当年一篇教程能让一台电脑焕然一新;今天的效率工具换成了 AI。 想知道当下最可靠的 AI 模型怎么选?看 赢政指数——我们用了 28 年的选软件严谨,现在用来评测大模型。

档案性质声明:本页为赢政天下(1998-2011)老文章的历史档案复原, 内容反映当年发表时的原貌——包括当年的技术表述、软件版本与观点,部分方法在今天已不适用或不合规,仅作互联网历史记录与研究用途, 不提供任何形式的下载。原始数据经由公共网络存档(Internet Archive)复原。发现问题请联系我们
← 返回老文章档案 · 软件史档案馆 · 赢政天下站史