一个判断F(UserName) == G(SN)的软件注册算法分析

  这是一个典型的判断F(UserName) == G(SN)的软件,由于该软件属于商业软件,所以出于可以理解的

原因,这里省略掉该软件的名称等信息,我们只是分析和学习他的注册算法。

[Cracker]     : prince

[时间]        : 2005.03.27

[声明]        : 只做技术交流,不做商业用途,如果你手头宽裕并喜欢这个软件的话请支持正版软件。

[E-mail]      : Cracker_prince@163.com

[软件信息]

[软件说明]    : 该软件是一套基于数据库的客户管理软件,就像每个公司的销售人员手中的名片夹一样,它

可以管理整个公司的客户的所有信息,方便查看,编辑等,所以它是面向企业级的商业软件。

[保护方式]    : 序列号保护

[限制方式]    : 时间限制

[外壳保护]    : ASPack 2.12 -> Alexey Solodovnikov

[编译器/语言] : Borland Delphi 6.0 - 7.0  / PASCAL

  
  用AspackDie搞定ASPack,无自校验,脱壳后可直接运行。启动时要求你输入用户名和注册码,我们输

入用户名:prince,注册码:98765432101234567890a,对于注册码的格式后面的跟踪会分析。点确定后有错误

提示,根据错误提示很容易定位关键部分代码,下断点在006056AB,重新注册,确定被断下:

---------------------------------------------------------------------------------
006056AB   |.>push eax
006056AC   |.>lea edx,dword ptr ss:[ebp-1C]
006056AF   |.>mov eax,dword ptr ds:[ebx+30C]
006056B5   |.>call Unpacked.00472010               ;  取假码“98765432101234567890a”
006056BA   |.>mov eax,dword ptr ss:[ebp-1C]        ;  假码送EAX
006056BD   |.>lea ecx,dword ptr ss:[ebp-6]
006056C0   |.>lea edx,dword ptr ss:[ebp-5]
006056C3   |.>call Unpacked.006051FC               ;  根据假码计算比较中间码1
006056C8   |.>lea edx,dword ptr ss:[ebp-24]
006056CB   |.>mov eax,dword ptr ds:[ebx+304]
006056D1   |.>call Unpacked.00472010               ;  取用户名“prince”
006056D6   |.>mov eax,dword ptr ss:[ebp-24]        ;  用户名送EAX
006056D9   |.>lea edx,dword ptr ss:[ebp-20]
006056DC   |.>call Unpacked.006050B4               ;  根据用户名计算比较中间码2
006056E1   |.>mov eax,dword ptr ss:[ebp-20]        ;  根据用户名计算出的比较中间码3
006056E4   |.>mov edx,dword ptr ss:[ebp-4]         ;  根据假码计算出的比较中间码4
006056E7   |.>call Unpacked.0040499C               ;  比较中间码3和中间码4
006056EC   |.>jnz Unpacked.006057B2                ;  不等则跳到失败提示
006056F2   |.>call Unpacked.0040BBA0               ;  取当前时间计算一个浮点值
006056F7   |.>add esp,-8                           ; /
006056FA   |.>fstp qword ptr ss:[esp]              ; |Arg1 (8-byte)
006056FD   |.>wait                                 ; |
006056FE   |.>mov eax,1                            ; |
00605703   |.>call Unpacked.00494A44               ; \Unpacked.00494A44
00605708   |.>fcomp qword ptr ss:[ebp-18]          ;  跟上面计算过的值进行比较,大于则提示过期
0060570B   |.>fstsw ax
0060570D   |.>sahf
0060570E   |.>jbe short Unpacked.0060571F          ;  跳到成功提示画面,否则提示过期
00605710   |.>mov eax,Unpacked.00605814
00605715   |.>call Unpacked.0043B148
0060571A   |.>jmp Unpacked.006057CA

