• 标 题:AlgoLab PtVector的破解及注册机的编写 (17千字)
  • 作 者:robot
  • 时 间:2001-5-4 13:46:41
  • 链 接:http://bbs.pediy.com

软件简介:algolab_photo_vector_v1.01  能将位图转换成矢量图!使用方便、功能强大!
下载地址:ftp://202.108.252.18/stcsr/rj/txtx/b-apv101.zip
破解工具:SoftICE 4.05,W32Dasm

说明:该软件没有壳,而且可以反复注册,真是合Cracker的脾气。分析一下程序后,进行暴破还是很容易的,但我更喜欢看他的注册码的计算。
破解开始:
运行程序后,选择Help下的About,就会进行注册。分别填入User Name,Company Name,E-mail Address和Registration,Ctrl+D进入SoftICE,下bpx GetWindowTextA,按注册按钮,程序会被拦下来,下面为用W32Dasm反编译的注册时的Call
* Reference To: MFC42.Ordinal:0217, Ord:0217h
                                  |
:004171AF E8E2120200              Call 00438496                ====> GetWindowTextA
......
:004171F5 8D55E0                  lea edx, dword ptr [ebp-20]
:004171F8 52                      push edx                      ====> Registration
:004171F9 8D45E8                  lea eax, dword ptr [ebp-18]
:004171FC 50                      push eax                      ====> E-mail Address
:004171FD 8D4DDC                  lea ecx, dword ptr [ebp-24]
:00417200 51                      push ecx                      ====> User Name
:00417201 E888CFFFFF              call 0041418E                ====> 关键比对,我们将追进去
:00417206 83C40C                  add esp, 0000000C
:00417209 898578FFFFFF            mov dword ptr [ebp+FFFFFF78], eax      ====> eax作为判断标志
:0041720F 83BD78FFFFFF00          cmp dword ptr [ebp+FFFFFF78], 00000000  ====> 是否注册成功
:00417216 7559                    jne 00417271                            ====> 没有则转
......
:00417234 6A00                    push 00000000
:00417236 6A00                    push 00000000

* Possible StringData Ref from Data Obj ->"Successful Registration!"
                                  |
:00417238 68740A4400              push 00440A74

* Reference To: MFC42.Ordinal:04B0, Ord:04B0h
                                  |
:0041723D E838130200              Call 0043857A              ====> MessageBoxA
:00417242 C645FC01                mov [ebp-04], 01
:00417246 8D4DF0                  lea ecx, dword ptr [ebp-10]
上面的其他判断过程略过,其中,eax=1则用户名输入错误;eax=2则E-mail地址输入错误;eax=3则注册码输入错误

从上面我们看出,00417201语句的call 0041418E是注册计算和比对的关键,我们追进去,如下:

:004141B8 8B4D10                  mov ecx, dword ptr [ebp+10]
:004141BB E860DEFEFF              call 00402020
:004141C0 50                      push eax                    ====> 注册码地址入栈
:004141C1 E8DF63FFFF              call 0040A5A5              ====> 计算注册码是否正确的call

此call首先比较注册码长度是否等于19(13h),如果相等则将前18个注册码的ascii值相加,除以36(24h),余数如果小于10,则检测值等于相应的数字,否则检测值等于'A'+余数-10,即为A-Z的一个字符,如果检测值与第19个字符相等,则注册码正确,返回EAX=1,否则EAX=0
4141C1语句可以这样理解,程序建立一个子程序或函数,用来检测注册码是否正确:call TestCorrect(Registration)

:004141C6 83C404                  add esp, 00000004
:004141C9 8845F8                  mov byte ptr [ebp-08], al
:004141CC 8B45F8                  mov eax, dword ptr [ebp-08]
:004141CF 25FF000000              and eax, 000000FF
:004141D4 85C0                    test eax, eax                ====> 注册码是否正确
:004141D6 7507                    jne 004141DF                ====> 正确则转
:004141D8 B803000000              mov eax, 00000003            ====> 错误号
:004141DD EB7F                    jmp 0041425E

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004141D6(C)
|
:004141DF 8B4D10                  mov ecx, dword ptr [ebp+10]
:004141E2 E839DEFEFF              call 00402020                ====> 返回ecx的内容地址指针
                                                              ====> 这里指向输入的注册码地址
