如何破解Opera 4.0 Final

看雪資料發表於2015-11-15

標 題:如何破解Opera 4.0 Final

發信人:dr0

時 間:2000年7月6日

詳細資訊


written  by dr0

時間:2000年7月6日
                   
Opera 4.0 Final的註冊碼與4.0 beta 5相比,差別不大,但是和以前的版本相比,註冊碼判斷演算法有根本的變化,找註冊碼等價於寫序號產生器。不過我覺得這種只改變演算法的策略並不能很好地保護其軟體。

首先,我們用bpx GetDlgItemTextA設斷點,它會呼叫該函式3次,分別讀入Name、Company、Code。由於Name、Company與Code無關,故我們只要在它最後一次呼叫GetDlgItemTexA時攔住即可。如下:

:004420A6 8B6C2410                mov ebp, dword ptr [esp+10]
:004420AA BB2B010000              mov ebx, 0000012B
:004420AF 57                      push edi

* Reference To: USER32.GetDlgItemTextA, Ord:0104h
                                  |
:004420B0 8B3D5C555300            mov edi, dword ptr [0053555C]
:004420B6 8D8638010000            lea eax, dword ptr [esi+00000138]
:004420BC 53                      push ebx
:004420BD 50                      push eax
:004420BE 68BD2B0000              push 00002BBD
:004420C3 55                      push ebp
:004420C4 FFD7                    call edi                          //讀取Name
:004420C6 8D8664020000            lea eax, dword ptr [esi+00000264]
:004420CC 53                      push ebx
:004420CD 50                      push eax
:004420CE 68BE2B0000              push 00002BBE
:004420D3 55                      push ebp
:004420D4 FFD7                    call edi                        //讀取Company
:004420D6 8D8690030000            lea eax, dword ptr [esi+00000390]
:004420DC 53                      push ebx
:004420DD 50                      push eax
:004420DE 68BF2B0000              push 00002BBF
:004420E3 55                      push ebp
:004420E4 FFD7                    call edi                        //讀取Code

查閱一下API手冊,可以知道傳遞給GetDlgItemTextA( )的第3個引數是緩衝區的首址。看一下上面的程式碼,發現在3次呼叫GetDlgItemTextA( )時使用的緩衝區首址是esi+00000390,於是等它執行完CS:004420E4處的call edi指令後,馬上用BPR esi+390 esi+390+len rw作為斷點監視你輸入的假註冊碼,看它如何處理你輸入的假註冊碼,其中len是假註冊碼的長度。然後按F5執行,馬上在下面的地方又中斷下來:

:0050E680 F7C703000000            test edi, 00000003
:0050E686 7514                    jne 0050E69C
:0050E688 C1E902                  shr ecx, 02
:0050E68B 83E203                  and edx, 00000003
:0050E68E 83F908                  cmp ecx, 00000008
:0050E691 7229                    jb 0050E6BC
:0050E693 F3                      repz
:0050E694 A5                      movsd                          //在這裡中斷
:0050E695 FF2495A8E75000          jmp dword ptr [4*edx+0050E7A8]

很顯然,它要把我們的假註冊碼搬到記憶體的另一個地方去。清除所有斷點,直接按一下F12,回到上一層,我們準備從更高的呼叫層次上來分析一下程式大致流程,免得過早地陷入細節分析中去。如無特殊說明,以後就只按F10了。

:0043CF6C 68BC040000              push 000004BC
:0043CF71 56                      push esi
:0043CF72 57                      push edi
:0043CF73 E8E8160D00              call 0050E660                    //剛才是在這裡面

...............................................

:0043D00C FF750C                  push [ebp+0C]
:0043D00F 894514                  mov dword ptr [ebp+14], eax
:0043D012 E849110D00              call 0050E160                  //求註冊碼長度
:0043D017 83F80C                  cmp eax, 0000000C              //長度等於12嗎?
:0043D01A 59                      pop ecx
:0043D01B 753C                    jne 0043D059
:0043D01D 56                      push esi
:0043D01E E8A40F0D00              call 0050DFC7
:0043D023 BE00010000              mov esi, 00000100
:0043D028 56                      push esi
:0043D029 E8D30F0D00              call 0050E001
:0043D02E 59                      pop ecx
:0043D02F 8BF8                    mov edi, eax
:0043D031 59                      pop ecx
:0043D032 56                      push esi
:0043D033 57                      push edi

* Possible Reference to String Resource ID=22005: "Invalid registration number. "
                                                  "You have probably entered a previous"
                                  |
:0043D034 68F5550000              push 000055F5
:0043D039 8B0DB8205600            mov ecx, dword ptr [005620B8]
:0043D03F E8E6500800              call 004C212A
:0043D044 6A10                    push 00000010
:0043D046 FF7514                  push [ebp+14]
:0043D049 57                      push edi
:0043D04A FF7508                  push [ebp+08]

* Reference To: USER32.MessageBoxA, Ord:01BEh
                                  |
:0043D04D FF1574565300            Call dword ptr [00535674]

上述程式碼檢查你輸入的假註冊碼長度是否為12,若為12則認為是老版本的註冊碼,否則繼續檢查你輸入的假註冊碼的前12位是否和舊版本的相同。