-----------------------------------------------------------------------------------------------
  我们可以看出注册判断分为两步:1,把用户输入的假码经过计算得到一个中间码,再把用户名也经过

某种运算得到一个中间码,然后进行判断是否相等。如果不等,直接跳到错误提示,反之继续第2步。2,将用

户输入的注册码经过某种计算得到一个值,再获取当前时间计算一个值,二者进行比较,如果前一个值小于后

面的值,则提示注册码过期,反之则注册成功!
  这里我们着重分析G(SN)函数,即要找到一个SN,使之满足F(UserName)==G(SN),这样也就达到了解密

的目的,所以对于F(UserName)函数,这里不做分析,我们只要知道经过变换之后的关键比较中间码3就可以了

入手分析了。
  先看根据注册码计算中间码4的过程,006056C3处跟进:
------------------------------------------------------------------------------------------------
006051FC   /$>push ebp
006051FD   |.>mov ebp,esp
006051FF   |.>push ecx
00605200   |.>mov ecx,5                            ;  ECX=5
00605205   |>>/push 0
00605207   |.>|push 0
00605209   |.>|dec ecx
0060520A   |.>\jnz short Unpacked.00605205         ;  向堆栈里压10个0
0060520C   |.>xchg dword ptr ss:[ebp-4],ecx
0060520F   |.>push ebx
00605210   |.>push esi
00605211   |.>push edi
00605212   |.>mov esi,ecx
00605214   |.>mov ebx,edx
00605216   |.>mov dword ptr ss:[ebp-4],eax         ;  取假码写入堆栈[EBP-4]
00605219   |.>mov edi,dword ptr ss:[ebp+14]
0060521C   |.>xor eax,eax                          ;  EAX清零
0060521E   |.>push ebp
0060521F   |.>push Unpacked.0060538B
00605224   |.>push dword ptr fs:[eax]
00605227   |.>mov dword ptr fs:[eax],esp
0060522A   |.>mov eax,dword ptr ss:[ebp+8]
0060522D   |.>call Unpacked.00404590               ;  未知,无关函数
00605232   |.>mov byte ptr ds:[ebx],0
00605235   |.>mov byte ptr ds:[esi],0
00605238   |.>mov byte ptr ds:[edi],1
0060523B   |.>mov eax,dword ptr ss:[ebp+10]
0060523E   |.>mov dword ptr ds:[eax],1
00605244   |.>mov cx,1                             ;  CX=1
00605248   |.>mov dx,1                             ;  DX=1
0060524C   |.>mov ax,7D0                           ;  AX=7D0(十进制2000)
00605250   |.>call Unpacked.0040B92C               ;  根据日期2000计算出一个浮点值

36526.000000000000存ST0
00605255   |.>mov eax,dword ptr ss:[ebp+C]
00605258   |.>fstp qword ptr ds:[eax]              ;  该浮点值写入内存
0060525A   |.>wait
0060525B   |.>lea edx,dword ptr ss:[ebp-8]
0060525E   |.>mov eax,dword ptr ss:[ebp-4]         ;  取假码(98765432101234567890a)
00605261   |.>call Unpacked.006043F0               ;  关键计算,跟进

