首页 > 代码库 > “金山杯2007逆向分析挑战赛”第一阶段第一题分析

“金山杯2007逆向分析挑战赛”第一阶段第一题分析

  题目来自于如下网址:

  http://www.pediy.com/kssd/

  第13篇 论坛活动 \ 金山杯2007逆向分析挑战赛 \ 第一阶段 \ 第一题 \ 题目 \ [第一阶段 第一题];

 

  现将此题目概述粘贴如下:
 

  CrackMe.exe 是一个简单的注册程序,见附件,请写一个注册机:

要求:


  1. 注册机是KeyGen,不是内存注册机或文件Patch
  2. 注册机可以使用ASM,VC,BC,VB,Delphi等语言书写,其他谢绝使用。
  3. 注册机必须可以运行在Windows系统上。

  ......

 

  昨天我偶然浏览到这里,看到这个题目,于是看了下,题目早就已经是过去时了,但今天依然可以看看,大概用了半天时间求解此题目(本来以为挺简单,但实际上还是少有点难度的)。把附件下载下来可以看到,CrackMe.exe 是一个仅有 1.85 KB 的 Windows 对话框程序。界面也非常简单,只有两个文本框用于获取用户名,注册码,和一个注册按钮,当点击注册按钮时,如果注册码正确,会弹出 MessageBox 显示 "OK!!",否则会显示 "Fail!" 。

 

  

 

  很显然,如果我们直接修改这个 exe,跳过其注册码检测或者修改里面的跳转逻辑,修改调用 DecryptText 的参数,修改栈上的密文数据等等,有 无数种方法可以直接令这个程序显示 “OK!!” 消息框。但是,这显然不是这个题目的目的(因为这太简单了,也无需理解那些验证用的汇编代码)。根据题目要求,可以看出出题者的要求是,原程序是不能修改的,解题的人通过阅读和分析验证注册码的汇编代码,得出其验证思路,然后根据此写出注册机(KeyGen),实现给出任意的 UserName,得出 SerialNo = f (UserName) ,也就是要求找出 f 关系,并用编程语言实现注册机。

 

  因此我们用 IDA 汇编这个极小的程序,可以看到它的组成非常简单,下面给出一些主要的汇编代码,并部分的翻译成 C 语言。

  首先是,当点击“注册按钮”,程序将会检测注册码是否正确。在对话框的窗口过程中,可以找到一段重要代码,现在把它提取出来作为一个注册机要用到的重要函数,即根据用户名字符串得到一个整数特征值,汇编代码省略,这里把这个函数翻译为 C 语言如下:

 

//根据用户名,计算出用户特征值
int GetUserValue(LPCTSTR szUser)
{
    int nUserVal= 0x13572468;
    int i;
    int len = _tcslen(szUser);
    for(i = 0; i < len; i++)
    {
        nUserVal = nUserVal + szUser[i];
        nUserVal = nUserVal * 0x03721273;
        nUserVal = nUserVal + 0x24681357;
        nUserVal = (nUserVal << 25) | (nUserVal >> 7);
    }
    return nUserVal;
}

 

  接下来,程序将会调用一个校验注册码的函数,在这个函数中同时会弹出 MessageBox,这个函数由几块功能组成,也是此题目必须要分析的重点,这个函数的原型可以推测出为形如:

 

  void CheckSerialNo(int nUserVal, char* pSerialNo);

 

  通过这个函数,我们可以很容易看到它是如何弹出显示着 “Fail!!" 的消息框的,该函数首先在栈上放置成功和失败的文本密文,然后通过检测结果,把相应的密文地址传送到解密函数(这里称之为 DecryptText),解密函数把解密后的明文放入栈上的缓冲区,然后以此调用 MessageBox,非常明显,这是模拟软件的常规保护方法,在实际软件中都会将关键和敏感信息进行隐藏,当然这也不是这道题目的重点,因为很容易就找到密文的位置,为紧靠 EBP 附近的两个字节数组。

 

  在代码段(.text) 的第一个函数就是解密函数,其原型形如:void DecrptText(const BYTE* pSecret, char* pPlainTextBuffer);  这个加解密非常简单,只是把一个数组用另一个事先拟定好的 key 数组线性的异或了一下而已,所以这不是本题重点,省略不提。

 

  下面给出的是这个程序的关键汇编代码,也就是 CheckSerialNo 的完整代码,此题目的本意正是要求读懂这个函数的逻辑,并找出注册机算法。这个函数较长,但分开割裂不太好,所以完整粘贴如下(前面有一大段花里胡哨的稀奇古怪的指令,一些变量赋值操作也通过隔开少许的 PUSH / POP 来完成,最恶劣的是 DWORD 指针竟然地址不对齐,仿佛是人为故意设置的障碍):

 