:0043D086 8D45C4                  lea eax, dword ptr [ebp-3C]  //字串1
:0043D089 50                      push eax
:0043D08A 8D857CFFFFFF            lea eax, dword ptr [ebp-84]  //字串2
:0043D090 50                      push eax
:0043D091 E87A0F0D00              call 0050E010                //比較字串
:0043D096 83C420                  add  esp, 20
:0043D099 85C0                    test eax, eax
:0043D09B 7521                    jne 0043D0BE
:0043D09D 56                      push esi
:0043D09E E8240F0D00              call 0050DFC7
:0043D0A3 BE00010000              mov esi, 00000100
:0043D0A8 56                      push esi
:0043D0A9 E8530F0D00              call 0050E001
:0043D0AE 59                      pop ecx
:0043D0AF 8BF8                    mov edi, eax
:0043D0B1 59                      pop ecx
:0043D0B2 56                      push esi
:0043D0B3 57                      push edi

* Possible Reference to String Resource ID=22006: "Invalid registration number."
                                                  "You have entered a 3.5x or 3.6x"
                                  |
:0043D0B4 68F6550000              push 000055F6
:0043D0B9 E97BFFFFFF              jmp 0043D039

如果不相同,則馬上到了關鍵的判斷(由於W32Dasm犯傻,故改用SoftICE反彙編出來的程式碼),總算是有點盼頭了。

015F:0043D0CF  8D8790030000        LEA      EAX,[EDI+00000390]
015F:0043D0D5  50                  PUSH      EAX                          //假註冊碼
015F:0043D0D6  E812040000          CALL      0043D4ED                    //核心判斷
015F:0043D0DB  59                  POP      ECX
015F:0043D0DC  84C0                TEST      AL,AL
015F:0043D0DE  59                  POP      ECX
015F:0043D0DF  7507                JNZ      0043D0E8
015F:0043D0E1  C745FC01000000      MOV      DWORD PTR [EBP-04],00000001  //設good標誌位

按F8跟進call 0043D4ED中去,可以看到這個函式寫得簡明扼要:

:0043D4ED 55                      push ebp
:0043D4EE 8BEC                    mov ebp, esp
:0043D4F0 83EC18                  sub esp, 00000018
:0043D4F3 E8319FFDFF              call 00417429              //準備性的計算工作,不必跟進去
:0043D4F8 84C0                    test al, al
:0043D4FA 7530                    jne 0043D52C              //這裡是絕對不會跳的
:0043D4FC FF7508                  push [ebp+08]
:0043D4FF 8D45E8                  lea eax, dword ptr [ebp-18]
:0043D502 50                      push eax
:0043D503 E8EC690B00              call 004F3EF4              //重要,必須跟進去
:0043D508 59                      pop ecx
:0043D509 84C0                    test al, al
:0043D50B 59                      pop ecx
:0043D50C 751E                    jne 0043D52C
:0043D50E 8D45E8                  lea eax, dword ptr [ebp-18]
:0043D511 50                      push eax
:0043D512 E8889FFDFF              call 0041749F            //重要,必須跟進去
:0043D517 84C0                    test al, al
:0043D519 59                      pop ecx
:0043D51A 7510                    jne 0043D52C
:0043D51C 8D45E8                  lea eax, dword ptr [ebp-18]
:0043D51F 50                      push eax
:0043D520 E8D56B0B00              call 004F40FA            //重要,必須跟進去
:0043D525 84C0                    test al, al
:0043D527 59                      pop ecx
:0043D528 7502                    jne 0043D52C
:0043D52A C9                      leave
:0043D52B C3                      ret
:0043D52C 0CFF                    or al, FF                //bad guy
:0043D52E C9                      leave
:0043D52F C3                      ret

由此可知,要點在上述的3個call中。下面得改按F8,逐一分析這3個call。先跟進call 004F3EF4:

:004F3EF4 55                      push ebp
:004F3EF5 8BEC                    mov ebp, esp
:004F3EF7 51                      push ecx
:004F3EF8 53                      push ebx
:004F3EF9 56                      push esi
:004F3EFA 8B750C                  mov esi, dword ptr [ebp+0C]
:004F3EFD B02D                    mov al, 2D
:004F3EFF 57                      push edi
:004F3F00 384601                  cmp byte ptr [esi+01], al    //第1個字元為'-'嗎?
:004F3F03 0F85A4000000            jne 004F3FAD
:004F3F09 384607                  cmp byte ptr [esi+07], al    //第7個字元為'-'嗎?
:004F3F0C 0F859B000000            jne 004F3FAD
:004F3F12 38460D                  cmp byte ptr [esi+0D], al    //第13個字元為'-'嗎?
:004F3F15 0F8592000000            jne 004F3FAD
:004F3F1B 384613                  cmp byte ptr [esi+13], al    //第19個字元為'-'嗎?
:004F3F1E 0F8589000000            jne 004F3FAD
:004F3F24 384619                  cmp byte ptr [esi+19], al    //第25個字元為'-'嗎?
:004F3F27 0F8580000000            jne 004F3FAD

:004F3F2D 56                      push esi
:004F3F2E E82DA20100              call 0050E160                //求註冊碼長度
:004F3F33 83F81F                  cmp eax, 0000001F            //長度為31嗎?
:004F3F36 59                      pop ecx
:004F3F37 7574                    jne 004F3FAD

到了這裡,我們就知道註冊碼應包含31個字元,且第1、7、13、19、25個均為橫槓字元'-'。於是將假註冊碼改成
w-RSuhU-8bbTm-taXah-v3uMt-EDrKv(這個是可用的,呵呵),重複上面的步驟,到如下的地方。