----------------------------------------------------------------------------------------------
00605261处为关键计算,跟进:
----------------------------------------------------------------------------------------------
006043F0   /$>push ebp
006043F1   |.>mov ebp,esp
006043F3   |.>add esp,-20
006043F6   |.>push ebx
006043F7   |.>push esi
006043F8   |.>push edi
006043F9   |.>xor ecx,ecx                          ;  ECX清零
006043FB   |.>mov dword ptr ss:[ebp-20],ecx
006043FE   |.>mov dword ptr ss:[ebp-1C],ecx
00604401   |.>mov dword ptr ss:[ebp-18],ecx
00604404   |.>mov dword ptr ss:[ebp-14],ecx        ;  以上都是堆栈清零
00604407   |.>mov edi,edx
00604409   |.>mov dword ptr ss:[ebp-4],eax         ;  假码写入[EBP-4]
0060440C   |.>xor eax,eax
0060440E   |.>push ebp
0060440F   |.>push Unpacked.0060450A
00604414   |.>push dword ptr fs:[eax]
00604417   |.>mov dword ptr fs:[eax],esp
0060441A   |.>mov eax,edi
0060441C   |.>call Unpacked.00404590               ;  未知,无关函数
00604421   |.>mov eax,dword ptr ss:[ebp-4]         ;  取假码送EAX
00604424   |.>call Unpacked.00404850               ;  取假码个数送EAX
00604429   |.>dec eax                              ;  假码个数-1
0060442A   |.>jl Unpacked.006044EF
00604430   |.>mov dword ptr ss:[ebp-C],3E
00604437   |.>lea eax,dword ptr ss:[ebp-14]
0060443A   |.>mov edx,dword ptr ss:[ebp-4]         ;  取假码
0060443D   |.>mov dl,byte ptr ds:[edx]             ;  取假码第1个字符
0060443F   |.>call Unpacked.00404778               ;  未知,无关函数
00604444   |.>mov eax,dword ptr ss:[ebp-14]        ;  下面是一张表
00604447   |.>mov edx,Unpacked.00604520            ;  ASCII 

"UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM"
0060444C   |.>call Unpacked.00404B94               ;  查表求出假码的第1个字符在表中的位置
00604451   |.>mov dword ptr ss:[ebp-8],eax         ;  保存该字符在表中的位置
00604454   |.>cmp dword ptr ss:[ebp-8],0           ;  判断该字符是否在表中
00604458   |.>je Unpacked.006044EF
0060445E   |.>mov eax,dword ptr ss:[ebp-4]         ;  再取假码
00604461   |.>call Unpacked.00404850               ;  再求假码个数
00604466   |.>sub eax,2                            ;  个数-2
00604469   |.>jl Unpacked.006044EF
0060446F   |.>inc eax                              ;  再+1
00604470   |.>mov dword ptr ss:[ebp-10],eax        ;  保存该值
00604473   |.>mov esi,2                            ;  ESI=2
00604478   |>>/lea eax,dword ptr ss:[ebp-18]
0060447B   |.>|mov edx,dword ptr ss:[ebp-4]        ;  取假码送EDX
0060447E   |.>|mov dl,byte ptr ds:[edx+esi-1]      ;  循环取假码的第i个字符(i=2;i<22;i++)
00604482   |.>|call Unpacked.00404778
00604487   |.>|mov eax,dword ptr ss:[ebp-18]
0060448A   |.>|mov edx,Unpacked.00604520           ;  ASCII 

"UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM"
0060448F   |.>|call Unpacked.00404B94              ;  取出该字符在表中的位置
00604494   |.>|mov ebx,eax                         ;  该字符在表中的位置送EBX
00604496   |.>|test ebx,ebx
00604498   |.>|jle short Unpacked.006044D0
0060449A   |.>|sub ebx,dword ptr ss:[ebp-8]        ;  该字符在表中的位置-[EBP-8]这个变量
0060449D   |.>|dec ebx                             ;  结果再-1
0060449E   |.>|test ebx,ebx                        ;  判断结果是否为正
006044A0   |.>|jg short Unpacked.006044A9          ;  为正则跳过下面的循环
006044A2   |>>|/add ebx,dword ptr ss:[ebp-C]       ;  否则+3E(十进制62)
006044A5   |.>||test ebx,ebx                       ;  继续检测结果是否为正
006044A7   |.>|\jle short Unpacked.006044A2        ;  如果不为正则继续加3E
006044A9   |>>|lea eax,dword ptr ss:[ebp-1C]
006044AC   |.>|mov edx,Unpacked.00604520           ;  ASCII 

"UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM"
006044B1   |.>|mov dl,byte ptr ds:[edx+ebx-1]      ;  根据上面的结果查表求字符
006044B5   |.>|call Unpacked.00404778
006044BA   |.>|mov edx,dword ptr ss:[ebp-1C]
006044BD   |.>|mov eax,edi
006044BF   |.>|call Unpacked.00404858
006044C4   |.>|mov eax,dword ptr ds:[edi]
006044C6   |.>|movzx eax,byte ptr ds:[eax+esi-2]   ;  该字符为本次循环的最终结果
006044CB   |.>|add dword ptr ss:[ebp-8],eax        ;  将该字符的ASCII码加到[EBP-8],准备下一次循


006044CE   |.>|jmp short Unpacked.006044E9
006044D0   |>>|lea eax,dword ptr ss:[ebp-20]
006044D3   |.>|mov edx,dword ptr ss:[ebp-4]
006044D6   |.>|mov dl,byte ptr ds:[edx+esi-1]
006044DA   |.>|call Unpacked.00404778
006044DF   |.>|mov edx,dword ptr ss:[ebp-20]
006044E2   |.>|mov eax,edi
006044E4   |.>|call Unpacked.00404858
006044E9   |>>|inc esi                             ;  i++
006044EA   |.>|dec dword ptr ss:[ebp-10]
006044ED   |.>\jnz short Unpacked.00604478         ;  继续循环
006044EF   |>>xor eax,eax
006044F1   |.>pop edx
006044F2   |.>pop ecx
006044F3   |.>pop ecx

-------------------------------------------------------------------------------------------------
  以上即为关键算法,总结一下:
  取假码第1个字符,查表UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM,将其

在表中的位置保存至[EBP-8],然后循环取假玛接下来的字符,查表求其在表中的位置,用该值减去[EBP-8],

再减1,判断结果是否为正,如果为负数则循环加62,直到该值为正数为止,也就是使最后这个值落在1-62之间

,然后按该值作为表的索引查找目标字符,比如最后结果为3,那么就把表的第3个字符z作为最后结果保存,接

下来[EBP-8]+最后求出的字符的ASCII码,继续下次循环,直到取完所有假码。
  将以上过程抽象成数学表达式,设第i个字符在表中的位置为x,[EBP-8]为y,最后求出的结果为z,那

么整个过程为:(x-y-1+n*62)%62=z,这里要求n要足够大,以保证(x-y-1+n*62)>0。记下这个表达式,我们继

续分析。返回到00605266:
------------------------------------------------------------------------------------------------
00605266   |.>mov eax,dword ptr ss:[ebp-8]         ;  求出的中间码1("HINH88FI8wOZeQRU28mQ")送EAX
00605269   |.>call Unpacked.00404850               ;  求该中间码个数
0060526E   |.>cmp eax,14                           ;  个数同0x14比较,
00605271   |.>jnz Unpacked.00605370                ;  不等就失败了,所以通过这里我们知道注册码应

