API Spy for NT v1.4 (16千字)

看雪資料發表於2000-10-25

APIS32NT v1.4
http://www.biosys.net/apis32

這個軟體加過殼,殼中似有檢測偵錯程式斷點的反跟蹤程式碼。真正入口如下:
001B:004185BD  MOV      ESP,[ESP+14]
001B:004185C1  POP      ESI
001B:004185C2  MOV      EDI,ESI
001B:004185C4  ADD      ESI,000015D7
001B:004185CA  PUSH      05
001B:004185CC  POP      ECX
001B:004185CD  REPZ      MOVSB
001B:004185CF  POPAD
001B:004185D0  POPF
001B:004185D2  JMP      004045A0  //入口地址
這裡不關心脫殼,主要是講如何製作它的序號產生器。

先用bpx GetDlgItemTextA設斷,會中斷兩次,分別讀入UserName和UserKey。然後用BPM或BPR監視你輸入的假註冊碼,發現它將註冊碼寫入登錄檔之後未再對讀入的註冊碼作進一步的判斷就彈出了“錯誤的註冊碼”對話方塊。這說明它肯定判斷的是從登錄檔中讀出來的註冊碼,即先寫入登錄檔再讀出來進行判斷,用RegMon也可以證實這一點。於是用bpx RegQueryValueExA設斷,中斷後按兩下F12,就看見了判斷的程式碼。首先是判斷長度:

:0041EEEC 83EC2C                  sub esp, 0000002C
:0041EEEF 53                      push ebx
:0041EEF0 55                      push ebp
:0041EEF1 56                      push esi
:0041EEF2 57                      push edi
:0041EEF3 6A50                    push 00000050
:0041EEF5 68C0AE4000              push 0040AEC0

* Possible StringData Ref from Data Obj ->"UserKey"
                                  |
:0041EEFA 6898864000              push 00408698
:0041EEFF E868030000              call 0041F26C    //RegQueryValueExA( )
:0041EF04 83C40C                  add esp, 0000000C
:0041EF07 83F811                  cmp eax, 00000011 //strlen(UserKey) >= 0x11?
:0041EF0A 7D0A                    jge 0041EF16
:0041EF0C 33C0                    xor eax, eax      //bad guy
:0041EF0E 5F                      pop edi
:0041EF0F 5E                      pop esi
:0041EF10 5D                      pop ebp
:0041EF11 5B                      pop ebx
:0041EF12 83C42C                  add esp, 0000002C
:0041EF15 C3                      ret

:0041EF16 6A2F                    push 0000002F
:0041EF18 68C0BE4000              push 0040BEC0

* Possible StringData Ref from Data Obj ->"UserName"
                                  |
:0041EF1D 6888864000              push 00408688
:0041EF22 E845030000              call 0041F26C      //RegQueryValueExA( )
:0041EF27 83C40C                  add esp, 0000000C
:0041EF2A 83F805                  cmp eax, 00000005  //strlen(UserName) >= 5 ?
:0041EF2D 7D0A                    jge 0041EF39
:0041EF2F 33C0                    xor eax, eax      //bad guy
:0041EF31 5F                      pop edi
:0041EF32 5E                      pop esi
:0041EF33 5D                      pop ebp
:0041EF34 5B                      pop ebx
:0041EF35 83C42C                  add esp, 0000002C
:0041EF38 C3                      ret

:0041EF39 6A1E                    push 0000001E
:0041EF3B 8D442418                lea eax, dword ptr [esp+18]
:0041EF3F 68C0BE4000              push 0040BEC0
:0041EF44 50                      push eax
:0041EF45 E812070000              call 0041F65C
:0041EF4A 83C40C                  add esp, 0000000C
:0041EF4D 8D4C2414                lea ecx, dword ptr [esp+14]
:0041EF51 51                      push ecx
:0041EF52 E845FFFFFF              call 0041EE9C              //strupr(UserName)
:0041EF57 8D7C2418                lea edi, dword ptr [esp+18]
:0041EF5B 83C9FF                  or ecx, FFFFFFFF
:0041EF5E 33C0                    xor eax, eax              //bad guy
:0041EF60 83C404                  add esp, 00000004
:0041EF63 F2                      repnz
:0041EF64 AE                      scasb
:0041EF65 F7D1                    not ecx
:0041EF67 49                      dec ecx
:0041EF68 83F905                  cmp ecx, 00000005        //strlen(UserName) >= 5?
:0041EF6B 7308                    jnb 0041EF75
:0041EF6D 5F                      pop edi
:0041EF6E 5E                      pop esi
:0041EF6F 5D                      pop ebp
:0041EF70 5B                      pop ebx
:0041EF71 83C42C                  add esp, 0000002C
:0041EF74 C3                      ret