.text:004002CC CheckSerialNo   proc near               ; CODE XREF: DialogFunc+CEp
.text:004002CC
.text:004002CC Text            = byte ptr -128h
.text:004002CC var_25          = byte ptr -25h
.text:004002CC var_24          = dword ptr -24h
.text:004002CC var_1B          = byte ptr -1Bh
.text:004002CC var_18          = byte ptr -18h
.text:004002CC var_16          = byte ptr -16h
.text:004002CC var_15          = byte ptr -15h
.text:004002CC var_14          = byte ptr -14h
.text:004002CC var_13          = byte ptr -13h
.text:004002CC var_12          = byte ptr -12h
.text:004002CC var_11          = byte ptr -11h
.text:004002CC var_10          = byte ptr -10h
.text:004002CC var_F           = byte ptr -0Fh
.text:004002CC var_E           = byte ptr -0Eh
.text:004002CC var_C           = byte ptr -0Ch
.text:004002CC var_A           = byte ptr -0Ah
.text:004002CC var_9           = byte ptr -9
.text:004002CC var_8           = byte ptr -8
.text:004002CC var_7           = byte ptr -7
.text:004002CC var_6           = byte ptr -6
.text:004002CC var_5           = byte ptr -5
.text:004002CC var_4           = byte ptr -4
.text:004002CC var_3           = byte ptr -3
.text:004002CC var_2           = byte ptr -2
.text:004002CC CurrentSerialChar= byte ptr -1
.text:004002CC nUserValue      = http://www.mamicode.com/dword ptr  8
.text:004002CC szSerialNo      = dword ptr  0Ch
.text:004002CC
.text:004002CC                 push    ebp             ; char Text[64];
.text:004002CC                                         ; BYTE var_25;
.text:004002CC                                         ; DWORD var_24;
.text:004002CD                 mov     ebp, esp
.text:004002CF                 sub     esp, 128h       ; alloc 296 bytes on stack
.text:004002D5                 and     byte ptr [ebp+var_24], 0 ; var_24[0] = 0;
.text:004002D9                 push    ebx
.text:004002DA                 push    esi
.text:004002DB                 push    edi
.text:004002DC                 xor     eax, eax
.text:004002DE                 lea     edi, [ebp+var_24+1] ; ch[1] = 0
.text:004002E1                 stosd
.text:004002E2                 and     [ebp+Text], 0   ; Text[0] = 0;
.text:004002E9                 push    40h
.text:004002EB                 stosd
.text:004002EC                 stosb
.text:004002ED                 pop     ecx
.text:004002EE                 xor     eax, eax
.text:004002F0                 lea     edi, [ebp-127h]
.text:004002F6                 or      [ebp+var_C], 0FFh
.text:004002FA                 rep stosd
.text:004002FC                 or      [ebp+var_18], 0FFh
.text:00400300                 or      ecx, 0FFFFFFFFh
.text:00400303                 stosw
.text:00400305                 stosb
.text:00400306                 mov     edi, [ebp+szSerialNo]
.text:00400309                 xor     eax, eax
.text:0040030B                 repne scasb
.text:0040030D                 not     ecx
.text:0040030F                 push    1
.text:00400311                 dec     ecx             ; ecx = strlen(szSerialNo)
.text:00400312                 pop     ebx             ; ebx = 1;
.text:00400313                 mov     byte ptr [ebp-0Bh], 63h ; Init BYTE SecretText_Fail[]; (EBP - 0x0C);
.text:00400317                 mov     [ebp+var_A], 0FBh ; "Fail!"的密文
.text:0040031B                 mov     [ebp+var_9], 9Ah
.text:0040031F                 mov     [ebp+var_8], 3
.text:00400323                 mov     [ebp+var_7], 0A3h
.text:00400327                 mov     [ebp+var_6], 0DAh
.text:0040032B                 mov     [ebp+var_5], 72h
.text:0040032F                 mov     [ebp+var_4], 0FEh
.text:00400333                 mov     [ebp+var_3], 0C9h
.text:00400337                 mov     [ebp+var_2], 0B7h
.text:0040033B                 mov     byte ptr [ebp-17h], 6Ah ; Init BYTE SecretText_Success[]; (EBP - 0x18)
.text:0040033F                 mov     [ebp+var_16], 0D1h ; "OK!!"的密文
.text:00400343                 mov     [ebp+var_15], 0D2h
.text:00400347                 mov     [ebp+var_14], 4Eh
.text:0040034B                 mov     [ebp+var_13], 82h
.text:0040034F                 mov     [ebp+var_12], 0DAh
.text:00400353                 mov     [ebp+var_11], 72h
.text:00400357                 mov     [ebp+var_10], 0FEh
.text:0040035B                 mov     [ebp+var_F], 0C9h
.text:0040035F                 mov     [ebp+var_E], 0B7h
.text:00400363                 mov     esi, ecx        ; ecx = strlen(szSerialNo)
.text:00400365                 mov     edi, ebx        ; EDI = 1;
.text:00400367
.text:00400367 loc_400367:                             ; CODE XREF: CheckSerialNo+ACj
.text:00400367                 mov     eax, [ebp+nUserValue] ; ----初始化 var_24[] 内容-----
.text:00400367                                         ; for (edi = 1; edi < 9; ++edi)
.text:0040036A                 mov     ecx, edi        ; ecx = edi;
.text:0040036C                 shr     eax, cl         ; eax = nUserValue >> edi;
.text:0040036E                 and     al, bl          ; al = al & 1
.text:00400370                 mov     byte ptr [ebp+edi+var_24], al ; var_24[edi] = (nUserVal >> edi) & 1;
.text:00400374                 inc     edi
.text:00400375                 cmp     edi, 9
.text:00400378                 jl      short loc_400367 ; for(edi = 1; edi < 9; edi++)
.text:0040037A                 xor     edi, edi        ; edi = 0
.text:0040037C                 mov     [ebp+var_1B], bl ; var_24[9] = 1
.text:0040037F                 test    esi, esi        ; esi = strlen(szSerial)
.text:00400381                 jle     short loc_4003EE
.text:00400383
.text:00400383 loc_400383:                             ; CODE XREF: CheckSerialNo+120j
.text:00400383                 mov     eax, [ebp+szSerialNo] ; for(edi = 0; edi < esi(strlen of Serial); edi++)
.text:00400386                 mov     al, [edi+eax]
.text:00400389                 cmp     al, 30h
.text:0040038B                 mov     [ebp+CurrentSerialChar], al
.text:0040038E                 jl      AlertFail
.text:00400394                 cmp     al, 39h
.text:00400396                 jg      AlertFail       ; if(szSerial[edi] < ‘0‘ || szSerial[edi] > ‘9‘)
.text:00400396                                         ;    goto AlertFail;
.text:0040039C                 mov     eax, edi        ; edi 是序列号字符串的索引值。
.text:0040039E                 push    1Fh
.text:004003A0                 cdq
.text:004003A1                 pop     ecx
.text:004003A2                 idiv    ecx             ; edx = edi % 31;
.text:004003A4                 mov     eax, [ebp+nUserValue]
.text:004003A7                 push    0Ah
.text:004003A9                 mov     ecx, edx        ; ecx = edi % 31;
.text:004003AB                 xor     edx, edx        ; edx = 0;
.text:004003AD                 shr     eax, cl         ; eax = nUserVal >> (EDI % 31);
.text:004003AF                 pop     ecx             ; ecx = 10;
.text:004003B0                 div     ecx             ; edx = (nUserVal >> (EDI % 31)) % 10;
.text:004003B2                 movsx   eax, [ebp+CurrentSerialChar]
.text:004003B6                 lea     eax, [edx+eax-30h] ; eax = EDX + (szSerial[i] - ‘0‘);
.text:004003BA                 xor     edx, edx
.text:004003BC                 div     ecx             ; edx = (edx + szSerial[i] - ‘0‘) % 10;
.text:004003BE                 cmp     edx, ebx        ; ebx = 1
.text:004003C0                 jnz     short loc_4003C7 ; if(edx != 1) goto ...
.text:004003C2                 xor     byte ptr [ebp+var_24+1], bl ; var_24[1] ^ = 1;
.text:004003C5                 jmp     short ContinueLoop ; continue;
.text:004003C7 ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
.text:004003C7
.text:004003C7 loc_4003C7:                             ; CODE XREF: CheckSerialNo+F4j
.text:004003C7                 cmp     [ebp+edx+var_25], bl ; if(var_24[edx-1] != 1)
.text:004003C7                                         ;   goto AleartFail;
.text:004003C7                                         ;
.text:004003C7                                         ; var_24[edx - 1] 必须为1
.text:004003CB                 jnz     short AlertFail
.text:004003CD                 lea     eax, [edx-2]    ; eax = edx - 2;
.text:004003D0                 mov     ecx, ebx        ; ecx = ebx = 1;
.text:004003D2                 cmp     eax, ebx        ; if(edx - 2 < 1) Jump to XOR_Oper
.text:004003D4                 jl      short ChangeOneToZero_
.text:004003D6
.text:004003D6 loc_4003D6:                             ; CODE XREF: CheckSerialNo+113j
.text:004003D6                 cmp     byte ptr [ebp+ecx+var_24], bl ; ecx=1;
.text:004003D6                                         ; do{
.text:004003D6                                         ;
.text:004003D6                                         ;    if (var_24[ecx] == 1) goto AlertFail;
.text:004003D6                                         ;    ++ecx;
.text:004003D6                                         ;
.text:004003D6                                         ; } while (ecx <= edx - 2);
.text:004003DA                 jz      short AlertFail
.text:004003DC                 inc     ecx             ; 检查数组 [1, edx - 2] 的元素是否都是 0.
.text:004003DD                 cmp     ecx, eax
.text:004003DF                 jle     short loc_4003D6
.text:004003E1
.text:004003E1 ChangeOneToZero_:                       ; CODE XREF: CheckSerialNo+108j
.text:004003E1                 xor     byte ptr [ebp+edx+var_24], bl ; var_24[edx] ^= 1; switch var_24[edx];
.text:004003E5                 lea     eax, [ebp+edx+var_24] ; 把当前的1元素变为0!
.text:004003E9
.text:004003E9 ContinueLoop:                           ; CODE XREF: CheckSerialNo+F9j
.text:004003E9                 inc     edi             ; ;for(edi = 0; edi < strlen(szSerial); ++edi) tail
.text:004003EA                 cmp     edi, esi
.text:004003EC                 jl      short loc_400383 ; ----循环尾部-----
.text:004003EE
.text:004003EE loc_4003EE:                             ; CODE XREF: CheckSerialNo+B5j
.text:004003EE                 mov     eax, ebx        ; for(eax = 1; eax < 10; eax++)
.text:004003F0
.text:004003F0 loc_4003F0:                             ; CODE XREF: CheckSerialNo+12Ej
.text:004003F0                 cmp     byte ptr [ebp+eax+var_24], bl ; {
.text:004003F0                                         ;    if(var_24 [eax] == 1)
.text:004003F0                                         ;        goto AlertFail;
.text:004003F0                                         ; }
.text:004003F4                 jz      short AlertFail
.text:004003F6                 inc     eax
.text:004003F7                 cmp     eax, 0Ah
.text:004003FA                 jl      short loc_4003F0
.text:004003FC                 lea     eax, [ebp+Text]
.text:00400402                 push    eax             ; DecryptText(SecretText_OK, Text);
.text:00400403                 lea     eax, [ebp+var_18]
.text:00400406
.text:00400406 loc_400406:                             ; CODE XREF: CheckSerialNo+169j
.text:00400406                 push    eax
.text:00400407                 call    DecryptText     ; 把密文解密,存放到Text中
.text:0040040C                 pop     ecx
.text:0040040D                 lea     eax, [ebp+Text]
.text:00400413                 pop     ecx
.text:00400414                 push    0               ; uType
.text:00400416                 push    offset Caption  ; lpCaption
.text:0040041B                 push    eax             ; lpText
.text:0040041C                 push    0               ; hWnd
.text:0040041E                 call    MessageBoxA
.text:00400424                 pop     edi
.text:00400425                 mov     eax, ebx        ; return 1;
.text:00400427                 pop     esi
.text:00400428                 pop     ebx
.text:00400429                 leave
.text:0040042A                 retn
.text:0040042B ; 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?
.text:0040042B
.text:0040042B AlertFail:                              ; CODE XREF: CheckSerialNo+C2j
.text:0040042B                                         ; CheckSerialNo+CAj ...
.text:0040042B                 lea     eax, [ebp+Text]
.text:00400431                 push    eax
.text:00400432                 lea     eax, [ebp+var_C] ; eax : = (BYTE*) FailText;
.text:00400435                 jmp     short loc_400406
.text:00400435 CheckSerialNo   endp

 

  在汇编代码右侧,我已经大致写了一定的注释,下面把这个校验注册码的函数翻译到 C 语言如下,然后再推断注册机算法。

 