:004141E7 50                      push eax

* Reference To: MSVCRT.strlen, Ord:02BEh
                                  |
:004141E8 E88F480200              Call 00438A7C
:004141ED 83C404                  add esp, 00000004
:004141F0 8945DC                  mov dword ptr [ebp-24], eax
:004141F3 8B4DDC                  mov ecx, dword ptr [ebp-24]
:004141F6 51                      push ecx                    ====> 注册码的长度
:004141F7 8B4D10                  mov ecx, dword ptr [ebp+10]
:004141FA E821DEFEFF              call 00402020
:004141FF 50                      push eax                    ====> 注册码地址
:00414200 6A11                    push 00000011                ====> 进行计算并变换代码的长度
:00414202 8D55E0                  lea edx, dword ptr [ebp-20]
:00414205 52                      push edx                    ====> 进行计算并变换代码的存放地址
:00414206 E84362FFFF              call 0040A44E                ====> 代码变换,共17(11h)个,很重要
:0041420B 83C410                  add esp, 00000010
:0041420E 8B4D08                  mov ecx, dword ptr [ebp+08]  ====> ecx用户名地址
:00414211 E80ADEFEFF              call 00402020
:00414216 50                      push eax
:00414217 E8FF5CFFFF              call 00409F1B                ====> 进行计算
对用户名变换后得到一个检测值,结果返回到EAX
变换过程:将用户名各个字符的ascii值相加,然后除以1Ah(26),余数+"A"-0Ah即余数+37h

:0041421C 83C404                  add esp, 00000004
:0041421F 8845FC                  mov byte ptr [ebp-04], al
:00414222 0FBE45E4                movsx eax, byte ptr [ebp-1C] ====> 变换代码的第5个值
:00414226 0FBE4DFC                movsx ecx, byte ptr [ebp-04] ====> 取出用户名的计算值
:0041422A 3BC1                    cmp eax, ecx                ====> 进行比较
:0041422C 7407                    je 00414235                  ====> 相等则继续
:0041422E B801000000              mov eax, 00000001            ====> 错误号
:00414233 EB29                    jmp 0041425E

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041422C(C)
|
:00414235 8B4D0C                  mov ecx, dword ptr [ebp+0C]
:00414238 E8E3DDFEFF              call 00402020
:0041423D 50                      push eax                    ====> E-mail地址
:0041423E E8D85CFFFF              call 00409F1B                ====> 进行计算,过程同用户名计算一样
:00414243 83C404                  add esp, 00000004
:00414246 8845F4                  mov byte ptr [ebp-0C], al
:00414249 0FBE55E6                movsx edx, byte ptr [ebp-1A] ====> 变换代码的第7个值
:0041424D 0FBE45F4                movsx eax, byte ptr [ebp-0C] ====> 取出E-mail的计算值
:00414251 3BD0                    cmp edx, eax                ====> 进行比较
:00414253 7407                    je 0041425C                  ====> 相等则转
:00414255 B802000000              mov eax, 00000002            ====> 错误号
:0041425A EB02                    jmp 0041425E

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00414253(C)
|
:0041425C 33C0                    xor eax, eax                ====> 正确的eax值

* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004141DD(U), :00414233(U), :0041425A(U)
|
:0041425E 8BE5                    mov esp, ebp
:00414260 5D                      pop ebp
:00414261 C3                      ret
上面这段的关键是对注册码进行变换和对用户名和E-mail地址的计算,其中,对用户名和E-mail地址的计算已经说完,现在看看如何对注册码进行变换,即414206的call 0040A44E语句,如下:

* Referenced by a CALL at Addresses:
|:00409D73  , :0040A673  , :0040A6F6  , :00414206  , :004142DD 
|
:0040A44E 55                      push ebp
:0040A44F 8BEC                    mov ebp, esp
:0040A451 83EC24                  sub esp, 00000024
:0040A454 C745F810000000          mov [ebp-08], 00000010      ====> 设初值
:0040A45B 8B4510                  mov eax, dword ptr [ebp+10]
:0040A45E 8A4811                  mov cl, byte ptr [eax+11]    ====> 取出注册码的第18个字符
:0040A461 884DF4                  mov byte ptr [ebp-0C], cl
:0040A464 8A55F4                  mov dl, byte ptr [ebp-0C]
:0040A467 52                      push edx                    ====> 第18个字符入栈
:0040A468 E801FDFFFF              call 0040A16E                ====> 进行计算,计算结果返回到eax中
上句可以看作是对某个参数进行变换的函数,如ChangeCode(edx),计算过程:如果edx(要计算的字符的ascii值)在30h-39h,则返回结果为0-9,否则返回结果为edx+"A"-0ah=edx-37h

:0040A46D 83C404                  add esp, 00000004
:0040A470 8945FC                  mov dword ptr [ebp-04], eax  ====> 计算结果放到[ebp-04]中,为说明方便,设值为X18
:0040A473 8B4508                  mov eax, dword ptr [ebp+08]  ====> 变换码的地址
:0040A476 8B4D10                  mov ecx, dword ptr [ebp+10]
:0040A479 8A11                    mov dl, byte ptr [ecx]      ====> 取注册码的第1个字符
:0040A47B 8810                    mov byte ptr [eax], dl      ====> 放到变换码中,即注册码的第1个字符也是变换码的第1个字符
:0040A47D C745F000000000          mov [ebp-10], 00000000      ====> 设初值
:0040A484 EB09                    jmp 0040A48F

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040A51D(U)
|
:0040A486 8B45F0                  mov eax, dword ptr [ebp-10]  ====> 取出变换次数值
:0040A489 83C001                  add eax, 00000001            ====> 准备取下一个字符
:0040A48C 8945F0                  mov dword ptr [ebp-10], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040A484(U)
|
:0040A48F 8B4DF0                  mov ecx, dword ptr [ebp-10]
:0040A492 3B4DF8                  cmp ecx, dword ptr [ebp-08]  ====> 是否变换完毕
:0040A495 0F8D87000000            jnl 0040A522                ====> 没有,则继续
:0040A49B 8B45F0                  mov eax, dword ptr [ebp-10]  ====> 取出变换次数值
:0040A49E 99                      cdq
:0040A49F 2BC2                    sub eax, edx
:0040A4A1 D1F8                    sar eax, 1                  ====> 除以2
:0040A4A3 8945E4                  mov dword ptr [ebp-1C], eax
:0040A4A6 8B55FC                  mov edx, dword ptr [ebp-04]  ====> 第18个注册码的计算值X18
:0040A4A9 F7DA                    neg edx                      ====> 取负数,即-X18
:0040A4AB 2B55F0                  sub edx, dword ptr [ebp-10]  ====> -X18-变换次数
:0040A4AE 8955DC                  mov dword ptr [ebp-24], edx  ====> 放到[ebp-24]中
:0040A4B1 837DE400                cmp dword ptr [ebp-1C], 00000000  ====> 要变换的是否是第2、3个的注册码
:0040A4B5 7409                    je 0040A4C0                      ====> 是则转
:0040A4B7 8B45FC                  mov eax, dword ptr [ebp-04]  ====> 第18个注册码的计算值X18
:0040A4BA 0345F0                  add eax, dword ptr [ebp-10]  ====> X18+变换次数
:0040A4BD 8945DC                  mov dword ptr [ebp-24], eax  ====> 放到[ebp-24]中
40A486-40A4BD的计算:i=1~19表示注册码字符所在的位置,如果变换的是第2和3个的注册码,则[ebp-24]=-(X18+i-2),否则=(X18+i-2)

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040A4B5(C)
|
:0040A4C0 6A24                    push 00000024                \
:0040A4C2 8B4DDC                  mov ecx, dword ptr [ebp-24]  \
:0040A4C5 F7D9                    neg ecx                      |
:0040A4C7 51                      push ecx                      ====> 这段跟注册码变换无关
:0040A4C8 E829FEFFFF              call 0040A2F6                |
:0040A4CD 83C408                  add esp, 00000008            /
:0040A4D0 8945E8                  mov dword ptr [ebp-18], eax /