接下來它要判斷註冊碼的第8位,即UserKey[8]。如下,顯然要滿足條件
      (UserKey[8] ^ 0x20) + 0xF3 = 0  (溢位的進位位不考慮)
從而可知註冊碼的第8個字元是橫槓字元"-"。

:0041EF75 8A1DC8AE4000            mov bl, byte ptr [0040AEC8] //取出UserKey[8]
...............................................
:0041EF85 80F320                  xor bl, 20
...............................................
:0041EFCB 80C3F3                  add bl, F3
...............................................
:0041EFD0 740A                    je 0041EFDC
:0041EFD2 33C0                    xor eax, eax              //bad guy
:0041EFD4 5F                      pop edi
:0041EFD5 5E                      pop esi
:0041EFD6 5D                      pop ebp
:0041EFD7 5B                      pop ebx
:0041EFD8 83C42C                  add esp, 0000002C
:0041EFDB C3                      ret

注意到此時bl暫存器的值應為0,這bl要作為下面這個迴圈的迴圈控制變數。該迴圈先將註冊碼的另外16個字元轉換成8個位元組,比如註冊碼是"11223344-55667788",則得到的8個位元組是
      0x11, 0x22,0x33,0x44,0x55,0x66,0x77,0x88
用陣列a[ ]表示這8個位元組,下面的迴圈對應的C語句就是:
char a[8];
for(k = 0; k < 8; k++)
{
    a[k] = UserKey[k] ^ (k + 0x50);
}
由於異或運算可逆,所以已知a[k]是可以求出UserKey[k]的。

:0041EFDC BEC1AE4000              mov esi, 0040AEC1
:0041EFE1 BFD4AE4000              mov edi, 0040AED4
:0041EFE6 57                      push edi
:0041EFE7 E8E0010000              call 0041F1CC    //將註冊碼中的兩個字元轉換成一個位元組
:0041EFEC 8ACB                    mov cl, bl        //(cl) = k
:0041EFEE 83C404                  add esp, 00000004
:0041EFF1 80C150                  add cl, 50        //(cl) = k + 0x50
:0041EFF4 83C702                  add edi, 00000002 //指向後續的兩個字元
:0041EFF7 32C1                    xor al, cl        // a[k] ^= k + 0x50
:0041EFF9 FEC3                    inc bl            // k++
:0041EFFB 8846FF                  mov byte ptr [esi-01], al
:0041EFFE C60600                  mov byte ptr [esi], 00
:0041F001 46                      inc esi
:0041F002 80FB08                  cmp bl, 08
:0041F005 72DF                    jb 0041EFE6      //繼續迴圈

之後對a[ ]進行不可逆變換,得到新的8個位元組,如下:

* Referenced by a CALL at Address:
|:0041F011 
|
:0041F1FC 53                      push ebx
:0041F1FD 55                      push ebp
:0041F1FE 8B6C2410                mov ebp, dword ptr [esp+10]
:0041F202 56                      push esi
:0041F203 57                      push edi
:0041F204 8B7C2414                mov edi, dword ptr [esp+14]
:0041F208 33C9                    xor ecx, ecx              //外迴圈控制變數初值
:0041F20A 2BFD                    sub edi, ebp
:0041F20C 897C2418                mov dword ptr [esp+18], edi
:0041F210 EB04                    jmp 0041F216

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041F260(C)
|
:0041F212 8B7C2418                mov edi, dword ptr [esp+18]

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041F210(U)
|
:0041F216 8D3429                  lea esi, dword ptr [ecx+ebp]
:0041F219 33D2                    xor edx, edx
:0041F21B B801000000              mov eax, 00000001        //累乘器的初值
:0041F220 C744241407000000        mov [esp+14], 00000007    //內迴圈控制變數的初值
:0041F228 8A1437                  mov dl, byte ptr [edi+esi]//取出a[k]
:0041F22B 8BFA                    mov edi, edx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041F24C(C)
|
:0041F22D 8BD7                    mov edx, edi
:0041F22F 0FAFC2                  imul eax, edx          //累乘器乘以a[k]
:0041F232 3D99880000              cmp eax, 00008899
:0041F237 7E0A                    jle 0041F243
:0041F239 99                      cdq
:0041F23A BB99880000              mov ebx, 00008899
:0041F23F F7FB                    idiv ebx                //對0x8899求餘
:0041F241 8BC2                    mov eax, edx

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0041F237(C)
|
:0041F243 8B542414                mov edx, dword ptr [esp+14]
:0041F247 4A                      dec edx                //內迴圈控制變數減1
:0041F248 89542414                mov dword ptr [esp+14], edx
:0041F24C 75DF                    jne 0041F22D            //內迴圈,求乘冪
:0041F24E 99                      cdq
:0041F24F BFBB000000              mov edi, 000000BB
:0041F254 F7FF                    idiv edi                //對0xBB求餘
:0041F256 41                      inc ecx
:0041F257 83F908                  cmp ecx, 00000008      //8個位元組均處理完?
:0041F25A 8816                    mov byte ptr [esi], dl  //儲存餘數
:0041F25C C6042900                mov byte ptr [ecx+ebp], 00
:0041F260 7CB0                    jl 0041F212            //外迴圈,共處理8個位元組
:0041F262 5F                      pop edi
:0041F263 5E                      pop esi
:0041F264 5D                      pop ebp
:0041F265 5B                      pop ebx
:0041F266 C3                      ret