int CheckSerialNo(UINT uintUserVal, char* pSerialNo)
{
    char CurrentSerialChar;
    BYTE Secret_Fail[] = 
    {
        0xFF, 0x63, 0xFB, 0x9A,
        0x03, 0xA3, 0xDA, 0x72, 
        0xFE, 0xC9, 0xB7
    };

    BYTE Secret_OK[] =
    {
        0xFF, 0x6A, 0xD1, 0xD2,
        0x4E, 0x82, 0xDA, 0x72,
        0xFE, 0xC9, 0xB7
    };

    BYTE nUserBits[10];

    char Text[64];

    Text[0] = 0;

    //初始化 nUserBits 内容
    int i; //EDI
    int k;
    int nWhichBit; //汇编中此变量为 EDX
    nUserBits[0] = 0;
    for(i = 1; i < 9; i++)
    {
        nUserBits[i] = (uintUserVal >> i ) & 1;
    }
    nUserBits[9] = 1;

    BOOL bShowFail = FALSE;
    int SerialLen = strlen(pSerialNo);

    //线性遍历注册码
    for(i = 0; i < SerialLen; i++)
    {
        CurrentSerialChar = pSerialNo[i];

        //注册码必须是数字
        if(CurrentSerialChar < 0 || CurrentSerialChar > 9)
        {    bShowFail = TRUE;  break;   } //最初发表时此处遗漏了 break,2014-4-29 补充
        
        nWhichBit = ((uintUserVal >> (i % 31)) + CurrentSerialChar - 0) % 10;

        //修改最低位时,无须做任何校验,可以直接修改。
        if(nWhichBit == 1)
        {
            nUserBits[1] ^= 1;
        }
        else
        {
            //紧邻的低位是 1 吗?如果不是,则注册码错。
            if(nUserBits[nWhichBit - 1] != 1)
            {
                bShowFail = TRUE;
                break;
            }

            //其余低位都是 0 吗?如果不是,则注册码错;
            for(k = 1; nWhichBit- 2 >= 1 && k <= nWhichBit - 2; k++)
            {
                if(nUserBits[k] != 0)
                {
                    bShowFail = TRUE;
                    break;
                }
            }

            if(bShowFail) break;

            //对当前位取反
            nUserBits[nWhichBit] ^= 1;
        }
    }

    //现在查验 nUserBits 是否都为 0,如果是则注册码正确,否则注册码错误
    for(k = 1; k < 10; k++)
        if(nUserBits[k] == 1)
            bShowFail = TRUE;

    //解密密文到 Text 中
    if(bShowFail)
        DecryptText(Secret_Fail, Text);
    else
        DecryptText(Secret_OK, Text);

    MessageBox(NULL, Text, "", MB_OK);
    return 1;
}

 

 

  在高级语言版本中,我使用了一些语言方法,来避免在高级语言代码把汇编中的那些相对跳转直译为 goto,大体上将是等效的。从上面的代码中可以看出检查注册码的重要标准,如果我们把注册码看做输入,实际上在扫描注册码的过程中,就是 nUserBits 这个元素为二元的数组变化的过程,nUserBits 是根据用户填写的用户名计算得到的数组,所以它的元素和用户名相关,是不确定的,注册码扫描结束后,这个数组必须所有元素都为 0。因此,相当于以注册码为驱动。

 

  从上面的代码逻辑中我们还能看到很重要的一点,要修改 nUserBits 的某一位(假设为 nWhichBit),那么必须满足以下条件,nUserBits 必须处于如下状态:

 