:0040A4D3 8B5510                  mov edx, dword ptr [ebp+10]  ====> 注册码地址
:0040A4D6 0355F0                  add edx, dword ptr [ebp-10]
:0040A4D9 8A4201                  mov al, byte ptr [edx+01]    ====> 取出相应的注册码
:0040A4DC 8845E0                  mov byte ptr [ebp-20], al
:0040A4DF 8A4DE0                  mov cl, byte ptr [ebp-20]
:0040A4E2 51                      push ecx
:0040A4E3 E886FCFFFF              call 0040A16E                ====> 进行变换
:0040A4E8 83C404                  add esp, 00000004
:0040A4EB 8945EC                  mov dword ptr [ebp-14], eax  ====> 结果放入到[ebp-14]中
:0040A4EE 8B55EC                  mov edx, dword ptr [ebp-14]
:0040A4F1 2B55DC                  sub edx, dword ptr [ebp-24]  ====> -[ebp-24],因此跟注册码所在的位置有关
:0040A4F4 8955EC                  mov dword ptr [ebp-14], edx
:0040A4F7 6A24                    push 00000024                ====> 为什么是24h?因为字符0~9加上A~Z正好共36个(24h)
:0040A4F9 8B45EC                  mov eax, dword ptr [ebp-14]
:0040A4FC 50                      push eax
:0040A4FD E8F4FDFFFF              call 0040A2F6                ====> 进行变换
变换过程:如果[ebp-14]为负数,则[ebp-14]+24h,直到为非负值且小于24h;如果[ebp-14]为正数且大于等于24h,则[ebp-14]-24h直到为非负值且小于24h

:0040A502 83C408                  add esp, 00000008
:0040A505 8945EC                  mov dword ptr [ebp-14], eax
:0040A508 8B4DEC                  mov ecx, dword ptr [ebp-14]
:0040A50B 51                      push ecx
:0040A50C E8A9FBFFFF              call 0040A0BA                ====> 根据上面相应的计算结果确定变换代码
确定过程:如果[ebp-14](即ecx)的值在0-9,则转为相应的数字,即eax=ecx+30h,否则eax=ecx+"A"-0ah=ecx+37h

:0040A511 83C404                  add esp, 00000004
:0040A514 8B5508                  mov edx, dword ptr [ebp+08]
:0040A517 0355F0                  add edx, dword ptr [ebp-10]
:0040A51A 884201                  mov byte ptr [edx+01], al    ====> 变换结果放入到变换代码地址中
:0040A51D E964FFFFFF              jmp 0040A486

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040A495(C)
|
:0040A522 8BE5                    mov esp, ebp
:0040A524 5D                      pop ebp
:0040A525 C3                      ret

总结上面的变换过程:
1. 注册码必须是19个字符且字符范围在0~9和A~Z之间
2. 设注册码用R来表示,则R19=((R1+R2+...+R18) mod 24h)+(30h或37h),如果前面的余数<=9则+30h,否则+37h
3. 设第18位注册码的变换码为X18,则X18=R18-37h(注册码在A~Z)或X18=R18-30h(注册码在0~9),如果用笔计算一下就可知道,R18肯定是A~Z之间的字符,因此,X18=R18-37h
4. 用户名和E-mail地址名的计算值=(所有字符的ascii值相加 mod 1Ah)+37h,注意:用户名和E-mail地址名不能用中文字符
5. 变换代码的计算过程(用y表示)
  (1) y1=r1
  (2) 如果i=2或3, yi=((X1-(-(X18+i-2))) mod 24h)+30h或37h=(X1+X18+i-2) mod 24h)+30h或37h
  (3) 如果i=4到17,yi=((X1-(X18+i-2))±n*24h)+30h或37h=(X1-X18-i+2±n*24h)+30h或37h  n=0,1,2
