DOOM3的CD KEY HASH 算法


Hash 算法是一类非常广泛的算法.更是软件保护机制的重点.实际上设计Hash算法是一种需要奇思妙想的活动.这里会收集一些不那么常见的算法.

下面的算法是从一个key gen中提取出来的,算法本身很简单,最难的是如何在汇编代码的丛林中定位这些代码.key gen的作者做到了,我只是将结果翻译出来

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

using namespace std;


int main()
{
// 这段数据在ntdll.dll ,usp10.dll,winnet.dll,urlmon.dll中都有出现
        char _bin[64][16] =  {
{0x00,0x00,0x00,0x00,0x96,0x30,0x07,0x77,0x2c,0x61,0x0e,0xee,0xba,0x51,0x09,0x99},
{0x19,0xC4,0x6D,0x07,0x8F,0xF4,0x6A,0x70,0x35,0xA5,0x63,0xE9,0xA3,0x95,0x64,0x9E},
{0x32,0x88,0xDB,0x0E,0xA4,0xB8,0xDC,0x79,0x1E,0xE9,0xD5,0xE0,0x88,0xD9,0xD2,0x97},
{0x2B,0x4C,0xB6,0x09,0xBD,0x7C,0xB1,0x7E,0x07,0x2D,0xB8,0xE7,0x91,0x1D,0xBF,0x90},
{0x64,0x10,0xB7,0x1D,0xF2,0x20,0xB0,0x6A,0x48,0x71,0xB9,0xF3,0xDE,0x41,0xBE,0x84},
{0x7D,0xD4,0xDA,0x1A,0xEB,0xE4,0xDD,0x6D,0x51,0xB5,0xD4,0xF4,0xC7,0x85,0xD3,0x83},
{0x56,0x98,0x6C,0x13,0xC0,0xA8,0x6B,0x64,0x7A,0xF9,0x62,0xFD,0xEC,0xC9,0x65,0x8A},
{0x4F,0x5C,0x01,0x14,0xD9,0x6C,0x06,0x63,0x63,0x3D,0x0F,0xFA,0xF5,0x0D,0x08,0x8D},
{0xC8,0x20,0x6E,0x3B,0x5E,0x10,0x69,0x4C,0xE4,0x41,0x60,0xD5,0x72,0x71,0x67,0xA2},
{0xD1,0xE4,0x03,0x3C,0x47,0xD4,0x04,0x4B,0xFD,0x85,0x0D,0xD2,0x6B,0xB5,0x0A,0xA5},
{0xFA,0xA8,0xB5,0x35,0x6C,0x98,0xB2,0x42,0xD6,0xC9,0xBB,0xDB,0x40,0xF9,0xBC,0xAC},
{0xE3,0x6C,0xD8,0x32,0x75,0x5C,0xDF,0x45,0xCF,0x0D,0xD6,0xDC,0x59,0x3D,0xD1,0xAB},
{0xAC,0x30,0xD9,0x26,0x3A,0x00,0xDE,0x51,0x80,0x51,0xD7,0xC8,0x16,0x61,0xD0,0xBF},
{0xB5,0xF4,0xB4,0x21,0x23,0xC4,0xB3,0x56,0x99,0x95,0xBA,0xCF,0x0F,0xA5,0xBD,0xB8},
{0x9E,0xB8,0x02,0x28,0x08,0x88,0x05,0x5F,0xB2,0xD9,0x0C,0xC6,0x24,0xE9,0x0B,0xB1},
{0x87,0x7C,0x6F,0x2F,0x11,0x4C,0x68,0x58,0xAB,0x1D,0x61,0xC1,0x3D,0x2D,0x66,0xB6},
{0x90,0x41,0xDC,0x76,0x06,0x71,0xDB,0x01,0xBC,0x20,0xD2,0x98,0x2A,0x10,0xD5,0xEF},
{0x89,0x85,0xB1,0x71,0x1F,0xB5,0xB6,0x06,0xA5,0xE4,0xBF,0x9F,0x33,0xD4,0xB8,0xE8},
{0xA2,0xC9,0x07,0x78,0x34,0xF9,0x00,0x0F,0x8E,0xA8,0x09,0x96,0x18,0x98,0x0E,0xE1},
{0xBB,0x0D,0x6A,0x7F,0x2D,0x3D,0x6D,0x08,0x97,0x6C,0x64,0x91,0x01,0x5C,0x63,0xE6},
{0xF4,0x51,0x6B,0x6B,0x62,0x61,0x6C,0x1C,0xD8,0x30,0x65,0x85,0x4E,0x00,0x62,0xF2},
{0xED,0x95,0x06,0x6C,0x7B,0xA5,0x01,0x1B,0xC1,0xF4,0x08,0x82,0x57,0xC4,0x0F,0xF5},
{0xC6,0xD9,0xB0,0x65,0x50,0xE9,0xB7,0x12,0xEA,0xB8,0xBE,0x8B,0x7C,0x88,0xB9,0xFC},
{0xDF,0x1D,0xDD,0x62,0x49,0x2D,0xDA,0x15,0xF3,0x7C,0xD3,0x8C,0x65,0x4C,0xD4,0xFB},
{0x58,0x61,0xB2,0x4D,0xCE,0x51,0xB5,0x3A,0x74,0x00,0xBC,0xA3,0xE2,0x30,0xBB,0xD4},
{0x41,0xA5,0xDF,0x4A,0xD7,0x95,0xD8,0x3D,0x6D,0xC4,0xD1,0xA4,0xFB,0xF4,0xD6,0xD3},
{0x6A,0xE9,0x69,0x43,0xFC,0xD9,0x6E,0x34,0x46,0x88,0x67,0xAD,0xD0,0xB8,0x60,0xDA},
{0x73,0x2D,0x04,0x44,0xE5,0x1D,0x03,0x33,0x5F,0x4C,0x0A,0xAA,0xC9,0x7C,0x0D,0xDD},
{0x3C,0x71,0x05,0x50,0xAA,0x41,0x02,0x27,0x10,0x10,0x0B,0xBE,0x86,0x20,0x0C,0xC9},
{0x25,0xB5,0x68,0x57,0xB3,0x85,0x6F,0x20,0x09,0xD4,0x66,0xB9,0x9F,0xE4,0x61,0xCE},
{0x0E,0xF9,0xDE,0x5E,0x98,0xC9,0xD9,0x29,0x22,0x98,0xD0,0xB0,0xB4,0xA8,0xD7,0xC7},
{0x17,0x3D,0xB3,0x59,0x81,0x0D,0xB4,0x2E,0x3B,0x5C,0xBD,0xB7,0xAD,0x6C,0xBA,0xC0},
{0x20,0x83,0xB8,0xED,0xB6,0xB3,0xBF,0x9A,0x0C,0xE2,0xB6,0x03,0x9A,0xD2,0xB1,0x74},
{0x39,0x47,0xD5,0xEA,0xAF,0x77,0xD2,0x9D,0x15,0x26,0xDB,0x04,0x83,0x16,0xDC,0x73},
{0x12,0x0B,0x63,0xE3,0x84,0x3B,0x64,0x94,0x3E,0x6A,0x6D,0x0D,0xA8,0x5A,0x6A,0x7A},
{0x0B,0xCF,0x0E,0xE4,0x9D,0xFF,0x09,0x93,0x27,0xAE,0x00,0x0A,0xB1,0x9E,0x07,0x7D},
{0x44,0x93,0x0F,0xF0,0xD2,0xA3,0x08,0x87,0x68,0xF2,0x01,0x1E,0xFE,0xC2,0x06,0x69},
{0x5D,0x57,0x62,0xF7,0xCB,0x67,0x65,0x80,0x71,0x36,0x6C,0x19,0xE7,0x06,0x6B,0x6E},
{0x76,0x1B,0xD4,0xFE,0xE0,0x2B,0xD3,0x89,0x5A,0x7A,0xDA,0x10,0xCC,0x4A,0xDD,0x67},
{0x6F,0xDF,0xB9,0xF9,0xF9,0xEF,0xBE,0x8E,0x43,0xBE,0xB7,0x17,0xD5,0x8E,0xB0,0x60},
{0xE8,0xA3,0xD6,0xD6,0x7E,0x93,0xD1,0xA1,0xC4,0xC2,0xD8,0x38,0x52,0xF2,0xDF,0x4F},
{0xF1,0x67,0xBB,0xD1,0x67,0x57,0xBC,0xA6,0xDD,0x06,0xB5,0x3F,0x4B,0x36,0xB2,0x48},
{0xDA,0x2B,0x0D,0xD8,0x4C,0x1B,0x0A,0xAF,0xF6,0x4A,0x03,0x36,0x60,0x7A,0x04,0x41},
{0xC3,0xEF,0x60,0xDF,0x55,0xDF,0x67,0xA8,0xEF,0x8E,0x6E,0x31,0x79,0xBE,0x69,0x46},
{0x8C,0xB3,0x61,0xCB,0x1A,0x83,0x66,0xBC,0xA0,0xD2,0x6F,0x25,0x36,0xE2,0x68,0x52},
{0x95,0x77,0x0C,0xCC,0x03,0x47,0x0B,0xBB,0xB9,0x16,0x02,0x22,0x2F,0x26,0x05,0x55},
{0xBE,0x3B,0xBA,0xC5,0x28,0x0B,0xBD,0xB2,0x92,0x5A,0xB4,0x2B,0x04,0x6A,0xB3,0x5C},
{0xA7,0xFF,0xD7,0xC2,0x31,0xCF,0xD0,0xB5,0x8B,0x9E,0xD9,0x2C,0x1D,0xAE,0xDE,0x5B},
{0xB0,0xC2,0x64,0x9B,0x26,0xF2,0x63,0xEC,0x9C,0xA3,0x6A,0x75,0x0A,0x93,0x6D,0x02},
{0xA9,0x06,0x09,0x9C,0x3F,0x36,0x0E,0xEB,0x85,0x67,0x07,0x72,0x13,0x57,0x00,0x05},
{0x82,0x4A,0xBF,0x95,0x14,0x7A,0xB8,0xE2,0xAE,0x2B,0xB1,0x7B,0x38,0x1B,0xB6,0x0C},
{0x9B,0x8E,0xD2,0x92,0x0D,0xBE,0xD5,0xE5,0xB7,0xEF,0xDC,0x7C,0x21,0xDF,0xDB,0x0B},
{0xD4,0xD2,0xD3,0x86,0x42,0xE2,0xD4,0xF1,0xF8,0xB3,0xDD,0x68,0x6E,0x83,0xDA,0x1F},
{0xCD,0x16,0xBE,0x81,0x5B,0x26,0xB9,0xF6,0xE1,0x77,0xB0,0x6F,0x77,0x47,0xB7,0x18},
{0xE6,0x5A,0x08,0x88,0x70,0x6A,0x0F,0xFF,0xCA,0x3B,0x06,0x66,0x5C,0x0B,0x01,0x11},
{0xFF,0x9E,0x65,0x8F,0x69,0xAE,0x62,0xF8,0xD3,0xFF,0x6B,0x61,0x45,0xCF,0x6C,0x16},
{0x78,0xE2,0x0A,0xA0,0xEE,0xD2,0x0D,0xD7,0x54,0x83,0x04,0x4E,0xC2,0xB3,0x03,0x39},
{0x61,0x26,0x67,0xA7,0xF7,0x16,0x60,0xD0,0x4D,0x47,0x69,0x49,0xDB,0x77,0x6E,0x3E},
{0x4A,0x6A,0xD1,0xAE,0xDC,0x5A,0xD6,0xD9,0x66,0x0B,0xDF,0x40,0xF0,0x3B,0xD8,0x37},
{0x53,0xAE,0xBC,0xA9,0xC5,0x9E,0xBB,0xDE,0x7F,0xCF,0xB2,0x47,0xE9,0xFF,0xB5,0x30},
{0x1C,0xF2,0xBD,0xBD,0x8A,0xC2,0xBA,0xCA,0x30,0x93,0xB3,0x53,0xA6,0xA3,0xB4,0x24},
{0x05,0x36,0xD0,0xBA,0x93,0x06,0xD7,0xCD,0x29,0x57,0xDE,0x54,0xBF,0x67,0xD9,0x23},
{0x2E,0x7A,0x66,0xB3,0xB8,0x4A,0x61,0xC4,0x02,0x1B,0x68,0x5D,0x94,0x2B,0x6F,0x2A},
{0x37,0xBE,0x0B,0xB4,0xA1,0x8E,0x0C,0xC3,0x1B,0xDF,0x05,0x5A,0x8D,0xEF,0x02,0x2D}
};
        char bin = &_bin[0][0];
        
        DWORD tick_count GetTickCount();
        //tick_count = 0x01A02A71;
        char inter[21];
        inter [20] = '\0';
        char charpool "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
            
        //
        // 外层循环5次,每次通过将tick_count乘以一个随机数得到一个32bit的数
        //
        
        for int i++)
        {
                //每次外层循环只是改变随机数
                /*int r;
                if (i == 0 ) r = 0x00000029;
                if (i == 1 ) r = 0x00004823;
                if (i == 2 ) r = 0x000018be;
                if (i == 3 ) r = 0x00006784;*/
                int tick_count_copy1 tick_count rand();//每次外层循环tick_count都是GetTickCount的值
                
                // 产生一个随机数,其中的5个4bits作为索引,得到5个字符
                // 一个随机数产生5个字符,4个随机数产生20个字符
                
                
                //
                //  内层循环,执行5次,每次取32bit数的低5位,将其作为索引在字符串池中grab a char
                // 将这个字符追加到结果字符串中,完成后将32bit数中使用过的5bit移除
                // 
                //
                
                for int j++)
                {
                        //每次内层循环将DWORD移位5次,每次4bit
                        int pool_index tick_count_copy1 0x1f;
                        char charpool[pool_index];
                        inter[i*5+j] = c;//ij:00 : 0 ij:01 :1 ij:10 : 5
                        tick_count_copy1 >>= 4;
                        
                        if == && == end_number tick_count_copy1;
                }
        }
        
        // 生成一个20个字符长的字符串,其中前面16个事实际使用的KEY
        //上面的代码其实就是生成KEY的过程,下面才是计算Hash
        
        inter[16] = '\0';//只有前面16个字符有意义 mov dword inter + 10h , 0
        
        //前面16个字符的结果完全正确
        
        int end_number = -1;
        printf("end_number:%08X\n",end_number);
        
        //DWORD tmp = *((DWORD *)(&bin[1*4]));//test works very well        
        //cout << hex << tmp << endl;
        //下面的算是一种hash算法,从16个字符中各自提取一些信息,并参与运算
        //得到的是一个8bit的数字
        
        //消息摘要算法,验证器用这个算法来对前面的16个字符进行摘要运算,结果得到一个8bit的数
        //如果cdkey最后两个字符表示的数字符合,就算通过
        
        
        for int 16 i++ )
        {
                char inter[i];
                printf("%02x,%02x\n",c,inter[i]);
        
                ^= (char)(end_number 0x000000ff);//xor dl, al,edx循环之前初始化为0,然后每次参与运算的都是dl,因此dl就可以表示edx
                printf("%02x,%04x\n",c,end_number);
                unsigned int cti ;
                cti &= 0x000000ff;
                end_number >>= 8;//end_number是带符号的,右移的时候高位填充1
                end_number &= 0x00ffffff;//高8位填充0,模拟shr
                DWORD tmp = (*(DWORD *)(&bin[((unsigned int)cti)*4]));
                cout << "取到的DWORD是" << hex << tmp << " edx是" ;printf("%02X\n",cti);
                end_number ^= tmp;
        }
        
        printf("end_number:%08X",end_number);
        
        
        end_number = ~end_number;
        
        //确保此时end_number是正确的
        
        printf("end_number:%08X",end_number);
        
        int ll end_number 0x000000ff;
        int lh end_number 0x0000ff00;
        lh >>=8;
        int hl end_number 0x00ff0000;
        hl >>=16;
        int hh end_number 0xff000000;
        hh >>= 24;
        
        hl ^= hh;
        lh ^= hl;
        ll ^= lh;
        
        //ll的低8位,就是最后的数字

        
        
        cout << "intermediate string is " << inter << endl;
        
        cout << "cd key is " << inter << "-" << hex << (unsigned)ll << endl;
        printf("%02X",ll);
  
        return 0;  
}

