• 标 题:API Spy for NT v1.4 (16千字)
  • 作 者:1212
  • 时 间:2000-10-25 20:13:05
  • 链 接:http://bbs.pediy.com

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++)
    {

  • 标 题:续 (1千字)
  • 作 者:1212
  • 时 间:2000-10-25 20:14:19

#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++)
    {
        printf("%02X", a[k] & 0xFF);
    }
    printf("-");
    for(k = 4; k < 8; k++)
    {
        printf("%02X", a[k] & 0xFF);
    }
    printf("\n");
}

blowfish