该是21个
00605277   |.>mov eax,dword ptr ss:[ebp+8]
0060527A   |.>push eax
0060527B   |.>mov ecx,0A
00605280   |.>mov edx,0B
00605285   |.>mov eax,dword ptr ss:[ebp-8]
00605288   |.>call Unpacked.00404AB0
0060528D   |.>lea eax,dword ptr ss:[ebp-C]
00605290   |.>push eax
00605291   |.>mov ecx,1
00605296   |.>mov edx,1
0060529B   |.>mov eax,dword ptr ss:[ebp-8]
0060529E   |.>call Unpacked.00404AB0
006052A3   |.>mov eax,dword ptr ss:[ebp-C]
006052A6   |.>mov edx,Unpacked.006053A4
006052AB   |.>call Unpacked.0040499C
006052B0   |.>sete byte ptr ds:[ebx]
006052B3   |.>lea eax,dword ptr ss:[ebp-10]
006052B6   |.>push eax
006052B7   |.>mov ecx,1
006052BC   |.>mov edx,2
006052C1   |.>mov eax,dword ptr ss:[ebp-8]
006052C4   |.>call Unpacked.00404AB0
006052C9   |.>mov eax,dword ptr ss:[ebp-10]
006052CC   |.>mov edx,Unpacked.006053A4
006052D1   |.>call Unpacked.0040499C
006052D6   |.>sete byte ptr ds:[esi]
006052D9   |.>lea eax,dword ptr ss:[ebp-14]
006052DC   |.>push eax
006052DD   |.>mov ecx,4
006052E2   |.>mov edx,3
006052E7   |.>mov eax,dword ptr ss:[ebp-8]         ;  取中间码1
006052EA   |.>call Unpacked.00404AB0               ;  截取第3到第6个字符
006052EF   |.>mov eax,dword ptr ss:[ebp-14]        ;  保存EAX
006052F2   |.>mov edx,Unpacked.006053B0            ;  常量ASCII"9999"
006052F7   |.>call Unpacked.0040499C               ;  这里便是判断是否为VIP用户的关键
006052FC   |.>setne byte ptr ds:[edi]              ;  设置标志
006052FF   |.>lea eax,dword ptr ss:[ebp-18]
00605302   |.>push eax
00605303   |.>mov ecx,4
00605308   |.>mov edx,7
0060530D   |.>mov eax,dword ptr ss:[ebp-8]
00605310   |.>call Unpacked.00404AB0
00605315   |.>mov eax,dword ptr ss:[ebp-18]
00605318   |.>mov edx,1
0060531D   |.>call Unpacked.00409B24
00605322   |.>mov edx,dword ptr ss:[ebp+10]
00605325   |.>mov dword ptr ds:[edx],eax
00605327   |.>lea eax,dword ptr ss:[ebp-1C]
0060532A   |.>push eax
0060532B   |.>mov ecx,4
00605330   |.>mov edx,3
00605335   |.>mov eax,dword ptr ss:[ebp-8]         ;  中间码1
00605338   |.>call Unpacked.00404AB0               ;  仍然截取第3到第6个字符
0060533D     >mov eax,dword ptr ss:[ebp-1C]        ;  送EAX
00605340   |.>mov edx,1
00605345   |.>call Unpacked.00409B24               ;  根据该字符串计算软件的日期限制
0060534A   |.>mov dword ptr ss:[ebp-20],eax
0060534D   |.>fild dword ptr ss:[ebp-20]           ;  计算结果送ST0
00605350   |.>fstp tbyte ptr ss:[ebp-2C]           ;  保存至堆栈
00605353   |.>wait
00605354   |.>mov cx,1
00605358   |.>mov dx,1
0060535C   |.>mov ax,7D0
00605360   |.>call Unpacked.0040B92C               ;  以2000为参数计算浮点值
00605365   |.>fld tbyte ptr ss:[ebp-2C]            ;  取上面的计算结果
00605368   |.>faddp st(1),st                       ;  2者相加,得数作为软件日期限制的标准
0060536A   |.>mov eax,dword ptr ss:[ebp+C]
0060536D   |.>fstp qword ptr ds:[eax]              ;  结果保存,后面要用到
0060536F   |.>wait
00605370   |>>xor eax,eax
00605372   |.>pop edx
00605373   |.>pop ecx
00605374   |.>pop ecx
------------------------------------------------------------------------------------------------
  006052F7处的函数对计算出的中间码1的第3到第6位同“9999”比较,经过后面的分析可知如果中间码

的第3到第6位==9999的话,软件就认为改序列号为VIP序列号,不再有日期限制。
  00605345处就是根据中间码1的第3到第6位计算软件的到期日期。后面的00605360函数就是以2000年为

基数计算一个基本值,然后跟00605345处计算出的浮点值相加,保存这个值,该值就是后面比较是否过期的标