下面是一個兩重迴圈,在這個兩重迴圈中,它要把你輸入的假註冊碼的後5部分轉換成5個長整數。根據其轉換過程,我們知道註冊碼的後5部分可以看作是5個五十進位制數。即RSuhU、8bbTm、taXah、v3uMt、EDrKv為5個五十進位制的數,只不過最低位
在最左邊。它所使用的五十進位制數的表格為"abcdefhijkmnprstuvwxyzABCDEFHJKLMNPQRSTUVWXY345678"。

  下面舉個例子,看它如何把註冊碼的第2部分即五十進位制數“RSuhU”轉換為對應的十六進位制數。
  1、首先取五十進位制數的最低位,即R,查表,發現R在該表中的下標為36,即五十進位制中的R對應十進位制中的36。用
      用36乘以50的0次方,得到36。
  2、再取五十進位制數RSuhU的次低位,即S,查表,發現S在該表中的下標為37,即五十進位制中的S對應十進位制中的37。
      用37乘以50的1次方,得到1850。
  3、取五十進位制數RSuhU的u位,查表,發現五十進位制的u對應十進位制的16。
      用16乘以50的2次方,得到40000。
  4、取五十進位制數RSuhU的h位,查表,發現五十進位制的h對應十進位制的6。
      用6乘以50的3次方,得到750000。
  5、取五十進位制數RSuhU的U位,查表,發現五十進位制的U對應十進位制的39。
      用39乘以50的4次方,得到243750000。
  將上述5個乘積累加,得到244541886。十進位制數244541886的十六進位制形式為0E9369BE,故五十進位制數RSuhU對應的十六進位制數為0E9369BE(當然前提是遵循Opera的作者所制定的轉換規則,即使用上面的那張表)。
  同理可把8bbTm、taXah、v3uMt、EDrKv轉換為對應的十六進位制數:04023177、023DCE97、05D430D9、068EFB70。
  除了轉換之外,它還限制轉換後的十六進位制數不得大於0x0FFFFFFF,即最高4 bit必須為0,這點在後面會用到。