上述迴圈可等價表示如下:
for(k = 0; k < 8; k++)
{
    temp = 1L;
    for(j = 7; j > 0; j--)
    {
        temp *= a[k];
        if (temp > 0x00008899L)
        {
            temp %= 0x00008899L;
        }
    }
   
    a[k] = temp % 0x000000BBL;
}
對於這個不可逆的變換,我們可以根據變換之後的8位元組的值來用窮舉的方法猜出變換之前的8個位元組的值,最壞的情況只需要猜(256 * 8)次即可,當然也可能無解。最佳化一下的話最壞只要猜256次。

緊跟著它要從UserName得到一個新的長為8的串UserString。下面的處理等價於C語句:

char index = 0;
for(k = 0; k < 8; k++)
{
    UserString[k] = UserName[index++];
    index %= NameLen;
}
即如果UserName的長度大於等於8,則新串UserString就是UserName的前8個字元,否則把UserName串重複多次,然後取整個串的前8個字元即可。

:0041F016 8D7C241C                lea edi, dword ptr [esp+1C]
:0041F01A 83C9FF                  or ecx, FFFFFFFF
:0041F01D 33C0                    xor eax, eax
:0041F01F 83C408                  add esp, 00000008
:0041F022 F2                      repnz
:0041F023 AE                      scasb
:0041F024 F7D1                    not ecx
:0041F026 2BF9                    sub edi, ecx
:0041F028 33ED                    xor ebp, ebp
:0041F02A 8BD1                    mov edx, ecx
:0041F02C 8BF7                    mov esi, edi
:0041F02E BFDEAE4000              mov edi, 0040AEDE
:0041F033 C1E902                  shr ecx, 02
:0041F036 F3                      repz
:0041F037 A5                      movsd
:0041F038 8BCA                    mov ecx, edx
:0041F03A 83E103                  and ecx, 00000003
:0041F03D F3                      repz
:0041F03E A4                      movsb
:0041F03F 8D7C2414                lea edi, dword ptr [esp+14]
:0041F043 83C9FF                  or ecx, FFFFFFFF
:0041F046 F2                      repnz
:0041F047 AE                      scasb
:0041F048 F7D1                    not ecx
:0041F04A 49                      dec ecx
:0041F04B 80F908                  cmp cl, 08                  //判UserName的長度
:0041F04E 884C2410                mov byte ptr [esp+10], cl
:0041F052 732F                    jnb 0041F083                //大於等於8則取前8個字元
:0041F054 8B542410                mov edx, dword ptr [esp+10] //否則拼也要拼8個字元出來
:0041F058 8D7C2414                lea edi, dword ptr [esp+14]
:0041F05C 81E2FF000000            and edx, 000000FF
:0041F062 83C9FF                  or ecx, FFFFFFFF
:0041F065 81C2DEAE4000            add edx, 0040AEDE
:0041F06B F2                      repnz
:0041F06C AE                      scasb
:0041F06D F7D1                    not ecx
:0041F06F 2BF9                    sub edi, ecx
:0041F071 8BC1                    mov eax, ecx
:0041F073 8BF7                    mov esi, edi
:0041F075 8BFA                    mov edi, edx
:0041F077 C1E902                  shr ecx, 02
:0041F07A F3                      repz
:0041F07B A5                      movsd
:0041F07C 8BC8                    mov ecx, eax
:0041F07E 83E103                  and ecx, 00000003
:0041F081 F3                      repz
:0041F082 A4                      movsb