Index: 1 2 3 ... 5 6 7 8 9 ...
Value: 0 0 0 ... 0 1 nWhichBit x x ...

 

  也就是说,当我们要切换某一位的状态时,从这一位向低位方向(左侧)看去,应该是 【0】* N + 【1】 的组合(紧邻的低位为 1, 其余均为 0)。最低位(索引 1 )可以随时修改,因为左边已经没有位了,当然就没必要向左看了。

 

  所以这样就提示注册机算法,可以用递归函数 ( 下面代码中的 SetBit 函数 ) 来求解。注册码是由一系列的 nWhichBit (要切换状态的位索引)组成,这个索引是根据注册码的当前位得到的。换句话说,求注册码相当于找出这样一个有序序列,{ b1, b2, b3, ...,  } ,每一步都是合法切换,处理后 nUserBits 所有位都为 0。

 

  这里给出一个例子来说明,以校验的索引范围为从 1 到 4 ,假设 nUserBits 的初始状态为 { 0, 0, 0, 1 } (索引从 1 开始),则如何经过一系列上述规则允许的元素切换,把它变为 { 0, 0, 0, 0 },参考下表(整个过程让我想起了汉诺塔):

 

Index [1] [2] [3] [4] nWhichBit
nUserBits: 0 0 0 1 Initial State
1 0 0 1 [1]
1 1 0 1 [2]
0 1 0 1 [1]
0 1 1 1 [3]
1 1 1 1 [1]
1 0 1 1 [2]
0 0 1 1 [1]
0 0 1 0 [4]
1 0 1 0 [1]
1 1 1 0 [2]
0 1 1 0 [1]
0 1 0 0 [3]
1 1 0 0 [1]
1 0 0 0 [2]
0 0 0 0 [1]

 

 

  把最右侧一列的索引值连在一起,就是一个我们需要设计的位置序列 { 121312141213121 },依次对这些位置进行开关切换(把 nUserBits 每一个元素想象成一个开关)后,就可以让 nUserBits 数组变为全为 0 的状态(注册码正确的条件)。如果不需要和用户特征值关联到一起的话,那这这个序列就是注册码。但原程序中是通过注册码和用户特征值(nUserVal)混编后得到这个调整序列,所以我们只需要对这个调整序列做个逆运算,“剔除”其中的用户特征值成分,即可得到实际注册码。

 

  为此我们给出注册机的如下多个函数,即为题目要求的注册机,显然,注册码如果不限长度,可以有无数多个,但我们当然选择生成简短的。