:004F3F39 8B4508                  mov eax, dword ptr [ebp+08]
:004F3F3C 8A0E                    mov cl, byte ptr [esi]          //註冊碼的第1部分
:004F3F3E 8365FC00                and dword ptr [ebp-04], 00000000
:004F3F42 8D7E02                  lea edi, dword ptr [esi+02]
:004F3F45 8808                    mov byte ptr [eax], cl          //把註冊碼的第1部分搬到另外的地方
:004F3F47 83C004                  add eax, 00000004
:004F3F4A 894508                  mov dword ptr [ebp+08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F3FA7(C)
|
:004F3F4D 33DB                    xor ebx, ebx                      //累加和清0
:004F3F4F C7450C01000000          mov [ebp+0C], 00000001            //50的0次方
:004F3F56 33F6                    xor esi, esi                      //迴圈控制變數清0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F3F8A(C)
|
:004F3F58 0FBE0437                movsx eax, byte ptr [edi+esi]        //取五十進位制數的一位
:004F3F5C 50                      push eax
:004F3F5D FF3584D35300            push dword ptr [0053D384]
:004F3F63 E888AA0100              call 0050E9F0                        //查表
:004F3F68 59                      pop ecx
:004F3F69 85C0                    test eax, eax
:004F3F6B 59                      pop ecx
:004F3F6C 743F                    je 004F3FAD
:004F3F6E 2A0584D35300            sub al, byte ptr [0053D384]
:004F3F74 0FB6C0                  movzx eax, al                      //查表所得的下標
:004F3F77 0FAF450C                imul eax, dword ptr [ebp+0C]      //乘以50的n次方
:004F3F7B 03D8                    add ebx, eax                      //累加到ebx中
:004F3F7D 8B450C                  mov eax, dword ptr [ebp+0C]
:004F3F80 6BC032                  imul eax, 00000032                //50的n次方再乘以50
:004F3F83 46                      inc esi                            //迴圈控制變數++
:004F3F84 89450C                  mov dword ptr [ebp+0C], eax
:004F3F87 83FE05                  cmp esi, 00000005                  //5位全轉換完了?
:004F3F8A 7CCC                    jl 004F3F58                        //內迴圈
:004F3F8C F7C3000000F0            test ebx, F0000000                //轉換結果不得大於0x0FFFFFFF
:004F3F92 7519                    jne 004F3FAD                      //jump if bad guy
:004F3F94 8B4508                  mov eax, dword ptr [ebp+08]
:004F3F97 FF45FC                  inc [ebp-04]
:004F3F9A 83450804                add dword ptr [ebp+08], 00000004
:004F3F9E 83C706                  add edi, 00000006
:004F3FA1 837DFC05                cmp dword ptr [ebp-04], 00000005  //註冊碼的後5部分全轉換完了?
:004F3FA5 8918                    mov dword ptr [eax], ebx
:004F3FA7 7CA4                    jl 004F3F4D                      //外迴圈
:004F3FA9 32C0                    xor al, al                        //good guy
:004F3FAB EB02                    jmp 004F3FAF

經過上面的轉換,註冊碼的後5部分被轉換成了5個十六進位制數。顯然上面的轉換是可逆的,即給定一個十六進位制數,我們可以寫出其五十進位制的表示。這對寫序號產生器是有用的。下面我們繼續看它如何處理這5個數。用陣列a[5]表示這5個數,則下面的彙編程式碼可以翻譯為對應的C程式:
      for(k = 0; k < 4; k++)
      {
            a[k] ^= a[k+1];
      }

* Referenced by a CALL at Address:
|:004174A3 
|
:004174F3 8B442404                mov eax, dword ptr [esp+04]
:004174F7 56                      push esi
:004174F8 6A04                    push 00000004                  //迴圈4次
:004174FA 83C004                  add eax, 00000004
:004174FD 5A                      pop edx
:004174FE 8B7004                  mov esi, dword ptr [eax+04]    //取出a[k+1]
:00417501 8D4804                  lea ecx, dword ptr [eax+04]
:00417504 3130                    xor dword ptr [eax], esi      //與a[k]異或
:00417506 4A                      dec edx
:00417507 8BC1                    mov eax, ecx
:00417509 75F3                    jne 004174FE                  //迴圈
:0041750B 32C0                    xor al, al
:0041750D 5E                      pop esi
:0041750E C3                      ret

由於異或運算是可逆的,因此上面的變換也可逆。我們繼續看它如何處理異或過的這5個數。接下來將是一個大的異或變換,不過不用擔心,這個變換仍是可逆的。如下:

* Referenced by a CALL at Address:
|:004174B4 
|
:004174C4 56                      push esi
:004174C5 57                      push edi
:004174C6 33FF                    xor edi, edi                  //迴圈控制變數edi清0

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004174EC(C)
|
:004174C8 6A05                    push 00000005
:004174CA 8BC7                    mov eax, edi
:004174CC 33D2                    xor edx, edx
:004174CE 59                      pop ecx
:004174CF F7F1                    div ecx                      //迴圈控制變數edi對5求餘
:004174D1 8B44240C                mov eax, dword ptr [esp+0C]
:004174D5 6A00                    push 00000000
:004174D7 8D749004                lea esi, dword ptr [eax+4*edx+04]
:004174DB E870FFFFFF              call 00417450                //取常數數列的下一項
:004174E0 25FFFFFF0F              and eax, 0FFFFFFF            //遮蔽掉高4個bit
:004174E5 59                      pop ecx
:004174E6 3106                    xor dword ptr [esi], eax      //與a[edi % 5]異或
:004174E8 47                      inc edi                      //迴圈控制變數加1
:004174E9 83FF65                  cmp edi, 00000065           
:004174EC 72DA                    jb 004174C8                  //繼續迴圈
:004174EE 5F                      pop edi
:004174EF 32C0                    xor al, al
:004174F1 5E                      pop esi
:004174F2 C3                      ret


上面的彙編程式碼可翻譯為如下的偽碼:
            for(edi = 0; edi < 0x65; edi++)
            {
                  NextNumber = GetNumberFromSequence( );
                  NextNumber &= 0x0FFFFFFF;
                  a[edi % 5] ^= NextNumber;
            }
即用一個常數數列的前0x65項來分別與我們在上一步所得到的5個數進行異或。這個迴圈的異或過程可以等價於:
            a[0] ^= 常數1;
            a[1] ^= 常數2;
            a[2] ^= 常數3;
            a[3] ^= 常數4;
            a[4] ^= 常數5;
其中上述5個常數是可以求出來的,因為所用到的常數數列是確定的,跟進call 00417450就可以看清常數數列的計算過程。
用Sequence[n]表示該數列的第n項,則call 00417450的作用就是根據Sequence[n]計算出Sequence[n+1]來,相當於數列的遞推公式,如下所示。

:0041745C 8B0D50625400            mov ecx, dword ptr [00546250]  //取出Sequence[n]
:00417462 8BC1                    mov eax, ecx
:00417464 8D1409                  lea edx, dword ptr [ecx+ecx]
:00417467 24FE                    and al, FE
:00417469 33C2                    xor eax, edx
:0041746B 8BD1                    mov edx, ecx
:0041746D D1E0                    shl eax, 1
:0041746F 83E2FC                  and edx, FFFFFFFC
:00417472 33C2                    xor eax, edx
:00417474 8BD1                    mov edx, ecx
:00417476 C1E002                  shl eax, 02
:00417479 83E2F0                  and edx, FFFFFFF0
:0041747C 33C2                    xor eax, edx
:0041747E 8BD1                    mov edx, ecx
:00417480 C1E002                  shl eax, 02
:00417483 83E2C0                  and edx, FFFFFFC0
:00417486 33C2                    xor eax, edx
:00417488 8BD1                    mov edx, ecx
:0041748A C1E019                  shl eax, 19
:0041748D 81E200000080            and edx, 80000000
:00417493 33C2                    xor eax, edx
:00417495 D1E9                    shr ecx, 1
:00417497 0BC1                    or eax, ecx
:00417499 A350625400              mov dword ptr [00546250], eax  //儲存Sequence[n+1]
:0041749E C3                      ret

看一下DS:[00546250]中存放的初始值,就知道Sequence[0] = 0x01A26A75。根據異或運算的結合律,可以知道:
    常數1 = (Sequence[1] ^ Sequence[6]  ^ ... ^ Sequence[96] ^ Sequence[101]) & 0x0FFFFFFF
    常數2 = (Sequence[2] ^ Sequence[7]  ^ ... ^ Sequence[97])                & 0x0FFFFFFF
    常數3 = (Sequence[3] ^ Sequence[8]  ^ ... ^ Sequence[98])                & 0x0FFFFFFF
    常數4 = (Sequence[4] ^ Sequence[9]  ^ ... ^ Sequence[99])                & 0x0FFFFFFF
    常數5 = (Sequence[5] ^ Sequence[10] ^ ... ^ Sequence[100])                & 0x0FFFFFFF

經過了上面的迴圈異或後,我們再來看看它下面是怎麼處理的。運算了半天,總該判斷一下了吧。
在如下的程式碼中,它要判斷註冊碼的第2部分。

* Referenced by a CALL at Address:
|:004F4107 
|
:004F3FB4 55                      push ebp
:004F3FB5 8BEC                    mov ebp, esp
:004F3FB7 83EC18                  sub esp, 00000018
:004F3FBA 8B4508                  mov eax, dword ptr [ebp+08]
:004F3FBD 56                      push esi
:004F3FBE 8B750C                  mov esi, dword ptr [ebp+0C]
:004F3FC1 57                      push edi
:004F3FC2 BA000000F0              mov edx, F0000000            //掩碼,用來提取一個數的高4bit(即28~31 bit)
:004F3FC7 8B4E08                  mov ecx, dword ptr [esi+08]  //取出a[1]
:004F3FCA 89480C                  mov dword ptr [eax+0C], ecx
:004F3FCD 8B4E0C                  mov ecx, dword ptr [esi+0C]  //取出a[2]
:004F3FD0 894810                  mov dword ptr [eax+10], ecx
:004F3FD3 8B4E10                  mov ecx, dword ptr [esi+10]  //取出a[3]
:004F3FD6 894814                  mov dword ptr [eax+14], ecx
:004F3FD9 8B4E14                  mov ecx, dword ptr [esi+14]  //取出a[4]
:004F3FDC 894818                  mov dword ptr [eax+18], ecx
:004F3FDF 8B4E04                  mov ecx, dword ptr [esi+04]  //取出a[0]
:004F3FE2 C1E104                  shl ecx, 04                  //a[0]左移4位
:004F3FE5 8BF9                    mov edi, ecx
:004F3FE7 23FA                    and edi, edx                //與掩碼相與
:004F3FE9 09780C                  or dword ptr [eax+0C], edi  //把a[0]的4個bit分給a[1]
:004F3FEC C1E104                  shl ecx, 04                  //a[0]再左移4位
:004F3FEF 8BF9                    mov edi, ecx
:004F3FF1 23FA                    and edi, edx                //與掩碼相與
:004F3FF3 097810                  or dword ptr [eax+10], edi  //把a[0]的4個bit分給a[2]
:004F3FF6 C1E104                  shl ecx, 04                  //a[0]再左移4位
:004F3FF9 8BF9                    mov edi, ecx
:004F3FFB 23FA                    and edi, edx                //與掩碼相與
:004F3FFD 097814                  or dword ptr [eax+14], edi  //把a[0]的4個bit分給a[3]
:004F4000 C1E104                  shl ecx, 04                //a[0]再左移4位
:004F4003 8BF9                    mov edi, ecx
:004F4005 23FA                    and edi, edx                //與掩碼相與
:004F4007 BA000F0000              mov edx, 00000F00          //另一個掩碼,用來提取一個數的第8~11 bit
:004F400C 097818                  or dword ptr [eax+18], edi  //把a[0]的4個bit分給a[4]
:004F400F C1E910                  shr ecx, 10                //a[0]再右移16位
:004F4012 8BF9                    mov edi, ecx
:004F4014 23FA                    and edi, edx                //與掩碼相與,
:004F4016 3BFA                    cmp edi, edx                //與掩碼比較!
:004F4018 752E                    jne 004F4048                //jump if bad guy
:004F401A 8BD1                    mov edx, ecx
:004F401C 83E101                  and ecx, 00000001          //與另一掩碼相與,提取最低bit(即第0 bit)
:004F401F D1EA                    shr edx, 1                  //右移一位,等於去掉了原來的第0 bit
:004F4021 6A06                    push 00000006
:004F4023 83E27F                  and edx, 0000007F          //與掩碼7F相與,提取出最低的7個bit
:004F4026 8908                    mov dword ptr [eax], ecx    //儲存第0個bit,以後再判斷
:004F4028 59                      pop ecx
:004F4029 8D7DE8                  lea edi, dword ptr [ebp-18]
:004F402C 895008                  mov dword ptr [eax+08], edx //儲存7個bit,以後再判斷
:004F402F F3                      repz
:004F4030 A5                      movsd

上面有很多位運算,實際上是在對a[0]進行五馬分屍,要把a[0]的28個bit共拆分為7個部分(a[0]的最高4個bit均為0,故不考慮在內)。詳細說明如下:
假定十六進位制表示為:
a[0] = 0MNYZUVW,a[1] = 0xxxxxxx,a[2] = 0yyyyyyy,a[3] = 0zzzzzzz,a[4] = 0kkkkkkk。
則將a[0]拆分為7部分後,變為:
a[1] = Mxxxxxxx,a[2] = Nyyyyyyy,a[3] = Yzzzzzzz,a[4] = Zkkkkkkk。
即a[0]給了a[1]、a[2]、a[3]、a[4]各4個bit(即M、N、Y、Z)作為他們的最高4個bit。另外,a[0]的最低8個bit(即VW)被拆分為兩部分,一部分只有一個bit,另外一部分有7個bit,並分別儲存起來,後面它要對這兩部分進行判斷,此為後話。
a[0]的另外4個bit(即U)在上面的CS:004F4016處進行了判斷,如果滿足a[0] & 00000F00 = 00000F00,則OK,由此可知 a[0]的第8~11 bit均應為1(即U = F)。

經過這一番折騰,它把a[0]瓜分了,顯然是想分散我們的注意力,只要我們漏過a[0]的7部分中的任一部分,有效的註冊碼就找不到。

還記不記得註冊碼的第1部分只有一個字元?接下來它就要對註冊碼的第1部分進行查表變換。

:004F4031 0FB655E8                movzx edx, byte ptr [ebp-18]      //取出註冊碼的第1部分
:004F4035 33C9                    xor ecx, ecx                      //下標清0

:004F4037 0FBEB180D35300          movsx esi, byte ptr [ecx+0053D380]//查表,從表"emuw"中取出一個字元
:004F403E 3BF2                    cmp esi, edx                      //和註冊碼的第一部分相等?
:004F4040 740A                    je 004F404C                      //相等則將下標記下來
:004F4042 41                      inc ecx                          //下標加1
:004F4043 83F903                  cmp ecx, 00000003                //該表中僅4個字元
:004F4046 7EEF                    jle 004F4037                    //迴圈,遍歷該表

:004F4048 0CFF                    or al, FF                        //bad guy
:004F404A EB05                    jmp 004F4051
:004F404C 894804                  mov dword ptr [eax+04], ecx      //儲存此時的下標
:004F404F 32C0                    xor al, al                        //good guy
:004F4051 5F                      pop edi
:004F4052 5E                      pop esi
:004F4053 C9                      leave
:004F4054 C3                      ret

由上可見,如果你輸入的假註冊碼的第一個字元如果不是e或m或u或w,則bye-bye了。否則它就記下該下標,此後要對這個下標進行判斷,耐心等等,後面還會提到。

接下來,它要根據a[1]、a[2]、a[3]計算出一個數來,然後和a[4]進行比較。至此我們知道a[4]是根據a[1]、a[2]、a[3]計算出來的,這對寫序號產生器很重要。計算沒什麼特別的地方,照搬到我們的序號產生器中,稍加改動即可。計算過程如下:

* Referenced by a CALL at Address:
|:004F411A 
|
:004F4055 55                      push ebp
:004F4056 8BEC                    mov ebp, esp
:004F4058 83EC0C                  sub esp, 0000000C
:004F405B 8B4D08                  mov ecx, dword ptr [ebp+08]
:004F405E 53                      push ebx
:004F405F 56                      push esi
:004F4060 33F6                    xor esi, esi
:004F4062 8B4104                  mov eax, dword ptr [ecx+04]
:004F4065 57                      push edi
:004F4066 40                      inc eax
:004F4067 C745FC79786573          mov [ebp-04], 73657879
:004F406E C745F865626162          mov [ebp-08], 62616265
:004F4075 8945F4                  mov dword ptr [ebp-0C], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40DF(C)
|
:004F4078 8BC6                    mov eax, esi
:004F407A 6A03                    push 00000003
:004F407C 99                      cdq
:004F407D 5F                      pop edi
:004F407E F7FF                    idiv edi
:004F4080 46                      inc esi
:004F4081 6A03                    push 00000003
:004F4083 8BC6                    mov eax, esi
:004F4085 5B                      pop ebx
:004F4086 8B7C910C                mov edi, dword ptr [ecx+4*edx+0C]
:004F408A 99                      cdq
:004F408B F7FB                    idiv ebx
:004F408D 8B45F4                  mov eax, dword ptr [ebp-0C]
:004F4090 85C0                    test eax, eax
:004F4092 8B54910C                mov edx, dword ptr [ecx+4*edx+0C]
:004F4096 7E37                    jle 004F40CF
:004F4098 8B5DFC                  mov ebx, dword ptr [ebp-04]
:004F409B 894508                  mov dword ptr [ebp+08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40AA(C)
|
:004F409E 8BC3                    mov eax, ebx
:004F40A0 0FAFC3                  imul eax, ebx
:004F40A3 33C7                    xor eax, edi
:004F40A5 FF4D08                  dec [ebp+08]
:004F40A8 8BD8                    mov ebx, eax
:004F40AA 75F2                    jne 004F409E
:004F40AC 895DFC                  mov dword ptr [ebp-04], ebx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40D2(U)
|
:004F40AF 8B4108                  mov eax, dword ptr [ecx+08]
:004F40B2 83C003                  add eax, 00000003
:004F40B5 85C0                    test eax, eax
:004F40B7 7E1B                    jle 004F40D4
:004F40B9 894508                  mov dword ptr [ebp+08], eax
:004F40BC 8B45F8                  mov eax, dword ptr [ebp-08]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40CB(C)
|
:004F40BF 8BF8                    mov edi, eax
:004F40C1 0FAFF8                  imul edi, eax
:004F40C4 33FA                    xor edi, edx
:004F40C6 FF4D08                  dec [ebp+08]
:004F40C9 8BC7                    mov eax, edi
:004F40CB 75F2                    jne 004F40BF
:004F40CD EB0A                    jmp 004F40D9

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F4096(C)
|
:004F40CF 8B5DFC                  mov ebx, dword ptr [ebp-04]
:004F40D2 EBDB                    jmp 004F40AF

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40B7(C)
|
:004F40D4 8B45F8                  mov eax, dword ptr [ebp-08]
:004F40D7 EB03                    jmp 004F40DC

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40CD(U)
|
:004F40D9 8945F8                  mov dword ptr [ebp-08], eax

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F40D7(U)
|
:004F40DC 83FE1F                  cmp esi, 0000001F
:004F40DF 7C97                    jl 004F4078
:004F40E1 81E3F0F0F000            and ebx, 00F0F0F0            //最高4 bit被幹掉了
:004F40E7 250F0F0F0F              and eax, 0F0F0F0F            //最高4 bit被幹掉了
:004F40EC 0BD8                    or ebx, eax
:004F40EE 8B450C                  mov eax, dword ptr [ebp+0C]
:004F40F1 5F                      pop edi
:004F40F2 5E                      pop esi
:004F40F3 8918                    mov dword ptr [eax], ebx      //儲存計算的結果
:004F40F5 32C0                    xor al, al
:004F40F7 5B                      pop ebx
:004F40F8 C9                      leave
:004F40F9 C3                      ret

注意上面的註釋!在CS:004F40E1和CS:004F40E7這兩處,最高4個bit都被變為0了,這意味著計算出來的這個數的最高4 bit全部為0。但是它又要拿這個計算出來的數和a[4]進行比較,那說明此時a[4]的最高4 bit也必須為0。
還記不記得此時a[4]的最高4 bit是從a[0]中分來的(十六進位制形式為Zkkkkkkk)!至此我們又搞定a[0]的7部分
中的一部分。

下面是和a[4]進行比較:

:004F4112 8D4508                  lea eax, dword ptr [ebp+08]
:004F4115 50                      push eax
:004F4116 8D45E4                  lea eax, dword ptr [ebp-1C]
:004F4119 50                      push eax
:004F411A E836FFFFFF              call 004F4055              //根據a[1]、a[2]、a[3]計算
:004F411F 59                      pop ecx
:004F4120 84C0                    test al, al
:004F4122 59                      pop ecx
:004F4123 7528                    jne 004F414D
:004F4125 8B45FC                  mov eax, dword ptr [ebp-04]
:004F4128 3B4508                  cmp eax, dword ptr [ebp+08] //和a[4]比較
:004F412B 7520                    jne 004F414D

緊跟著它要判斷a[1]、a[2]、a[3]了,如下:


* Referenced by a CALL at Address:
|:004F4131 
|
:004F4155 8B442404                mov eax, dword ptr [esp+04]
:004F4159 56                      push esi
:004F415A BE74D35300              mov esi, 0053D374
:004F415F 8D480C                  lea ecx, dword ptr [eax+0C]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004F4178(C)
|
:004F4162 8B01                    mov eax, dword ptr [ecx]    //取出a[k],k=1,2,3
:004F4164 33D2                    xor edx, edx
:004F4166 F736                    div dword ptr [esi]        //分別除以常數
:004F4168 85D2                    test edx, edx              //餘數不為0則bad guy
:004F416A 7512                    jne 004F417E
:004F416C 83C604                  add esi, 00000004
:004F416F 83C104                  add ecx, 00000004          //餘數為0則繼續比較
:004F4172 81FE80D35300            cmp esi, 0053D380
:004F4178 7CE8                    jl 004F4162                //迴圈
:004F417A 32C0                    xor al, al                  //a[1]、a[2]、a[3]均透過檢查則OK
:004F417C 5E                      pop esi
:004F417D C3                      ret

由上可知,此時的a[1]、a[2]、a[3]應為常數的整數倍,執行上面的那條除法指令時敲dd esi,可以看見常數分別為00114BCF, 0x0013D39F, 0x003687A9。至於究竟是多少倍,我們寫序號產生器的時候可以用隨機數(我用GetTickCount模擬隨機數),這也說明這個軟體有很多個註冊碼。注意到此時的a[1]、a[2]、a[3]的最高4 bit都是從a[0]中分來的一杯羹,於是我們又搞定了a[0]的7部分之中的3部分。

至此,我們已經可以寫程式生成a[1]、a[2]、a[3],然後再生成a[4],現在只剩下a[0]的7部分中的兩部分(即最低8bit位,分成7個bit和1個bit共兩部分)和註冊碼的第一部分(即查表後的下標)了。

下面就是對這3個東西的判斷:

:004F412D 8D45E4                  lea eax, dword ptr [ebp-1C]
:004F4130 50                      push eax
:004F4131 E81F000000              call 004F4155                    //這裡頭檢查a[1]、a[2]、a[3]
:004F4136 84C0                    test al, al
:004F4138 59                      pop ecx
:004F4139 7512                    jne 004F414D
:004F413B 837DE400                cmp dword ptr [ebp-1C], 00000000  //a[0]的最低bit,即第0bit
:004F413F 750C                    jne 004F414D
:004F4141 837DE803                cmp dword ptr [ebp-18], 00000003  //註冊碼的第1部分所對應的下標
:004F4145 7506                    jne 004F414D
:004F4147 837DEC02                cmp dword ptr [ebp-14], 00000002  //a[0]的第1~7bit
:004F414B 7404                    je 004F4151

根據上面的比較可知,註冊碼的第一部分在表格"emuw"中的下標應為3,即註冊碼的第一個字元固定為w;
a[0]的最低8個bit用十六進位制表示為0x04,結合前面我們得到的關於a[0]的資訊,就可以把a[0]表示出來了。

至此可以寫出序號產生器。(累死了,呵呵)

為保持完整性,把序號產生器再貼一下:

//keygen for Opera 4.0 Final
//compiled with Visual C++ 5

#include <stdio.h>
#include <windows.h>

unsigned long  Sequence;
const unsigned long  factor[3] = { 0x00114BCF, 0x0013D39F, 0x003687A9 };
const char table[50] = "abcdefhijkmnprstuvwxyzABCDEFHJKLMNPQRSTUVWXY345678";
int k, m;
unsigned long  a[5];
long EBP_04, EBP_08, EBP_0C, EBP_p08;

void main(void)
{
 
  printf("Key generator for Opera 4.0 final.\n");
  printf("http://www.opera.com.\n");
  printf("coded by dr0, 2000.6.30.\n");
 
  for (k = 1; k <= 3; k++)
  {
      a[k] = factor[k-1] * (GetTickCount( ) % (0xFFFFFFFFL / factor[k-1] + 1));     
  }

  _asm
  {
              push      eax
              push      ebx
              push      ecx
              push      edx
              push      esi
              push      edi

              mov      ecx, offset a

              XOR      ESI,ESI
              MOV      EAX,0x03
              INC      EAX
              MOV      DWORD PTR [EBP_04],0x73657879L
              MOV      DWORD PTR [EBP_08],0x62616265L
              MOV      [EBP_0C],EAX
    _004F4078: MOV      EAX,ESI
              PUSH      0x03
              CDQ
              POP      EDI
              IDIV      EDI
              INC      ESI
              PUSH      0x03
              MOV      EAX,ESI
              POP      EBX
              MOV      EDI,[EDX*4+ECX+4]
              CDQ
              IDIV      EBX
              MOV      EAX,[EBP_0C]
              TEST      EAX,EAX
              MOV      EDX,[EDX*4+ECX+4]
              JLE      _004F40CF
              MOV      EBX,[EBP_04]
              MOV      [EBP_p08],EAX
  _004F409E:  MOV      EAX,EBX
              IMUL      EAX,EBX
              XOR      EAX,EDI
              DEC      DWORD PTR [EBP_p08]
              MOV      EBX,EAX
              JNZ      _004F409E
              MOV      [EBP_04],EBX
  _004F40AF:  MOV      EAX,0x02
              ADD      EAX,0x03
              TEST      EAX,EAX
              JLE      _004F40D4
              MOV      [EBP_p08],EAX
              MOV      EAX,[EBP_08]
  _004F40BF:  MOV      EDI,EAX
              IMUL      EDI,EAX
              XOR      EDI,EDX
              DEC      DWORD PTR [EBP_p08]
              MOV      EAX,EDI
              JNZ      _004F40BF
              JMP      _004F40D9
  _004F40CF:  MOV      EBX,[EBP_04]
              JMP      _004F40AF
  _004F40D4:  MOV      EAX,[EBP_08]
              JMP      _004F40DC
  _004F40D9:  MOV      [EBP_08],EAX
  _004F40DC:  CMP      ESI,0x1F
              JL        _004F4078
              AND      EBX,0x00F0F0F0L
              AND      EAX,0x0F0F0F0FL
              OR        EBX,EAX

              mov      [ecx+4*4], ebx

              pop      edi
              pop      esi
              pop      edx
              pop      ecx
              pop      ebx
              pop      eax
  }

  a[0] = 0;
  for (k = 1; k <= 3; k++)
  {
      a[0] |= a[k] >> 28;
      a[0] <<= 4;
  }
  a[0] <<= 12;
  a[0] |= 0x00000F04L;

  for (k = 0; k < 4; k++)
  {
      a[k] &= 0x0FFFFFFFL;
  }

  Sequence = 0x01A26A75;
  for(k = 0; k < 0x65; k++)
  {
    _asm
    {
            push eax
            push ecx
            push edx
           
            MOV      ECX, [Sequence]
            MOV      EAX, ECX
            LEA      EDX, [ECX+ECX]
            AND      AL,  0xFE
            XOR      EAX, EDX
            MOV      EDX, ECX
            SHL      EAX, 1
            AND      EDX, -0x04
            XOR      EAX, EDX
            MOV      EDX, ECX
            SHL      EAX, 0x02
            AND      EDX, -0x10
            XOR      EAX, EDX
            MOV      EDX, ECX
            SHL      EAX, 0x02
            AND      EDX, -0x40
            XOR      EAX, EDX
            MOV      EDX, ECX
            SHL      EAX, 0x19
            AND      EDX, 0x80000000
            XOR      EAX, EDX
            SHR      ECX, 1
            OR        EAX, ECX
            MOV      [Sequence], EAX

            pop edx
            pop ecx
            pop eax
      }

      a[k % 5] ^= (Sequence & 0x0FFFFFFFL);
  }

  for (k = 3; k >= 0; k--)
  {
      a[k] ^= a[k+1];
  }

  printf("Your code is: ");
  putchar('w');
  for(k = 0; k < 5; k++)
  {
      putchar('-');
      for(m = 0; m < 5; m++)
      {
            putchar(table[a[k] % 50L]);
            a[k] /= 50L;
      }
  }

  printf("\n");
}

 


相關文章