第八章--注册码是怎样炼成的 !4w|!*o
你应该明白的是,并不是所有的软件作者都像你想象并希望的那笨 没有人愿意自己的软件被别人在调试器中用一条d指令就能找到正确的注册码...要是那样的话还出来搞什么? _L<"u#`n
前边儿我们讲的查找软件注册码的方法是有针对性的,必须保证的是该软件使用的是明码比较,这样的话,我们只需找对地方,一个d指令就成了。那既然有明码比较这个词,就不难猜出还有相应的非明码比较...非明码比较也比较容易理解,就是软件比较两个注册码的方法不同而以,并不是计算出正确的注册码后就与用户输入的进行比较,它可能会采用每计算出一位就与注码中的相应位比较一次,一但发现与用户输入的不同,就提示出错等等等等... 798qYP\2
遇到这样的软件,我们其实也可以找到其相应的注册码,但有点儿惨,要一位一位的计下来...但是如果人家不给你面子,一但计算出某位不正确就跳走的话,那你怎么办?所以,国民想致富,种树是根本...NG!所以遇到这种软件,我们就只有对其算法进行分析,并做出注册机才是唯一的方法(如果你想写注册机的话)... p+cOqV'X
你要明白,就算我们能找到那些采用明码比较的软件的注册码,原因也仅仅是因为其采用的是明码比较,所以我们没有什么值的高兴的地方,我们真正要做的,并不是找到一个注册码而以...当然如果你刚入门,那对你的提高还是很有帮助的。我们Crack一个软件的最终目的,是对其进行相应的分析,搞懂它的注册算法并写出注册机,这样才算是成功的Crack了一个软件,成功后的心情是难以表达的!就像你便秘了多天后一下子排了出来一样 ^_^,呵呵这个比喻虽然粗俗,但是你可以想象一下,对一个软件进行仔细的分析,最后一下把它的算法给搞明白了,那种感觉...我深信不疑的认为有一天你也能体会的到,偶等你 _U ')0
相信你以前看过那些高人大虾的关于软件注册算法分析的文章,同时也相信你有过试图跟踪分析某软件的举动,虽然后来的结果另人不太满意 n^vT[>&V
其实分析一个软件的注册算法,这其中包括了一些技巧性方面的东西以及必要的经验,很难想象一个连调试器的使用都还没掌握的人试图去分析一个软件会是怎样一个场面...嘿嘿,偶是见过的 使用调试器并不难,但那并不意味着你就能去分析一个软件了,见CALL就追这样的举动可不是偶一个人有过的经历,本章我尽量给你说明适当的分析方法。 QDKbV9y`
相信大家都有不在父母陪同下独自使用调试器的能力以及看懂大部分汇编指令的能力了吧,那就够了!我们开始... duwh^ah
正式开始今天的正题,我来举两个例子,相信这两个例子都有足够的表达能力,最起码比我们家楼下那个卖油条的表达能力要强多了... E"Jj+#5i
好的,首先,我们还是请出我们的那位老朋友吧 嘿嘿,在此,偶向CHINAZIP(中华压缩)v7.0的作者表示我内心最真诚的歉意!相信我用这个老版本的中华压缩不会给您带来经济上的麻烦... n?|C8
通过前边儿两章的讲解,我们已经把这个软件大体上给搞明白了,并且也追出了其相应的注册码。而我们今天的目的是对其注册算法进行分析,并写出注册机!这个软件的注册算法其实也比较简(并且存在Bug)用它来当例子,很能说明情况... 24zPa5I'
好的,我们开始,前边儿追注册码的时候我们就已经知道了其用于计算正确注册码的关键CALL的所在位置为004f4dde,我们用TRW2000来对其进行分析!(鉴于目前大部分教程中仍以TRW2000为主,而且这个是大多数菜鸟都会用的调试器,偶就用这个调试器来做具体讲解) z.@r}9ab
先启动CHINAZIP,帮助--注册(所以我才说这个软件非常适合写教程用嘛,注册后仍然中以再次注册)输入注册名Suunb[CCG],注册码19870219。之看按Ctrl+N呼出TRW2000,下断点bpx 004f4dde,F5返回。 U>oo gU
接着就按确定吧,呵呵,被TRW2000拦到了。通过前边两章的分析,我们以经知道了004f4dde处的这个CALL用于计算正确的注册码,所以我们直接按F8跟进吧!注册码的算法,就包涵在这个CALL中,把它给分析透了,我们也就能弄明白软件的注册码是怎样生成的了。但是要怎么分析呢?这是一个比较严肃的问题,面对那一堆堆的指令,我不知道你是怎么想的,反正我第一次时是觉的找不着北,我怎么哪些重要哪些不重要呢?再说了,里面又包涵了那么多CALL,我还要一个一个地追进去看看? ?VG5W@CP
呵呵,这就是我说的技巧所在了。其实也没什么可怕的,只要你汇编不是问题,就行了。我们首先可以先把这个计算注册码的CALL从头到尾执行一遍,搞明白其中大概的跳转以及其中某些CALL的作用,hehe~~你可以执行过一个CALL后就看一下各个寄存器的变化情况(如果寄存器中的值改变了,颜色就会变)如果某寄存器的值在CALL过之后改变了,我们就可以看一下其包含的值是何类型,如是内存地址就用d指令看一下,如是数值就看一下是不是得到你输入注册名或注册码的位数等等,这样的话就可以淘汰下来一大部分的CALL,因为有许多CALL的作用只是把注册名或注册码装入到内存中的某个地址或者得到注册名(注册码)的位数或注册码某一位的ASCII码,对与这些,我们不必深究。还是推荐你用Ollydbg,执行过一条指令后很多信息都可以看到 好的,我接着说,按F8追入CALL之后先大概走一遍...我给出追入后的反汇编代码,并给出注释,相应的分析看后面... 9oDum
0167:004f4fac push ebp <--F8跟入后的第一条指令 r;8tOEEB
0167:004f4fad mov ebp,esp 4%!h]"L
0167:004f4faf push byte +00 nO30f3C
0167:004f4fb1 push byte +00 {1!zM? h'
0167:004f4fb3 push byte +00 \ =g#pru
0167:004f4fb5 push byte +00 2a"!X2H
0167:004f4fb7 push byte +00 Ga`"J$cHM
0167:004f4fb9 push byte +00 I*uJA H2w
0167:004f4fbb push byte +00 Mi/)B7As*^
0167:004f4fbd push ebx ttBdt)`
0167:004f4fbe push esi |OID\0}k
0167:004f4fbf push edi H)}U"lC0<
0167:004f4fc0 mov [ebp-08],ecx ;AmtaO }2O
0167:004f4fc3 mov [ebp-04],edx : W~=Vh
0167:004f4fc6 mov eax,[ebp-04] u5rN!~I1
0167:004f4fc9 call 004041c0 ;qfj>>Pv
0167:004f4fce xor eax,eax 7 P,dONs5j
0167:004f4fd0 push ebp gXfy'Sr&
0167:004f4fd1 push dword 004f5097 4i%$.i7K
0167:004f4fd6 push dword [fs:eax] ]cKPx7"?9
0167:004f4fd9 mov [fs:eax],esp \O&lxK U
0167:004f4fdc xor esi,esi JSW f8 n
0167:004f4fde lea eax,[ebp-0c] y{.b. u
0167:004f4fe1 mov edx,[ebp-04] Y&)7[C
0167:004f4fe4 call 00403e24 <--此CALL过后用于得到用户输入的注册名 +|\;S\#
0167:004f4fe9 mov eax,[ebp-0c] <--将得到的注册名的地址装用eax寄存器 [TNuD~$jh|
0167:004f4fec call 0040400c <--此CALL用于得到用户输入的注册名的位数,并将其放入eax中 Qe NU{QOL
0167:004f4ff1 mov edi,eax <--将注册名的位数装入edi中 ~:?@\.(z
0167:004f4ff3 test edi,edi <--对edi进行测试 N+N<bS3
0167:004f4ff5 jng 004f5051 <--如果edi中的值为0就会跳走 -2{qs-AA
0167:004f4ff7 mov ebx,01 <--ebx置1,用于后面的运算 ?*/xJ%
0167:004f4ffc mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,此时将其付于eax w90=='3~
0167:004f4fff mov al,[eax+ebx-01] <--eax中此时装的是注册名的内存地址,加上ebx中的值再减去01,用于得到注册码中的相应位的字符,比如说我们第一次执行到这里的时候ebx中装入的是01,再减去01后得到的值其实还是eax本身,这样就能得到注册名中的第一个字符了,而执行到后边再跳回来时ebx会加上1,所以就能得到下一个字符了... I'D*Ch9u
0167:004f5003 call 004f4f60 <--这个CALL很重要,后面会说明我们是怎样知道它很重要的 &>UJ|$-
0167:004f5008 test al,al <--在这里我们会发现一个测试运算,对象是al,而al在前边CALL之前刚装入了注册名中的某一个字符,所以我们可以断定上面的那个CALL会对得到的字符做上一些手脚,待会儿我们再跟入... CH9{fa\X
0167:004f500a jz 004f5031 <--如果al中装的是0就跳到004f5031处,而al中的值会被004f5003处的那个CALL所改变 iz\6:4S
0167:004f500c lea eax,[ebp-18] YI>eCSXB
0167:004f500f mov edx,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,此时装入edx中 G4"xvKDB
0167:004f5012 mov dl,[edx+ebx-01] <--跟前边儿004f4fff处的指令道理相同,得到注册码中的当前参加运算的字符 5*;]zLLx
0167:004f5016 call 00403f34 <--不重要!! l-<?5
0167:004f501b mov eax,[ebp-18] 9":FcM6
0167:004f501e lea edx,[ebp-14] 'Cpnp%qGO
0167:004f5021 call 004088ac <--不重要!! 2s? g/x
0167:004f5026 mov edx,[ebp-14] @wU V)@
0167:004f5029 lea eax,[ebp-10] Wm6}$-
0167:004f502c call 00404014 <--该CALL同样比较重要,其作用是这样的,如果当前参加运算的字符在前边004f5003的CALL里进行运算之后符合了要求(符合要求后al会被置非[color=blue]0值)那么在004f500a处的跳转将会失去作用,而执行到这里后该CALL会将当前的这个符合要求的字符保存到00D3B3C4处(内存)!!后边儿会再详细说明 (^RYmC@
0167:004f5031 cmp ebx,byte +01 <--用此时ebx中装的值减去1 =tlXyPTq
0167:004f5034 jz 004f5040 <--如果为零,也就是说此时计算的是注册名中的第一个字符的话就跳到004f5040处 YR2UFrH
0167:004f5036 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址,该指令将注册名的内存地址装入eax中 Ps-p!5
0167:004f5039 movzx eax,byte [eax+ebx-02] <--用于得到上一个参加运算的字符 J~ Beky
0167:004f503e jmp short 004f5046 <--无条件跳转到004f5046处 z 7gb%WTBl
0167:004f5040 mov eax,[ebp-0c] <--ebp-0c中装的是注册名的内存地址 8G$cCv*>
0167:004f5043 movzx eax,byte [eax] <--得到注册名的第一个字符 .*u"5~
0167:004f5046 lea esi,[esi+eax*4+a8] <--!!!这一条指令就是关键所在,后面会说明的!!!此指令先得到本轮参加运算的字符的ASCII码,然后乘以6,之后再加上a8(即十进制数168,呵呵,可以理解)同时再将这个字符计算得到的值与前面已经运算过的字符的值的和相加! v>w&iga.
0167:004f504d inc ebx <--ebx加1,用于得到注册码的下一个字符 >en.dh 'S
0167:004f504e dec edi <--edi减1,edi中装的是注册码的位数 7UpBEQ'a\^
0167:004f504f jnz 004f4ffc <--不为零就跳到004f4ffc处开始对下一个字符进行运算...也就是说每计算完一个字符就将edi减去1,直到其为0也就是所有的字符全参加过运算为止。 s}Yyco<
0167:004f5051 lea edx,[ebp-1c] <--把装注册码后半部分的地址装入edx,传给下面的CALL < 6k]>
0167:004f5054 mov eax,esi <--将前面计算的注册码的后半部分的值装入eax中 L3b+f# 6
0167:004f5056 call 00408c70 <--将前面计算得到的注册码后半部分的值转换为十进制,并装入ebp-1c中 I_{GF+1?:
0167:004f505b mov ecx,[ebp-1c] <--epb-1c中装的是注册码的后半部分 c02e$1Og
0167:004f505e lea eax,[ebp-0c] -3H)m1
0167:004f5061 mov edx,[ebp-10] <--ebp-10中装的是注册码的前半部分 ^vb1jeKv
0167:004f5064 call 00404058 <--该CALL用于将前后两部分注册码合并置一起,合并后的注册码会存放置ebp-0c处 P'.EmNA
0167:004f5069 mov eax,[ebp-08] )?%N Sf"
0167:004f506c mov edx,[ebp-0c] P5;urGe;QV
0167:004f506f call 00403de0 fc9Oh|
0167:004f5074 xor eax,eax i { )ry(
0167:004f5076 pop edx C18.i9OZ
0167:004f5077 pop ecx $AgNCa8%)[
0167:004f5078 pop ecx {1F+SV[
0167:004f5079 mov [fs:eax],edx Zgh!S06@
0167:004f507c push dword 004f509e zh[+8Zl*
0167:004f5081 lea eax,[ebp-1c] d!g{fo>as
0167:004f5084 mov edx,05 =f=`8d
0167:004f5089 call 00403db0 <c0] {uM
0167:004f508e lea eax,[ebp-04] MM'UZ~jC
0167:004f5091 call 00403d8c !)D}0W^i]
E{XgOuuzV
呵呵,看了我加了注释后的代码是不是好理解多了?你也许会问,你怎么知道那些CALL是做什么的?我前边儿不是说过方法了吗?我们先大概地过上一遍,看一下各个跳转,然后再大大概的看一下各个CALL的作用...你以为上面这些注释是我过一遍之后就能写出来的?你多过几遍,心中就会有了个大概... 61o`nu
你现在在想些什么?众人:站着说话不腰痛...我晕~~ -cf\s>n~r
呵呵,我尽量说的仔细一些: }m!h3Bo[
其实很好理解的,我们追了进来,之后大概的看一下那些个CALL,其中一些稍有经验的一看就知道是用来得到注册名或长度什么的...之后我们再从头跟一遍...跟到004f4ff3处,会发现其会对注册名的位数进行一个比较,看用户是否输入了注册名...(也就是说如果edi中装的注册名的位数不大于0,即没输入就跳走,一会儿我会在后面说一下关于这点儿的Bug)而后在004f4ffc处我们会发现软件会得到注册名的内存地址,接下来的一条指令一看就知道是用来得到注册名中的各个字符的,嘿嘿,见到这类指令,马上就向下看吧,找一下下边儿哪条指令会再跳回到004f4ffc处...呵呵,我们会在004f504f处发现目标,好了,现在我们就知道了从004f4ffc到004f504f之间的那些个指令会对注册名中的每一个字符进行计算... $Znge.~r
呵呵,也就是说软件从004f4ffc处开始先是得到注册名中的第N位字符,然后进行一系列的运算,之后执行到了004f504e处时把先前先到的注册名的位数减去1然后看其是否为0,不为0就再跳到004f4ffc处,然后得以注册名的N+1位再来进行计算。此举的目的就是为了看注册名的各位是否都被计算过了,如果不为0就说明还没有计算完,呵呵,很简单的道理嘛,edi中装的是注册名的位数,第计算过一位后就将其减1,减完了,注册名的各位也就都参加了运算... M'z} Rov1
好的,我们再来看具体的算法部分: n|# N ~E
在004f4ff5的跳转,如果你输入了注册名,其就不会跳走...偶输入的是Suunb[CCG],好的,此时会继续执行到004f4ff7处,该指令对ebx进行初始化...给它付1,然后在004f4ffc处时会将ebp-0c中装的注册名的内存地址装入eax中,接着的004f4fff处用于得到注册名的第一个字符,并将其装入al。想象一下,eax中装的是注册名的内存地址,从该地址开始连续10个内存单元是我们输入的注册名S u u n b [ C C G ] 呵呵,明白了吗?eax中装的内存地址就是注册名在内存中的首地址,第一次执行到这里时ebx中装的是1,eax+ebx-01后得到的还是注册名的首地址,也就是S。而等到后面004f504f处的跳转指令跳转回来之前,会在004f504d处有一条inc指令会给ebx加1,这样的话再执行到这里时就会得到注册名中的第2个字符u了,嘿嘿,第三次来之前会再给ebx加上1,明白了吗?总知你可以把ebx中的值理解为当前参加运算的字符在注册名中的位数,即ebx是1就是得到注册名的第一位(S),如果ebx是2就是得到注册名的第2位(u). #r_-@ 6x
而后紧接着在004f5003处会有一个CALL等着我们,呵呵,这个CALL比较关键,注册码的一部份由它来决定,要发现它的重要性并不难,因为在004f5003处下面会有一个跳转,跳转之前会对al进行测试,嘿嘿,而al在CALL之前装入的是当前参与运算的字符...并且你用调试器过一下这个CALL就会发现其对al进行了修改,呵呵,这个CALL会对al做一些处理,而处理的结果直接影响了后面部分的流程,所以,对于它,我们一定要跟进...最好能派出两个人在边路对其进行防守,并找专门的后位对其盯梢... =lki)UY
FM_N_@G
我们待会儿再跟进它,现在还是要先搞明白软件大体上的算法。好的,我接着说,在004f5008处对al进行了测试之后会有一个跳转,即如果al中此时装的值为0就跳到004f5031处去...你可以理解为这个CALL会对字符进行一些运算,如果符合了要求,al就会被置0或1什么的,出来后的测试用来判断当前字符是否符合要求,如果符合就跳或不符合就跳... B([pilX:Q
继续,由于我输入的注册名的第一个字符是S,而S刚好能通过004f5003处的那个CALL的计算 所以就没有跳走,我继续按F10进行单步执行...接下来的004f500c、004f500f、004f5012这三条指令跟前边儿的得到注册码第N位字符的指令道理是一样的,你看注释好了...而后面从004f5016到004f5029处的这几条指令也没什么好讲的,对中间的两个CALL好奇的话可以进去大概看一下。得不到什么实质性的东西...而004f502c处的这个CALL嘛,就很重要了,呵呵,它的作用是什么呢?还记的我刚才说过的004f5003处的那个CALL吧,它执行过后会使al发生变化,它下面的跳转指令会根据al的值做相应跳转,即如果al为0,就跳到004f5031处,刚好就跳过了004f502c处的这个CALL...而我输入的第一个字符是S,刚好符合了004f5003处那个CALL的要求,所以没有跳走,于是就执行到了这里,你可以追进去看一下,里面并不复杂,只是将当前参加运算的字符装入内存的00D3B3C4处(如果当前参加运算的字符在004f5003处没有通过,就不会执行到这里,呵呵,明白过来了吧,这个CALL用于收集注册名中所有符合004f5003处那个CALL要求的字符) H<iB\Gn8~l
HOHOHO~~(请模仿周星星式的笑声...)现在我们已经明白了一半了...好的,我们继续... C,pz"<
不管你是从004f500a处跳到004f5031处的,还是一步步执行到这里的,总知,不管你输入的注册名中参加当前运算的那一个字符符不符合004f5003处的那个CALL的要求,总知都会执行到这里...这条指令用来干什么呢?还记的ebx中装的是参加运算的字符在注册名中的相应的位数吗?cmp ebx,byte +01 就是用ebx减去1,该条指令的用途也就是看一下当前参加运算的字符是不是注册名中的第一个字符,如果是就跳到 004f5040处,否则继续... 我们先看004f5040处,当执行到此处时,ebp-0c中装的其实是注册名的内存地址(前边就已经说过了)在这里将其装入eax中,而后面004f5043处的指令的用途就是得到注册名的第一个字符...好了,我们再拐回来看004f5036处,如果当前参加运算的字符不是注册名中的第一个字符,就不会跳走,而执行到这里时同样将ebp-0c中装的注册名的内存地址放入eax中,而004f5039处的eax,byte [eax+ebx-02]嘛,呵呵,很好理解,eax+ebx-01得到的是当前参加运算的字符的内存地址,而这里的eax+ebx-02得到的就是当前参加运算的字符的前面的那个字符,了解? Q6h`^7WCcY
我们接着看004f5046处的那条指令吧,这个同样非常重要,它的作用是计算注册码的后半部分! `-vitG(
我相信你很容易就能理解它的意思了,当执行到这里时,eax中装的或者是注册码中的第一个字符,或者是当前参加运算的字符的前一个字符(注:字符在内存或寄存器中是以ASCII码来表示的,如S在eax中会显示为00000053,而S的ASCII码便是53,十进制为83)...我们第一次执行到这里时,esi中的值为0(即00000000)eax*4+a8的意思就是用当前参加运算的字符的ASCII码乘以4,再用积加上a8(也就是十进制数168,一路发?)再用这个和与esi相加,我已经说过了,第一次执行到这里时esi中的值为0...而当第二次执行到这里时,esi中装的便是注册名的第一个字符的ASCII码乘以4再加一路发的和... !6:t5D
你会问你为什么知道它是计算注册码的后半部分的?猜的!!呵呵,当然不是,我们可以看到,在004f5054处,程序会将前面计算的结果装用eax中,后边儿紧接着就是一个CALL,嘿嘿,光天化日之下,这也太明显了吧,我们追进去大概看一下就知道它的作用是将十六进制的数转换为十进制的...并将转换后的结果装入edx中装的内存地址处,在CALL之前我们会看到edx中的值以由004f5051处装入,即ebp-1c,呵呵,CALL过之后你用d ebp-1c看一下,就会看到你注册码的后半部分了... k2y}qcY
而后程序会在004f505b将注册码后半部分装入ecx中,在004f505e处时会将一个内存地址ebp-0c装入eax处(它的作用就是起一个传递参数的作用,在待会儿的CALL中会用eax中装入的值来存放结果)之后的004f5061处会将ebp-10装入edx中,ebp-10处装的是什么呢?我们用d ebp-10指令看一下就会知道它的地址为00D3B3C4,嘿嘿,你的嗅觉敏感吗?不敏感的话我就再说一遍,还记的004f502c处的那个CALL吗?它的作用就是收集符合004f5003处的那个CALL的要求的字符... yC5:M;M=Q
嘿嘿,你明白过来了吗? 0/.@'P Ym
这个软件的注册算法是这样的:首先得到注册码的位数,看其是否大于0,不大于0就跳到004f5051处...好的,我们输入了Suunb[CCG]这个注册名,此时的注册码位数就是10,所以不会跳走,之后我们会来到004f4fff处,第一次执行到这里时会将注册名的第一个字符S装入al中,第二次来时会将注册名中的第二个字符(即u)装入al中,它的作用就是将当前参加运算的字符装入al中,之后紧接着就是一个CALL,这个CALL会对当前参加运算的字符进行计算...接着出来会有一个跳转,看al中装的是不是0,如果是就跳到004f5031处,如果不是非0值就说明当前这个字符符合了要求,那么就会执行到004f502c处,这里的CALL会将其存放置内存的00D3B3C4处...而后到了004f5031处会有一个比较,作用是看当前参加运算的字符是不是注册名中的第一个字符,是的话就跳到004f5040处,在此将注册名的第一个字符装入eax,用来参加004f5046处的计算。如果当前参加运算的不是注册名的第一个字符,那么就会在执行到004f5039处时得到当前参加运算的字符前面的那个字符,将其装入eax后就无条件跳到004f5046处来参加运算。了解?也就是说你输入的注册名的第一个字符会参加两次计算,而最后一个字符不会参加计算(想想看,如果当前参加运算的字符是注册名中的第一个字符,它会参加计算,如果是第二个,就取前边的一个,即第一个又会参加一次计算,到了第三个的时候取第二个,到了第四个的时候取第三个...而当最后一个字符来到这里时会取前边的那个字符来参加运算,而这之后就循环就结束了,所以,最后一个不会被计算入内)等到注册名中的所有字符都参加过了运算,就会来到004f5056处,在这里将前面004f5046处的计算结果转换为十进制...而后会在后面的004f5064处的那个CALL里,将其与先前装入00D3B3C4处的所有符合004f5003处的CALL要求的字符合并到一起,这个结果,嘿嘿,就是真正的注册码了
也就是说,真正的注册码是由以下部分组成的: ^1%H)#$4
你输入的注册名中的所有符合004f5003处的那个CALL要求的字符+((注册名中的第一个字符的ASCII码*4+168)*2+(除第一位和最后一位外的所有字符)*4+168的和的和) <,,)?
现在我们也知道注册码是怎样炼成了的 那么我们要写注册机,就一定要把004f5003处的那个CALL给搞明白,这样的话,我们才能对注册名中的字符进行筛选... ,1O,+j4
好的,我们再来: uh:km2
Ctrl+N呼出TRW2000,下bpx 004f5003,F5退出点确定被拦后便按F8跟进,呵呵,这个就没什么好说的了,看我给出的注释吧: vicw4N
0167:004f4f60 push ebp j!`mT+Q~
0167:004f4f61 mov ebp,esp NW{6Sz
0167:004f4f63 push ecx O g{M|0u/
0167:004f4f64 push ebx H2)Z7JyS
0167:004f4f65 push esi % $i$nA
0167:004f4f66 mov [ebp-01],al <--将字符装入内存ebp-01处 G>RjG,+;r
0167:004f4f69 mov byte [ebp-03],02 <--ebp-03处装入02 +%P/0G ih
0167:004f4f6d mov byte [ebp-02],01 <--ebp-02处装入01 <F[wz~D_P
0167:004f4f71 mov cl,[ebp-01] <--将参加运算的字符装入cl < z/ yQJ
0167:004f4f74 dec ecx <--cl减1 q'Q RS>
0167:004f4f75 sub cl,02 <--cl再减去2 +j>{~/d(
0167:004f4f78 jc 004f4fa4 <--有进位就跳转,你不用担心,一般都不会跳走的啦 AIUE} -=
0167:004f4f7a inc ecx <--ecx加1,也就是cl加1 px9SRj@L
0167:004f4f7b mov bl,02 <--bl装入02 |1u5569
0167:004f4f7d xor eax,eax <--eax做异或运算,即将eax置0 )Q{?j\{X-G
0167:004f4f7f mov al,[ebp-01] <--al装入参加运算的字符 WcZ`4>/a
0167:004f4f82 xor edx,edx <--edx置0 [ih";cB #
0167:004f4f84 mov dl,bl <--将bl中的值付给dl )O\B_dx
0167:004f4f86 mov esi,edx <--再付给esi v\J[{M"{Z
0167:004f4f88 xor edx,edx <--edx置0 *e~9bNd
0167:004f4f8a div esi <--用eax中装的参加运算的字符的ASCII码除以当前esi的值 yI9y" bC m
0167:004f4f8c test edx,edx <--测试edx,edx中装的是上刚才除法计算后的余数 )S@GkX8
0167:004f4f8e jnz 004f4f93 <--不为0就跳到004f4f93处 uJck32
0167:004f4f90 inc byte [ebp-03] <--ebp-03处的值加1 vx_yf
0167:004f4f93 cmp byte [ebp-03],02 <--用ebp-03处的值减去02 -3r ny/
0167:004f4f97 jna 004f4f9f <--不大于就跳到004f4f9f处 ^x}zI]0-
0167:004f4f99 mov byte [ebp-02],00 <--ebp-02装入00 XS(33N @
0167:004f4f9d jmp short 004f4fa4 <--无条件跳转到004f4fa4处 GR)c7Y'*8
0167:004f4f9f inc ebx <--ebx加1 ]Y`VMEZ *
0167:004f4fa0 dec cl <--cl减1 Kz= 4
0167:004f4fa2 jnz 004f4f7d <--不为0就跳到004f4f7d处再来一遍 TaVc%\!
0167:004f4fa4 mov al,[ebp-02] <--将ebp-02处的值装入al后返回 ^Q[^VU:2
0167:004f4fa7 pop esi n;]=x8ow
0167:004f4fa8 pop ebx vrIC>*
0167:004f4fa9 pop ecx vIXT/4 O?
0167:004f4faa pop ebp s+_[V98we
rNlUh
不知道你看不看的明白,我大概给你说明一下,大体上就是这样的: mlnjd}*
先得到这个字符的ASCII码,然后用其减去2,并将bl置值02...然后用eax中装的减了2的ASCII码除以esi中装的bl中的值,之后就看edx中装的余数是否为0,如果为0就将ebp-03处的值加1,你应该知道ebp-03处在初始化的时候被付值为2,如果其被加了1,那就大于了2,这样的话后面在004f4f97处就不会跳走,如果不跳走,在004f4f99处,ebp-02就会被装入00,之后就会无条件跳转到004f4fa4处,在那里就会把ebp-02的值,也就是00装入al,明白过来了吧...但如果edx中装的余数不为0,那么在004f4f8e处就会跳走,到了004f4f93处时由于ebp-03中装的是02,所以条件就会成立,从而可以跳到004f4f9f处去继续执行程序...而到了004f4f9f处后ebx(也就是bl)会被加上1,而cl在后面会被减去1,如果cl不为0的话就再跳到004f4f7d处,再来一遍,直到cl变为零为止... !hScwdTc`
上面这个过程在Delphi中可以这样表示: @ue'~rPJ!
(变量S中装的是当前参加运算的字符,Code变量用来收集符合要求的字符,变量的声明我没有写明) :LMtY#
N:=Ord(S); J9@GMgY
for i:=2 to N-1 do (9D;LUp#3
begin "%m Ll@WX
modz:=(N-i) mod i; ) cX U,%
if modz=0 then Break; eG9:T{PVI-
end; WHFuoc?
if modz<>0 then DpEG=%iPg
Code:=Code+S; ~Mw62&/SF:
呵呵,这就起到了与前面汇编代码相同的作用了 ` 7nLd8J
下面我给你写一个函数,该函数可以用来得到注册码中的所有符合要求的字符: jTwwtl)fb9
function GetKeyChar(Name: String): String; II:6BBnQx
var kxd&L&g3v
i,ASC,sh,modz:integer; s[v D|PzK
Code:String; WKRI~uj0d
begin d7Km !Zm[
for i:=1 to Length(Name) do z< pe9|_"
begin 'SIz,/@q[
ASC:=Ord(Name); -gc~jkL5Q
for sh:=2 to ASC-1 do r(H r'
begin QPYgZ1O o
modz:=(ASC-sh) mod sh; 9] c0>#Y
if modz=0 then Break; "!LF.9{
end; H5CBWc
if modz<>0 then U]~}bNj
Code:=Code+Name; \WSo e7~
end; X}loQ|L9JJ
Result:=Code; ;iJ)5[5
end; Ku%LxJ r{x
你可以这样来用: ^'^NY aP$
var ~@ xFR\(w
S1,S2:String; |;jYPQ2
begin --@|nV}A.y
S1:=Edit1.text; //Edit1用来输入注册名; u#us$L1)G
S2:=GetKeyChar(S1); //此时S2中得到的便是注册名中所有符合要求的字符了; 7@VHH1s]&
end; 0hjs+-}k.
嘿嘿,你现在是不是很想知道都有哪些字符能符合要求?嘿嘿,其实CHINAZIP自己就可以告诉我们,你只要在输入注册名的时候把所有的可用字符全填上,然后在用d指令看一下注册码的前半部(即非数字部分),就可以知道了,当然你也可以自己写一个程序来试试,用Delphi的话可以直接用我上面给的函数...告诉你好了,在所有的ASCII字符中,只有以下几个符合要求: ~')3t`*
CGIOSYaegkmq5=%)+;/ /)#d|yzQ~
不信的话你可以试试,正确的注册码中的字符只能是这几个...嘿嘿,看来还是比较尊重我们CCG的嘛 }OM-)1~e
好了,我又废话了这么多,把完整的注册机给你贴出来吧,这并不难,只要加上后半部分注册码的计算就OK了: P d>:s
你可以在Delphi中声明以下函数,便可直接使用: ]4ZApb"
function GetKey(Name: String): String; iUqrE3{
var .RXS DW
N:String; hf7#fD$T
i,sh,ci:integer; 7M^2T8a!
Si:integer; w/uVc.M6
ASC,modz:integer; 4n La'6ux
begin AmL0A89=
i:=Length(Name); U/"nx
if i=0 then Result:='请输入注册名...' H9]tw ;
else Lvx r
begin #s]?|a]'
for sh:=1 to i do 0y|6X s
begin d1E1L
ASC:=Ord(Name[sh]); t!d?Ttqh
for ci:=2 to ASC-1 do Y r EO7O
begin UP|r _m
modz:=(ASC-ci) mod ci; ",p#i(z
if modz=0 then Break; I> qZBp-B
end; ]Z $=V_Fr
if modz<>0 then N:=N+Name[sh] )[UeX0
end; U6_>Y &
Si:=Ord(Name[1])*4+168; nO@[:u@
for sh:=1 to i-1 do eGeM842oZ
begin nU~$o6ZMM
Si:=Si+Ord(Name[sh])*4+168; "iz`Bh
end; Rne jI5
N:=UpperCase(N+inttostr(Si)); }]F
Result:=N; C2.[] AY'
end; {8JEZPf4
end; ==O]N
最后顺便说一下,如果你稍微留意一下,就会发现,在刚开始的004f4ff3处会对注册名的位数进行测试,即如果位数为0就会在下一条指令时跳至004f5051处,如果你稍微有一点儿经验,就会发觉,通常如果程序发现注册名的位数为0,就会直接跳到失败处,而这个软件却没有!004f5051处是什么呢?晕~~具然是要得到注册码的后半部分,呵呵,我们以到00D3B3C4处看看,会看到一堆0,你现在在想什么呢?既然我们没有输入注册名,那么就不可能有注册码的前半部分,而后边便会有CALL来合并前后两部分的注册码 你重新启动一下软件,注册名不填,把注册码填为0注册一下看看...HOHO~~(请仍旧模仿周星星式的笑声)这个粗心的作者啊,造成这样的原因很简单,软件根本就没有判断注名是否为空并在软件初始化的时候把用于计算注册码后半部分的integer变量付了初始值0,否则的话00D3B3C4处的内存应该为空值..(难不成到时连0都不用输就能注册?) w_I,CHhC6
所以说,Crack并不是一件坏事,像这种情况你完全可以告诉作者的嘛,到时不但交了一个朋友而且说不准还会得到个免费的注册码....(不知道有没有白帽子Cracker?嘿嘿,CCC刚好也可以是注册码的前半部分哦~~)我希望你明白,对于这种注册算法简单且存在Bug的软件(通常也说明其作者还没什么经验 ^_^),我们不应该为能提供它注册机而感到高兴,如果能帮助其作者改善算法或去掉Bug,又何尝不是一件好事呢?毕竟软件上面加的有中华两个字,你忍心??? L~i B__:Q[
F{ y+&s.L
我不知道上面给你讲的中华压缩注册分析你是否看懂了,我个人认为我讲的还是比较详细的了(几乎每条指令都加了注释且又再三在后面说明)但如果你仍然看不懂的话,请务必相信是本人写的文章不好,不要放弃啊哥们儿~~! XjZ6So\C?
好了,我再来给你举另外一个例子...通过它来给你讲一下另外一种比较常见的注册码计算方法,即将运算的结果与一个表中的字符进行转换,也就是常说的密码表啦^_^ Q/8c
本来是想用网际快车FlashGet的,可是在看雪已经有人贴了最新的1.40版的破文&注册机,正好前些天的时候网友啥也不是在后面跟贴说要帮他看一下语音界面2.0这个软件,down下来后大概看了一下,呵呵,发现这个正是我想要的,注册码计算的过程中采用了密码表并且也不难...hehe~~后来HMILY老哥看到了啥也不是的另一个贴子,也写个注册机和破文,你可以参考一下,嘿嘿,HMILY跟偶是自己人,所以偶不怕他... ax&vi E71
我们开始吧... >JZ L]b|
首先运算一下这个软件,其会自动生成机器码,在我这边儿是xn2urkeUMwpNv5xZ。 +_t/S1c{U
这一章,我会用调试器Ollydbg来作讲解,它真的很好用...我们开始吧: 5PkX-nf
偶不知道你是否喜欢Ollydbg的下断方式,总知偶是不喜欢,从那么多API里面先(字好小),再说了,偶还是喜欢用Hmemcpy来断,除非断不到或在2K/XP下,否则偶才不要去跑API呢,往往要三四次才断到,多累啊 我们还是先请临时演员TRW2000出出一下场吧(把你的MP3先暂停一下 ),下bpx hmemcpy点确定后会被拦,pmodule后返回到0040432f处,我们就从这里开始吧 导演:"Stop!换吴孟达出场" 嘿嘿,继续你的MP3,请同时打开Ollydbg A@)>[pV
我们用Ollydbg载入,在反汇编代码处(即左上方那个子窗口中)按Ctrl+G,输入0040432f,回车后便来到了这里。我们大概看看,有经验的话很容易就会看出此地便是了(这次运气比较好,一下就能找到软件计算注册码的地方 ^_^) gP(xLC93
好吧,我们按F2在这里下断,接着按F9来运行程序,在注册处输入CHINA Cracking Group Suunb后按确定会被Ollydbg断下来,大概跑跑看看吧 我贴出反汇编代码: ,dkl%/={(
0040432F |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+10] fva&e|EX
00404333 |. 6A 05 PUSH 5 )) yoyi
00404335 |. 51 PUSH ECX {tC>![0c
00404336 |. 68 E8030000 PUSH 3E8 z">fM6
0040433B |. 8BCE MOV ECX,ESI ~<E&`[gm@
0040433D |. E8 10050000 CALL <JMP.&MFC42.#3092> e6Q yJP[<
00404342 |. 8BC8 MOV ECX,EAX *x{ 61+
00404344 |. E8 7D060000 CALL <JMP.&MFC42.#3873> :!r7Fzb'H
00404349 |. 8D5424 18 LEA EDX,DWORD PTR SS:[ESP+18] FjM72qK4O
0040434D |. 6A 05 PUSH 5 N 4)_u} op
0040434F |. 52 PUSH EDX XFOi` WU
00404350 |. 68 E9030000 PUSH 3E9 ' u@;}{?
00404355 |. 8BCE MOV ECX,ESI 5_xRN52Q<Y
00404357 |. E8 F6040000 CALL <JMP.&MFC42.#3092> |)Ji)1B3
0040435C |. 8BC8 MOV ECX,EAX ;()s f[j}
0040435E |. E8 63060000 CALL <JMP.&MFC42.#3873> #!2U'.|T
00404363 |. 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+20] cIyB|+:9
00404367 |. 6A 05 PUSH 5 d-Mo}})``
00404369 |. 50 PUSH EAX ;6BHTkfU
0040436A |. 68 EA030000 PUSH 3EA $ZCNd X1
0040436F |. 8BCE MOV ECX,ESI ]q`n+4R
00404371 |. E8 DC040000 CALL <JMP.&MFC42.#3092> 1VS@Nj*%*w
00404376 |. 8BC8 MOV ECX,EAX @|FC4#5(
00404378 |. E8 49060000 CALL <JMP.&MFC42.#3873> '_c%O~
0040437D |. 8D4C24 28 LEA ECX,DWORD PTR SS:[ESP+28] Flvb_<X
00404381 |. 6A 05 PUSH 5 L 6%O6e
00404383 |. 51 PUSH ECX >6BBy$O
00404384 |. 68 EB030000 PUSH 3EB PD1%&Tj
00404389 |. 8BCE MOV ECX,ESI ( I6_%"l;
0040438B |. E8 C2040000 CALL <JMP.&MFC42.#3092> n,J 1r3r
00404390 |. 8BC8 MOV ECX,EAX s[$s;u&EW
00404392 |. E8 2F060000 CALL <JMP.&MFC42.#3873> F ~Ja:8W
00404397 |. 8B7C24 68 MOV EDI,DWORD PTR SS:[ESP+68] ["aOOoqE*
0040439B |. 33DB XOR EBX,EBX 1cwc* ##8
0040439D |. 33C9 XOR ECX,ECX |=J=Q(FX'
0040439F |. 8D04BF LEA EAX,DWORD PTR DS:[EDI+EDI*4] qrQkVFKu
004043A2 |. 8D0480 LEA EAX,DWORD PTR DS:[EAX+EAX*4] Y#'" 6;'
004043A5 |. 8D3480 LEA ESI,DWORD PTR DS:[EAX+EAX*4] <--上面的这些你都不需要管,因为它们帮不了我们什么忙 wY)';"@
4/"d\E^-x
6]I-1"?er
004043A8 |. C1E6 02 SHL ESI,2 <--执行后ESI的值为5DC,及十进制数1500 %[Grf'8aj
004043AB |> 0FBE440C 50 /MOVSX EAX,BYTE PTR SS:[ESP+ECX+50] <--ESP+ECX+50就是机器码在内存中的地址(首次执行到这里明ECX为0,得到的就是机器码的第一位,第二次到这里时ECX会加上1,得到的是第二位...) i8*7HC$
004043B0 |. 03C6 |ADD EAX,ESI <--与ESI相加,也就是加上1500 p,K&! |P
004043B2 |. BD 3E000000 |MOV EBP,3E <--EBP置3E,及十进制数62 3[obo]ar
004043B7 |. 99 |CDQ <--扩展... n9s5cJ=
004043B8 |. F7FD |IDIV EBP <--EAX中装的机器码的第1 or 2 or 3 or 4位与1500的和与62相除 ZLjI-"{O
004043BA |. 0FBE440C 54 |MOVSX EAX,BYTE PTR SS:[ESP+ECX+54] <--ESP+ECX+54得到机器码的第5位(还记的ESP+ECX+50中装的是第一位吗?首次执行到这里明ECX为0,得到的就是机器码的第5位,第二次到这里时ECX会加上1,得到的是第6位...) DT)` C4
004043BF |. 03C6 |ADD EAX,ESI <--同样加上1500 10/^_
004043C1 |. 8A92 E4704000 |MOV DL,BYTE PTR DS:[EDX+4070E4] <--重要的地方来了!此时EDX中装的是前面的第1 or 2 or 3 or 4位机器码加上1500后再除以62的余数,那4070E4处是什么呢?如果在TRW2000中,我们用d 004070E4就可以看到,在Ollydbg中,我们可以在左下角处按Ctrl+G来输入相应的内存地址,这样的话就可以看到了。我们会发现从4070E4开始,装的是一串字符,依次是0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ。呵呵明白什么意思了吗?4070E4处指的是0,那4070E4加上edx中装的余数后的内存地址中装的便是当前机器码所对应的注册码。(如余数为5,那么从4070E4处的0开始往后数第5个字符就是了)该条指令执行过后就会把相应的注册码装入dl中 9nbN w[%rp
004043C7 |. 88540C 30 |MOV BYTE PTR SS:[ESP+ECX+30],DL <--将机器码的第1 or 2 or 3 or 4所对应的注册码装入ESP+ECX+30处 t<P:p: )~
004043CB |. 99 |CDQ <--前边儿得到的第5 or 6 or 7 or 8位机器码扩展 yF[ciX9t*B
004043CC |. F7FD |IDIV EBP <--同样除以62 \ykjV{q;
004043CE |. 8A82 E4704000 |MOV AL,BYTE PTR DS:[EDX+4070E4] <--与004043C1处作用相同,不用我多说了吧,就是得到当前机器码的对应注册码 );~A6K!
004043D4 |. 88440C 38 |MOV BYTE PTR SS:[ESP+ECX+38],AL <--装入ESP+ECX+38处 w1EL)<
004043D8 |. 0FBE440C 58 |MOVSX EAX,BYTE PTR SS:[ESP+ECX+58] <--得到机器码的第9 or 10 or 11 or 12位 y<Nbj~+i
004043DD |. 03C6 |ADD EAX,ESI <--与1500相加 }.f#%0Qq
004043DF |. 99 |CDQ <--扩展.... !a3(`e:
004043E0 |. F7FD |IDIV EBP <--除以62 HZW1w<5bVx
004043E2 |. 0FBE440C 5C |MOVSX EAX,BYTE PTR SS:[ESP+ECX+5C] <--得到机器码的第13 or 14 or 15 or 16位 @-GP XM
004043E7 |. 03C6 |ADD EAX,ESI <--与1500相加 $V |6:
004043E9 |. 8A92 E4704000 |MOV DL,BYTE PTR DS:[EDX+4070E4] <--前边的第9 or 10 or 11 or 12位所对应的注册码 TnkN`LR
004043EF |. 88540C 40 |MOV BYTE PTR SS:[ESP+ECX+40],DL <--装入ESP+ECX+40处 ={d3?i`r
004043F3 |. 99 |CDQ <--扩展 /(G$ K
004043F4 |. F7FD |IDIV EBP <--除以62 :2 V/Bpz
004043F6 |. 41 |INC ECX <--ECX加1 kR14=6u
004043F7 |. 83F9 04 |CMP ECX,4 <--看ECX是否为4(前边儿是一次计算四位的嘛,第一次计算第1、5、9、13位,第二次是2、6、10、14...) [%)p@m .P
004043FA |. 8A82 E4704000 |MOV AL,BYTE PTR DS:[EDX+4070E4] <--得到前边的第13 or 14 or 15 or 16位机器码所对应的注册码 $i7gUN<
00404400 |. 88440C 47 |MOV BYTE PTR SS:[ESP+ECX+47],AL <--装入ESP+ECX+47处 $F&o
00404404 |.^7C A5 \JL SHORT LIAOCACH.004043AB <--ECX小于4就从头再来一遍(直到16位机器码都计算完为止) "VX9O8L
00404406 |. 8B35 AC524000 MOV ESI,DWORD PTR DS:[<&MSVCRT.atoi>] ; MSVCRT.atoi H'k}29
0040440C |. 8D4C24 10 LEA ECX,DWORD PTR SS:[ESP+10] 2=oeV8
00404410 |. 51 PUSH ECX ; /s hf_a+%
00404411 |. 885C24 38 MOV BYTE PTR SS:[ESP+38],BL ; | q3=\V
00404415 |. 885C24 40 MOV BYTE PTR SS:[ESP+40],BL ; | \[~!:^
00404419 |. 885C24 48 MOV BYTE PTR SS:[ESP+48],BL ; | h<6.ZN +>
0040441D |. 885C24 50 MOV BYTE PTR SS:[ESP+50],BL ; | [_{h~Ojx
00404421 |. FFD6 CALL ESI ; \atoi ^a}Uc-c(o
00404423 |. 83C4 04 ADD ESP,4 Z$(}6_C5!
00404426 |. 83F8 01 CMP EAX,1 NJ$fW_jL
00404429 |. 75 3C JNZ SHORT LIAOCACH.00404467 r&7]sX*<M
0040442B |. 8D5424 18 LEA EDX,DWORD PTR SS:[ESP+18] ";f!Rt= P
0040442F |. 52 PUSH EDX 9Gph0!MeK
00404430 |. FFD6 CALL ESI d!@9KGk
00404432 |. 83C4 04 ADD ESP,4 z-U9po;D~
00404435 |. 83F8 01 CMP EAX,1 EN2*:K)
00404438 |. 75 2D JNZ SHORT LIAOCACH.00404467 ca(R?;
0040443A |. 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+20] UbZzHEf j
0040443E |. 50 PUSH EAX {{O6,_k:7
0040443F |. FFD6 CALL ESI 0>a8=W}_
00404441 |. 83C4 04 ADD ESP,4 *-nav[
00404444 |. 83F8 01 CMP EAX,1 fHc;+^R
00404447 |. 75 1E JNZ SHORT LIAOCACH.00404467 TxVXVP(
00404449 |. 8D4C24 28 LEA ECX,DWORD PTR SS:[ESP+28] 5)8bRp/{KP
0040444D |. 51 PUSH ECX -,P-!Z
0040444E |. FFD6 CALL ESI $KJy<n LK
00404450 |. 83C4 04 ADD ESP,4 8(PU)
00404453 |. 83F8 01 CMP EAX,1 9fDBz[(
00404456 |. 75 0F JNZ SHORT LIAOCACH.00404467 &>\+u`Q$
00404458 |. 5F POP EDI ,zW&!bH]
00404459 |. 5E POP ESI <DYWz
0040445A |. 5D POP EBP -y2,s:
0040445B |. B8 FEFFFFFF MOV EAX,-2 rbnh~'o
00404460 |. 5B POP EBX ;-@|0V0m
00404461 |. 83C4 54 ADD ESP,54 ]UYT1rYJCP
00404464 |. C2 0400 RETN 4 ?y=8|7g
00404467 |> 8D7424 30 LEA ESI,DWORD PTR SS:[ESP+30] <--正确的注册码的前4位的地址装入ESI中,嘿嘿,执行到这里时我们就可以看到正确的注册的前4位了,在TRW2000 or SoftICE中可以用d ESP+30来查看而在Ollydbg中什么都不用做就可以在左上方反汇编代码区与左下内存区中间的那个小窗体中看见 在TRW2000中下过D指令后按Alt+上下键翻几下,就可以看到所有的注册码了(它们并没有分太开嘛0063F5E0-0063F5E3是前4位,0063F5E8-0063F5EB是5-8位,0063F5F0-0063F5F3是9-12位,0063F5F8-0063F5FB是最后4位,而你输入的注册码的内存地址:0063F5C0-0063F5C3是前4位,0063F5C8-0063F5CB是5-8位,0063F5D0-0063F5D3是9-12位,0063F5D8-0063F5DB是最后4位,机器码存放的地址是0063F6000-0063F60F) w6.:@1
0040446B |. 8D4424 10 LEA EAX,DWORD PTR SS:[ESP+10] <--你输入的注册码的前4位的地址装入EAX中 c RS3Q
0040446F |> 8A10 /MOV DL,BYTE PTR DS:[EAX] <--得到你输入的注册码的第1位或第3位(EAX中的值到了后边儿会加上2,这样再执行到这里时得到的就是第3位了) ~r\>qXgZ
00404471 |. 8ACA |MOV CL,DL <--传入cl中 kxa%wJn+8
00404473 |. 3A16 |CMP DL,BYTE PTR DS:[ESI] <--与正确的注册码的第1位或第3位比较(ESI中的值会与EAX中的值一起改变) =!W:d g
00404475 |. 75 1C |JNZ SHORT LIAOCACH.00404493 <--不相等就跳走 $sscX#^Z
00404477 |. 3ACB |CMP CL,BL <--CL与BL比较,BL中的值为00000000(也就是空啦),这条指令有什么用呢?其实很简单了,每4位注册码的后面都会再跟一个空值,也就是如你在内存中可以看到1234.那个.就是空值,明白过来了吧,等到前4位都被测试过了,cl中就会装入.也就是00000000,到时就可以在后面跳走了 y5f B4@3a
~Rn!/G 6
4~r dXS
00404479 |. 74 14 |JE SHORT LIAOCACH.0040448F <--如果CL中的值为零(即4位已经全部比较过了),就跳走 oKc#na~Y5
0040447B |. 8A50 01 |MOV DL,BYTE PTR DS:[EAX+1] <--EAX+1后得到的会是你输入的注册码的第2位或者第4位(视EAX的值而定) -9FNlG
0040447E |. 8ACA |MOV CL,DL <--传入CL ;IYw }
00404480 |. 3A56 01 |CMP DL,BYTE PTR DS:[ESI+1] <--与正确的注册码的第2位或第4位比较(ESI的值会与EAX一起改变) ftNmvWo:
00404483 |. 75 0E |JNZ SHORT LIAOCACH.00404493 <--不相等就跳走 GCF` 74N C
00404485 |. 83C0 02 |ADD EAX,2 <--EAX加上2,这样的话待会儿再跳到0040446F处时,再得到的就是你输入的注册码的第3位了 +&=dv#
00404488 |. 83C6 02 |ADD ESI,2 <--同上,ESI加上2后再跳到0040446F处重新再来一遍时就会得到正确的注册码的第3位 %i5<CJ/ez#
0040448B |. 3ACB |CMP CL,BL <--再次比较CL是否为空 ;{end?N#7
0040448D |.^75 E0 \JNZ SHORT LIAOCACH.0040446F <--不为空就再跳到0040446F处,来继续比较前4位中的1、3两位 BhaAu5I
0040448F |> 33C0 XOR EAX,EAX <--1-4位全部比较完后会跳到这里 N^_vALt
00404491 |. EB 05 JMP SHORT LIAOCACH.00404498 ,:}zn.I
00404493 |> 1BC0 SBB EAX,EAX {&TereKX%Q
00404495 |. 83D8 FF SBB EAX,-1 ` "|f)y[7
00404498 |> 3BC3 CMP EAX,EBX &)L o4,g
0040449A |. 0F85 AB000000 JNZ LIAOCACH.0040454B fS!_9HJ!
004044A0 |. 8D7424 38 LEA ESI,DWORD PTR SS:[ESP+38] <--与上面的大体相同嘛,将正确注册码的5-8位的内存地址装入ESI中 QGAFBM
004044A4 |. 8D4424 18 LEA EAX,DWORD PTR SS:[ESP+18] <--你输入的注册码的5-8位的内存地址装入EAX中 Gi(`mUpeWL
004044A8 |> 8A10 /MOV DL,BYTE PTR DS:[EAX] <--得到你输入的注册码的第5 or 7位(道理我相信你一定已经明白了) 5=?uC'
004044AA |. 8ACA |MOV CL,DL <--再装入CL中 Cgb'L.D])
004044AC |. 3A16 |CMP DL,BYTE PTR DS:[ESI] <--与正确的注册码的第5 or 7位比较 s eKgFm
004044AE |. 75 1C |JNZ SHORT LIAOCACH.004044CC <--不相等就跳走 Z1XY4p<h
004044B0 |. 3ACB |CMP CL,BL <--与BL中的00000000比较,看5-8位是否已经全部比较完毕 $$\zW\Nh
004044B2 |. 74 14 |JE SHORT LIAOCACH.004044C8 <--是的话就跳走 w96`E&{4
004044B4 |. 8A50 01 |MOV DL,BYTE PTR DS:[EAX+1] <--得到你输入的注册码的第6 or 8位 JlWlr[S
004044B7 |. 8ACA |MOV CL,DL <--装入CL 7n&L{y>
004044B9 |. 3A56 01 |CMP DL,BYTE PTR DS:[ESI+1] <--与正确的注册码的第6 or 8位比较 !oe{:8
004044BC |. 75 0E |JNZ SHORT LIAOCACH.004044CC <--不正确就跳走 8a]?ZH
004044BE |. 83C0 02 |ADD EAX,2 <--EAX加2,这样做的目的相信你已经知道了吧 7j1\Ss(
*cf^wF%l
o1`8m i0-+
004044C1 |. 83C6 02 |ADD ESI,2 <--ESI也加上2 4.D%(|D.q
004044C4 |. 3ACB |CMP CL,BL <--比较CL是否为空 b=v?z:l 8u
004044C6 |.^75 E0 \JNZ SHORT LIAOCACH.004044A8 <--不是就跳回去再来一遍 Mju By$
004044C8 |> 33C0 XOR EAX,EAX <--5-8位全部比较完后全跳到这里 FA]MC
004044CA |. EB 05 JMP SHORT LIAOCACH.004044D1 Zy`^U\T
004044CC |> 1BC0 SBB EAX,EAX AU '";,U6
004044CE |. 83D8 FF SBB EAX,-1 Os.y@7
004044D1 |> 3BC3 CMP EAX,EBX )+t$J$cj
004044D3 |. 75 76 JNZ SHORT LIAOCACH.0040454B #v^%yZj
004044D5 |. 8D7424 40 LEA ESI,DWORD PTR SS:[ESP+40] <--将正确的注册码的9-12位的内存地址装入ESI LF=,rlb\.
004044D9 |. 8D4424 20 LEA EAX,DWORD PTR SS:[ESP+20] <--你输入的 Qprl @l
004044DD |> 8A10 /MOV DL,BYTE PTR DS:[EAX] <--得到你输入的注册码的第9 or 11位 BGVT%z<EZ
004044DF |. 8ACA |MOV CL,DL <--装入CL (~L)Aok
004044E1 |. 3A16 |CMP DL,BYTE PTR DS:[ESI] <--与正确的注册码的第9 or 11位比较 )[s_
004044E3 |. 75 1C |JNZ SHORT LIAOCACH.00404501 <--不对便跳走 ?l&SB^%ag
004044E5 |. 3ACB |CMP CL,BL <--看CL是否为空,即看9-12位是否全部比较完毕 )V,}qi
004044E7 |. 74 14 |JE SHORT LIAOCACH.004044FD <--是的话跳走 z4{#f8?pR
004044E9 |. 8A50 01 |MOV DL,BYTE PTR DS:[EAX+1] <--得到你输入的注册码的第10 or 12位 $M8N^aNR2
004044EC |. 8ACA |MOV CL,DL <--装入CL '6{2H3vL
004044EE |. 3A56 01 |CMP DL,BYTE PTR DS:[ESI+1] <--与正确的注册码的第10 or 12位比较 IX4L? OiI
004044F1 |. 75 0E |JNZ SHORT LIAOCACH.00404501 <--不相等就跳走 O][s^Oi%K
004044F3 |. 83C0 02 |ADD EAX,2 <--EAX加2 J&W(Y N
004044F6 |. 83C6 02 |ADD ESI,2 <--ESI加2 U4[@:q'
004044F9 |. 3ACB |CMP CL,BL <--看是否全部比较完毕 ]a23z5#Q
004044FB |.^75 E0 \JNZ SHORT LIAOCACH.004044DD <--没有就跳回去再来一遍 pPLOCBh
004044FD |> 33C0 XOR EAX,EAX <--9-12位全部比较完毕后会跳到这里 <rZKmts
004044FF |. EB 05 JMP SHORT LIAOCACH.00404506 ox$/4]@]
00404501 |> 1BC0 SBB EAX,EAX 5(#9={$C
00404503 |. 83D8 FF SBB EAX,-1 <-s5TU_gr
00404506 |> 3BC3 CMP EAX,EBX cxDWxQJ
00404508 |. 75 41 JNZ SHORT LIAOCACH.0040454B 1V:fk ML=V
0040450A |. 8D7424 48 LEA ESI,DWORD PTR SS:[ESP+48] <--嘿嘿,都讲了三遍了,你一定明白了吧,所以我把这最后一段留给你自己来看吧,从这里到00404530处我相信你猜也猜的出来 (众人:鄙视你!) ]Ry$f. v'
0040450E |. 8D4424 28 LEA EAX,DWORD PTR SS:[ESP+28] W_u&[T-$
00404512 |> 8A10 /MOV DL,BYTE PTR DS:[EAX] Hlc]v2
00404514 |. 8ACA |MOV CL,DL 2lYqjl:=
00404516 |. 3A16 |CMP DL,BYTE PTR DS:[ESI] f5gdhk
00404518 |. 75 1C |JNZ SHORT LIAOCACH.00404536 y dvy4 ~Xo
0040451A |. 3ACB |CMP CL,BL 2GT/v
0040451C |. 74 14 |JE SHORT LIAOCACH.00404532 %h4gS4Jf
0040451E |. 8A50 01 |MOV DL,BYTE PTR DS:[EAX+1] 68~2]2qN
00404521 |. 8ACA |MOV CL,DL C'}BGI(
00404523 |. 3A56 01 |CMP DL,BYTE PTR DS:[ESI+1] NXh-np-
00404526 |. 75 0E |JNZ SHORT LIAOCACH.00404536 %rCGTS4D{
00404528 |. 83C0 02 |ADD EAX,2 dJjz>XB
0040452B |. 83C6 02 |ADD ESI,2 %UYfj6G
0040452E |. 3ACB |CMP CL,BL hN:CeYt
00404530 |.^75 E0 \JNZ SHORT LIAOCACH.00404512 M@MW9NOu\
00404532 |> 33C0 XOR EAX,EAX <--全部通过后来到这里 Z.!(N6W:
00404534 |. EB 05 JMP SHORT LIAOCACH.0040453B INsA=$Yfc
00404536 |> 1BC0 SBB EAX,EAX )Ps0AXLI9
00404538 |. 83D8 FF SBB EAX,-1 _W2~3}g3
0040453B |> 3BC3 CMP EAX,EBX [; ?IeQ`
0040453D |. 75 0C JNZ SHORT LIAOCACH.0040454B M{jAh8
0040453F |. 8BC7 MOV EAX,EDI an}:0+C%
00404541 |. 5F POP EDI bj8@e,
00404542 |. 5E POP ESI 1e% `${
00404543 |. 5D POP EBP J}*Lqjl;k
00404544 |. 5B POP EBX 5=d&^<Bwx
00404545 |. 83C4 54 ADD ESP,54 [Z^5Pl~9{
00404548 |. C2 0400 RETN 4 Dr8N"}
d27 6>5ZM
我的注释写的还算清楚吧 ^_^,我再大概给你讲解一下: z]dAQ<
软件的注册码是这样计算出来的,机器码中的各个字符的ASCII码加上1500后除以62的余数在密码表中对应的字符,就是相应的注册码。 8DQ+|vY^
比如说我这里的机器码为xn2urkeUMwpNv5xZ,x的ASCII码为78(十进制120) 78+5DC的值为654(即1620) 接着用1620除以3E(62)得商26余8,好的,我们从“密码表”0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ的开始处向后数8下,就会到了8这个字符,嘿嘿,这就是x对应的注册码。 m D6'Ti@]
好的,我给出Delphi的注册机(我仍将其写为函数的形式): &J?& y}
function KeyGen(Name: String): String; bt6o {7Z
var 5Og3Y!\=
S:String[16]; PuW^8f&5-
P:String; UtRAS]1
Key:String; Tf=4@sa:
i,N,Z:integer; UtdPk7*j/
begin T#HDw8cpy
P:='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; H-ONM0u
if Length(Name)<16 then -!Z1jG{J4
Result:='机器码必须为16位...' fu+WgiY
else 4o! )U!6
begin d>k56wdm7
S:=Name; zA.Ob
for i:=1 to 16 do 8M<-P.k
begin dg2P Bd
N:=Ord(S); (B6 xsHX"
N:=N+1500; 1){g0#Hf#y
Z:= N mod 62; _VfJV?A
Z:=Z+1; ,O4?Q-{p
Key:=Key+P[Z]; HA`Qc`m=
end; pS_m;$:
Result:=Key; 2q//q(,YW
end; -C8 C&-RY
end; |1[%#hX6
?ZJ1 w
呵呵,这一章就是最后一章了,现在也写完了.... 打个Kiss~~ A#W>X?:6V
<本章完>