AlgoLab PtVector的破解及序號產生器的編寫 (17千字)

看雪資料發表於2001-05-04

軟體簡介: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]=-(X182),否則=(X182)

* 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-(-(X182))) mod 24h)+30h或37h=(X1+X182) mod 24h)+30h或37h
  (3) 如果i=4到17,yi=((X1-(X182))±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

相關文章