准。接下来我们一路返回:
------------------------------------------------------------------------------------------------
006056AC   |.>lea edx,dword ptr ss:[ebp-1C]
006056AF   |.>mov eax,dword ptr ds:[ebx+30C]
006056B5   |.>call Unpacked.00472010               ;  取假码“98765432101234567890a”
006056BA   |.>mov eax,dword ptr ss:[ebp-1C]        ;  假码送EAX
006056BD   |.>lea ecx,dword ptr ss:[ebp-6]
006056C0   |.>lea edx,dword ptr ss:[ebp-5]
006056C3   |.>call Unpacked.006051FC               ;  根据假码计算比较中间码1
006056C8   |.>lea edx,dword ptr ss:[ebp-24]        ;  <---- 返回到这里
006056CB   |.>mov eax,dword ptr ds:[ebx+304]
006056D1   |.>call Unpacked.00472010               ;  取用户名“prince”
006056D6   |.>mov eax,dword ptr ss:[ebp-24]        ;  用户名送EAX
006056D9   |.>lea edx,dword ptr ss:[ebp-20]
006056DC   |.>call Unpacked.006050B4               ;  根据用户名计算比较中间码2
006056E1   |.>mov eax,dword ptr ss:[ebp-20]        ;  根据用户名计算出的比较中间码3("07aa94e7b5")
006056E4   |.>mov edx,dword ptr ss:[ebp-4]         ;  根据假码计算出的比较中间码4("OZeQRU28mQ")
006056E7   |.>call Unpacked.0040499C               ;  比较中间码3和中间码4
006056EC   |.>jnz Unpacked.006057B2                ;  不等则跳到失败提示
006056F2   |.>call Unpacked.0040BBA0               ;  取当前时间计算一个浮点值
006056F7   |.>add esp,-8                           ; /
006056FA   |.>fstp qword ptr ss:[esp]              ; |Arg1 (8-byte)
006056FD   |.>wait                                 ; |
006056FE   |.>mov eax,1                            ; |
00605703   |.>call Unpacked.00494A44               ; \Unpacked.00494A44
00605708   |.>fcomp qword ptr ss:[ebp-18]          ;  跟上面计算过的值进行比较,大于则提示过期
0060570B   |.>fstsw ax
0060570D   |.>sahf
0060570E   |.>jbe short Unpacked.0060571F          ;  跳到成功提示画面,否则提示过期
00605710   |.>mov eax,Unpacked.00605814

---------------------------------------------------------------------------------------------
  接下来取用户名“prince”开始计算中间码2,再对中间码2经过查表,截取等操作计算出中间码3:

07aa94e7b5,因为我们重点讨论如何解密G(SN)函数,所以这里不对计算用户名的过程进行分析;对于中间码4

的计算则很简单,就是中间码1("HINH88FI8wOZeQRU28mQ")的后10位。
  006056E7函数对中间码3和中间码4进行了比较,不等就OVER。
  哈哈,过程清楚了,现在我们要成功的话首先要满足第1个条件:使中间码3和中间码4相等,也就是说

,我们要构造一个注册码,使其经过计算后得到中间码为后10位为07aa94e7b5才能满足第1个条件。第2个条件

我们也分析过了,要想成为VIP,就要中间码1的第3到第6位为“9999”,至于其他没有限制的字符我们先不管

。好,先构造一个符合条件的中间码出来,不妨设中间码为:019999001007aa94e7b5,即

G(SN)==019999001007aa94e7b5, 那么现在我们的任务就是求G的逆函数G', 通过G'(019999001007aa94e7b5)求

出SN。看一下上面我们分析过的计算中间码1的过程表达式:(x-y-1+n*62)%62=z,整理一下,x就是我们要求的

SN的单个字符在表UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM中的位置,y是这样一

个变量:第一次循环y==SN的第1个字符在表中的位置,以后每次循环y+=第i个中间码的ASCII码,n为指定好的