//用户特征值(注意有时候需要使用其无符号形式,根据CrackMe的指令而定)
int g_UserVal;

//用户特征位数组,由9个位组成 [1,... 9] 仅索引1~9有效
int g_UserBits[10];

//序列号
TCHAR g_SerialNo[4096];

////如果把序列号看着stack,这是栈顶
int g_Top;

//注册机算法:
void Push(int index);
BOOL CanModify(int index);
void SetBit(int index, int nDesiredVal);
int GetUserValue(LPCTSTR szUser);
void InitUserBits();

//index 即需要修改的 UserBits 索引。这个索引变换后,追加到序列号
void Push(int index)
{
    //注意汇编中的右移指令是 SHR,所以需要转为无符号数字(否则为 SAR)
    UINT uintUserVal = g_UserVal;
    
    int nMapped =  index - (uintUserVal >> (g_Top % 31)) % 10;

    if(nMapped < 0)
        nMapped += 10;

    //把数字转换成字符,这样 SerialNo 就是字符串。
    g_SerialNo[g_Top] = nMapped + _T(0);
    ++g_Top;
}

//是否可以直接修改 Bits[ index ]
//要求必须满足 [0 0 0 ... 0 1 Index...

BOOL CanModify(int index)
{
    int i;
    if(index > 1)
    {
        if(g_UserBits[index - 1] != 1)
            return FALSE;
    }
    for(i = 1; i <= index - 2; i++)
    {
        if(g_UserBits[i] != 0)
            return FALSE;
    }
    return TRUE;
}