最後就是利用UserString[ ]和UserKey[ ]進行判斷。這也是一個迴圈。

:0041F028 33ED                    xor ebp, ebp
......................................................
:0041F083 C605E6AE400000          mov byte ptr [0040AEE6], 00
:0041F08A B9D4AE4000              mov ecx, 0040AED4
:0041F08F BE08000000              mov esi, 00000008      //迴圈控制變數k = 8
:0041F094 8A01                    mov al, byte ptr [ecx//取出a[k]
:0041F096 3C20                    cmp al, 20              //和0x20相比,分兩種情況處理
:0041F098 730E                    jnb 0041F0A8
:0041F09A 33D2                    xor edx, edx
:0041F09C 25FF000000              and eax, 000000FF
:0041F0A1 8A510A                  mov dl, byte ptr [ecx+0A] //取出UserString[k]
:0041F0A4 0BD0                    or edx, eax              //與a[k]相或
:0041F0A6 EB0C                    jmp 0041F0B4
:0041F0A8 33D2                    xor edx, edx
:0041F0AA 25FF000000              and eax, 000000FF
:0041F0AF 8A510A                  mov dl, byte ptr [ecx+0A] //取出UserString[k]
:0041F0B2 33D0                    xor edx, eax              //與a[k]異或
:0041F0B4 03EA                    add ebp, edx              //累加到ebp中
:0041F0B6 41                      inc ecx                  //下一個位元組
:0041F0B7 4E                      dec esi                  //k--;
:0041F0B8 75DA                    jne 0041F094              //繼續迴圈

:0041F0BA 33C0                    xor eax, eax
:0041F0BC 5F                      pop edi
:0041F0BD 85ED                    test ebp, ebp            //累加和為0嗎?
:0041F0BF 5E                      pop esi
:0041F0C0 5D                      pop ebp
:0041F0C1 0F94C0                  sete al                  //為0則OK,否則bad guy
:0041F0C4 5B                      pop ebx
:0041F0C5 83C42C                  add esp, 0000002C
:0041F0C8 C3                      ret

上面這個迴圈可等價為:
long ebp = 0
for(k = 0; k < 8; k++)
{
    if (a[k] < 0x20)
    {
        ebp += UserString[k] | a[k];
    }
    else
    {
        ebp += UserString[k] ^ a[k];
    }
}

註冊碼正確與否的標準為:

if (ebp)
{
    printf("Wrong key");
}
else
{
    printf("good guy");
}

最後這個迴圈初看之下也比較難求逆,因為其中的或運算是不可逆的。但實際上我們只要讓(a[k] < 0x20)這個條件永遠不會滿足即可避開這個或運算,此時上面這個迴圈簡化為可逆的異或變換:
for(k = 0; k < 8; k++)
{
    ebp += UserString[k] ^ a[k];
}

這樣要想讓累加和ebp為0,只要滿足a[k] = UserString[k](k = 0, ..., 7)即可,由於UserString[k]是由可顯示的使用者名稱得來的,所以肯定滿足(a[k] >= 0x20)。至此得到序號產生器如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main(void)
{
    char        UserName[128];
    char        UserString[8];
    char        a[8];
    int          index, len;
    char        k, j;
    signed long  temp, guess;
    char        SuccessCounter;
   
    do
    {
        printf("Input your name(at least 5 chars):");
        gets(UserName);
        len = strlen(UserName);
    } while(len < 5);

    strupr(UserName);

    index = 0;
    for(k = 0; k < 8; k++)
    {
        a[k]  = UserString[k] = UserName[index++];
        index %= len;
    }

    //下面用窮舉求出變換前的8個位元組
    SuccessCounter = 0;
    for(k = 0; k < 8; k++)
    {       
        for(guess = 0; guess <= 255; guess++)
        {
            temp = 1L;
            for(j = 7; j > 0; j--)
            {
                temp *= guess;
                if (temp > 0x00008899L)
                {
                    temp %= 0x00008899L;
                }
            }
   
            if ((temp % 0x000000BBL) == a[k])
            {
                SuccessCounter++;
                a[k] = guess;
                break;
            }
        }
    }

    //判斷8個位元組是否全部窮舉成功
    if (SuccessCounter != 8)
    {
        printf("Guess failed.\n");
        exit(-1);
    }

    for(k = 0; k < 8; k++)
    {
        a[k] ^= (k + 0x50);
    }

    printf("Your registration key is: ");
    for(k = 0; k < 4; k++)
    {

相關文章