一个整数,不妨设100000,z为中间码1的第i个字符在表中的位置。哈,发现了吗?要求出SN的第i位x,只需要

确定一个值,其他都是已知量,这个值就是SN的第1个字符在表

UlzCVB78typa3nmQWE4ASDsd90qwexcvIOPuioJ12FGH56TYKLZXrfghjkbRNM中的位置!也就是说,SN的第1个字符可

以是这表中的62个字符中的任意一个,每个字符打头都可以算出一个唯一的中间码,说明同一个用户名可以有

62个注册码!说起来罗哩罗嗦的,我自己都晕了!用笔算,太累,还是让程序帮忙吧:
----------------------------------------------------------------------------------------------
#include "stdafx.h"
#include "stdio.h"

char chTable1[62] = {'U', 'l', 'z', 'C', 'V', 'B', '7', '8', 't', 'y',
           'p', 'a', '3', 'n', 'm', 'Q', 'W', 'E', '4', 'A',
           'S', 'D', 's', 'd', '9', '0', 'q', 'w', 'e', 'x',
           'c', 'v', 'I', 'O', 'P', 'u', 'i', 'o', 'J', '1',
           '2', 'F', 'G', 'H', '5', '6', 'T', 'Y', 'K', 'L',
           'Z', 'X', 'r', 'f', 'g', 'h', 'j', 'k', 'b', 'R',
           'N', 'M'};

char chEncryptKey[20] = {'0', '1', '9', '9', '9', '9', '0', '0', '1', '0',
             '0', '7', 'a', 'a', '9', '4', 'e', '7', 'b', 

'5'};  // 为了简便,这里指定了要解密的字符串,实际可以动态输入

int  nEncryptKey[20]  = {48, 49, 57, 57, 57, 57, 48, 48, 49, 48, 48, 55, 97, 97, 57, 52, 101, 55, 

98, 53};  // 为了简便,这里指定了要解密的字符串,实际可以动态输入

int GetPos(char m)
{
  int i;
  for (i = 0; i < 62; i++)
  {
    if (chTable1[i] == m)
    {
      return i;
    }
  }
  return -1;
}

int main(int argc, char* argv[])
{
  char chKey[21] = {0};
  int nTemp = 0;
  // 简单起见我们指定首字符为a,实际可以让程序随机生成
  chKey[0] = 'a';
  nTemp = GetPos(chKey[0]) + 1;
  if (0 == nTemp)
  {
    printf("Internal Error!\n");
    return -1;
  }
  else
  {
    int nPos;
    int nKeyPos = 0;
    for (int i = 0; i < 20; i++)
    {
      nPos = GetPos(chEncryptKey[i]) + 1;
      nKeyPos = nTemp + 1 + nPos;
      if (62 < nKeyPos)
      {
        nKeyPos = (nTemp + 1 + nPos) % 62;
      }

      chKey[i + 1] = chTable1[nKeyPos - 1];
      nTemp += nEncryptKey[i];
    }

    printf("My key is: \n");
    for (i = 0; i < 21; i++)
    {
      printf("%c", chKey[i]);
    }
    printf("\n");
  }

  return 0;
}

---------------------------------------------------------------------------------------------
  以上程序在VC++6.0和WIN2K平台上测试通过,输出结果:aJJpBUkf113Nw0NTuuruk
  将用户名“prince”和注册码aJJpBUkf113Nw0NTuuruk输入程序,注册成功,没有日期限制,VIP版!

你也可以将中间码1的第3到第6位改为其他数字,比如5000,这样算出来的注册码也是合法的,但有日期限制,

到2013年过期,呵呵,也够用了吧?
  总结:
  
  一个可用的注册码:
  User Name:         prince
        Key                aJJpBUkf113Nw0NTuuruk

  菜鸟写菜文,有失误还请大侠指教!

                                                                 prince
                                                            2005.03.27