//递归函数,把索引为index的位设置为 nDesiredVal。

void SetBit(int index, int nDesiredVal)
{
    int i;
    if(g_UserBits[index] != nDesiredVal)
    {
        //能立即修改吗?如果不能,调整低位。
        if(!CanModify(index))
        {
            SetBit(index - 1, 1);
            for(i = index - 2; i >= 1; i--)
            {
                SetBit(i, 0);
            }
        }

        //现在可以修改这一位了!
        g_UserBits[index] = nDesiredVal;
        Push(index);
    }
}

//根据用户名,计算出用户特征值
int GetUserValue(LPCTSTR szUser)
{
    int nUserVal= 0x13572468;
    int i;
    int len = _tcslen(szUser);
    for(i = 0; i < len; i++)
    {
        nUserVal = nUserVal + szUser[i];
        nUserVal = nUserVal * 0x03721273;
        nUserVal = nUserVal + 0x24681357;
        nUserVal = (nUserVal << 25) | (nUserVal >> 7);
    }
    return nUserVal;
}

//
void InitUserBits()
{
    //下面指令中用到 SHR (逻辑右移),所以需要无符号数

    UINT uintUserVal = g_UserVal;
    int i;
    for(i = 1; i < 9; i++)
    {
        g_UserBits[i] = (uintUserVal >> i) & 1;
    }

    //最高位被置为1,保证注册码必然具有相当长度。
    g_UserBits[9] = 1;
}