6. y5与用户名的计算值相等,y7与E-mail地址的计算值相等,因此,y5和y7肯定是在A~Z之间
根据上面的变换过程我们可以做出这个软件的注册器,第一种方法是先构造一个19位的注册码,然后利用第5,7,18位的注册码再相应构造用户名和E-mail地址,这种方法虽然简单,但用户名和E-mail地址可能很难看,因为我们还是喜欢用户名和E-mail地址易懂的注册码,因此采用第二种方法
第二种方法是先构造用户名和密码,生成相应的计算值,再构造第18位的注册码,然后根据用户名和密码的计算值及第18位的注册码确定第5和第7位的注册码,而第1~4,6,8~17位的注册码随机生成,只要符合在0~9或A~Z之间即可,最后生成第18位的注册码,根据这个思路,注册机如下(VB程序):
Private Sub Command1_Click()
    Dim UserName, EmailAddress, RegCode, UserNameLong, EmailLong
    Dim CheckUserCode, CheckEmailCode, RandCode, CheckCode
    RegCode = "": CheckCode = ""
    UserName = Text1.Text
    EmailAddress = Text2.Text
    UserNameLong = Len(Text1.Text)
    EmailLong = Len(Text2.Text)
    If UserNameLong = 0 Then
        MsgBox "用户名不能为空!", vbOKOnly, "请输入用户名"
        Exit Sub
    Else
        For i = 1 To UserNameLong
            CheckUserCode = CheckUserCode + Asc(Mid(UserName, i, 1))
        Next
        CheckUserCode = (CheckUserCode Mod 26) + &H41
    End If
    If EmailLong = 0 Then
        MsgBox "Email地址不能为空!", vbOKOnly, "请输入Email地址"
        Exit Sub
    Else
        For i = 1 To EmailLong
            CheckEmailCode = CheckEmailCode + Asc(Mid(EmailAddress, i, 1))
        Next
        CheckEmailCode = (CheckEmailCode Mod 26) + &H41
    End If
GetReg18:
    Reg18 = MakeRndCode()
    If Reg18 <= &H39 Then
        GoTo GetReg18
    Else
        Reg18Mod = Reg18 - &H37
    End If
    Reg5 = (Reg18Mod + 3 + CheckUserCode - &H37) Mod &H24
    If Reg5 <= 9 Then
        Reg5 = Reg5 + &H30
    Else
        Reg5 = Reg5 + &H37
    End If
    If Reg5 > &H5A Then GoTo GetReg18
    Reg7 = (Reg18Mod + 5 + CheckEmailCode - &H37) Mod &H24
    If Reg7 <= 9 Then
        Reg7 = Reg7 + &H30

Else
        Reg7 = Reg7 + &H37
    End If
    If Reg7 > &H5A Then GoTo GetReg18
    For i = 1 To 4
    RandCode = MakeRndCode()
        RegCode = RegCode + Chr(RandCode)
    Next
        RegCode = RegCode + Chr(Reg5)
    RandCode = MakeRndCode()
        RegCode = RegCode + Chr(RandCode)
        RegCode = RegCode + Chr(Reg7)
    For i = 8 To 17
    RandCode = MakeRndCode()
        RegCode = RegCode + Chr(RandCode)
    Next
        RegCode = RegCode + Chr(Reg18)
    RandCode = 0
    For i = 1 To 18
        RandCode = RandCode + Asc(Mid(RegCode, i, 1))
    Next
    RandCode = RandCode Mod &H24
    If RandCode <= 9 Then
        RegCode = RegCode + Chr(RandCode + &H30)
    Else
        RegCode = RegCode + Chr(RandCode + &H37)
    End If
    Text3.Text = RegCode
End Sub

Function MakeRndCode()
Dim a
    Randomize
NextRnd:
    Do
        a = Int(Rnd * 120)
    Loop Until a >= &H30 And a <= &H5A
        If a > &H39 And a < &H41 Then
            GoTo NextRnd
        End If
    MakeRndCode = a
End Function