附:Hash算法反汇编代码

0040109B GenerateCDKey proc near                    ; CODE XREF: sub_401025+46p
0040109B call    GetTickCount
004010A0 mov     TickCounteax
004010A5 mov     ebx, offset LocalVar
004010AA mov     edi, offset a23456789abcdef     ; "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
004010AF xor     esiesi
004010B1
004010B1 loc_4010B1:                             ; CODE XREF: sub_40109B+40j
004010B1 call    MSVCRT_rand
004010B6 mov     ecxTickCount
004010BC imul    eaxecx                        ; imul是两个操作数还是一个操作数,有两种格式
004010BF mov     edxeax
004010C1 mov     ecx5                          ; 0 到 31 ,刚好是上面字符串的下标
004010C6
004010C6 loc_4010C6:                             ; CODE XREF: sub_40109B+3Aj
004010C6 and     eax1Fh
004010C9 mov     al, [eax+edi]                   ; TickCount乘随机数,取低5位bit
004010CC mov     [ebx], al
004010CE inc     ebx
004010CF shr     edx4                          ; 原先的随机数
004010D2 mov     eaxedx                        ; eax重新开始循环
004010D4 dec     ecx
004010D5 jnz     short loc_4010C6                ; 结束的时候产生5个字符
004010D7 inc     esi                             
004010D8 cmp     esi4                          ;外层循环 i++
004010DB jnz     short loc_4010B1
004010DD mov     dword_4024560
004010E7 mov     esi, offset LocalVar
004010EC xor     edxedx
004010EE or      eax0FFFFFFFFh                 ; 此时eax的值是什么,循环结束前,进行>>4之后的结果
004010F1 mov     ecx10h
004010F6
for (int ; i < 16 ; i++)
{
004010F6 loc_4010F6:                             ; CODE XREF: sub_40109B+6Bj
004010F6 mov     dl, [esi]                       ; LocalVar
004010F8 xor     dlal                          ; 取eax的低8位和字符异或
004010FA shr     eax8
004010FD xor     eaxdword_402000[edx*4]
00401104 inc     esi                             ;遍历字符串的索引
00401105 dec     ecx                             ;遍历字符串,16次
00401106 jnz     short loc_4010F6
}
00401108 not     eax                             ;根据产生的前16个字符计算一个签名
0040110A mov     dword_40247Aeax
0040110F mov     edxdword_40247A
00401115 and     edx0FFh
0040111B mov     eaxdword_40247A
00401120 and     eax0FF00h
00401125 shr     eax8
00401128 mov     ecxdword_40247A
0040112E and     ecx0FF0000h
00401134 shr     ecx10h
00401137 mov     esidword_40247A
0040113D and     esi0FF000000h
00401143 shr     esi18h
00401146 xor     ecxesi
00401148 xor     eaxecx
0040114A xor     edxeax
0040114C push    edx
0040114D push    offset a02x                     ; "%02X"
00401152 push    offset dword_402456             ; 生成最后的一个十六进制数,长度为2
00401157 call    wsprintf
0040115C add     esp0Ch
0040115F push    4
00401161 push    offset LocalVar
00401166 push    offset unk_40247E               ; 生成CDKEY的前4个字符
0040116B call    strncpy
00401170 mov     dword_4024822Dh
0040117A push    4
0040117C push    offset unk_40244A
00401181 push    402483h                         ; 生成第二个key,就是读取生成的字符串中的字符
00401186 call    strncpy
0040118B mov     dword_4024872Dh               ; 给字符串做截断 A B C D 2D 00 00 00,2D 即'-'
00401195 push    4
00401197 push    offset unk_40244E
0040119C push    402488h                         ; 第三个key
004011A1 call    strncpy
004011A6 mov     dword_40248C2Dh
004011B0 push    4
004011B2 push    offset unk_402452
004011B7 push    40248Dh                         ; 第四个key
004011BC call    strncpy
004011C1 mov     dword_4024912Dh
004011CB push    2
004011CD push    offset dword_402456             ; 最后的两个字符
004011D2 push    402492h
004011D7 call    strncpy
004011DC add     esp3Ch
004011DF push    offset unk_40247E               lParam
004011E4 push    0
004011E6 push    0Ch                             ;WM_SETTEXT
004011E8 push    dword_402476
004011EE call    SendMessage
004011F3 retn
004011F3 sub_40109B endp ; sp = -10