BOOL GetSerialNo(LPCTSTR szUserName)
{
    int i;

    if(_tcslen(szUserName) == 0)
        return FALSE;

    g_UserVal = GetUserValue(szUserName);
    memset(g_UserBits, 0, sizeof(g_UserBits));
    InitUserBits();

    g_Top = 0;

    for(i = 9; i >= 1; i--)
        SetBit(i, 0);

    //字符串的 null-terminator;
    g_SerialNo[g_Top] = 0;
    ++g_Top;
}

 

  这里,附上原题目程序,我写的注册机的源码以及可执行文件的压缩包下载:

  http://files.cnblogs.com/hoodlum1980/CrackMe_1_1.zip

 

  注册机截图,用 VC 写的一个对话框程(当然写成 Console 程序更容易,但 Windows 程序用户更熟悉):

  

 

  最后,随便给出一个注册机计算出的注册码作为结束(由于注册码太长,所以插入了换行和缩进):

 

  User: hoodlum1980 Serial:

  32991678634237933710214174007422371074154242840289212146582658628926588332475377
  00312194908492670084956232944239113126781668321916686352376337402131741074023730
  74054252845289812136583658428946587332575347006121849094924700049552320442991171
  26681678329916886342377337102161740074123710742542428462892121865826585289265893
  32475357003121149084925700849572329442091131260816683209166863623763372021317430
  740237207405426284528931213658