发新话题
打印

[转载]WinRAR 3.62注册算法(CRC校验 + 黑名单 + SHA1 hash算法)

[转载]WinRAR 3.62注册算法(CRC校验 + 黑名单 + SHA1 hash算法)

文章作者:icytear

时 间: 2006-12-19,22:24
链 接: http://bbs.pediy.com/showthread.php?threadid=36647

WinRAR 3.62注册算法 - CRC校验

[声明] 本文为逆向学习参考文章, 若使用 winrar 请向作者注册.

用WinRAR3.51的key文件rarreg.key发现被加入了黑名单, 所以分析了一下.

3.51注册key内容:
RAR registration data
Carol Thompson
Single PC usage license
UID=b8bc6fb0a8094b9eeb29
6412212250eb294bd5b605e535f7334b6e2e56a9e405a044f60225
c843a161a156aa01684c6035c6ab9048e2c5c62f0238f183d28519
aa87488bf38f5b634cf28190bdf438ac593b1857cdb55a7fcb0eb0
c3e4c2736090b3dfa45384e08e9de05c5860ae8049eaa9443b44f9
faac06b7ced5f95ab06b40a99e850616dc92fc5301fe63c674ea55
3971fefd9e10f300d2a515c74b02f673b7fe5a89fa92f51260a5af
78a306093f5763d6acc779488f5d42e9b044836a837c0424153795

1. CRC校验

使用CreateFileA 函数断点, 跟踪对文件rarreg.key文件的读取, 到以下代码:


.text:0040E6C4 check_regdata  proc near
.text:0040E6C4            push   ebx
.text:0040E6C5            push   esi
.text:0040E6C6            push   edi
.text:0040E6C7            push   ebp
.text:0040E6C8            add    esp, 0FFFFF178h ; data
.text:0040E6CE            mov    edi, eax
.text:0040E6D0            or    eax, 0FFFFFFFFh ; crc32
.text:0040E6D3            mov    esi, offset regData
.text:0040E6D8            mov    [esp+0E88h+var_E88], dl
.text:0040E6DB            mov    edx, offset g_HCode0 ; "70c2441db366d92ea7be1342b3bf629026ba92b"...
.text:0040E6E0            mov    ecx, 40h      ; len
.text:0040E6E5            call   NCrc32
.text:0040E6E5
.text:0040E6EA            cmp    eax, 26E831B8h
.text:0040E6EF            jz    short HCODE0_CRC_OK ; [Check1] 如果硬编码串的CRC32出错, 注册失败!
.text:0040E6EF
.text:0040E6F1            xor    eax, eax
.text:0040E6F3            jmp    REG_FAIL_EXIT

首先出现了一个硬编码的字符串
g_HCode0 = "70c2441db366d92ea7be1342b3bf629026ba92bb675f06e684bdd34511097434"


然后调用NCrc32计算 g_HCode0的 ~CRC32值, 比较此值是否等于0x26E831B8, 这里注册成功
的关键是WinRAR未修改g_HCode0的值.


也许你已经发现为什么函数的名字前面加了一个N, 其实查看此函数就知道, 这里计算出的结
果并非CRC32值, 而是CRC32的非, 代码如下

.text:00410734 ; DWORD __fastcall NCrc32(DWORD crc32,unsigned __int8 *buffer,DWORD len)
.text:00410734 NCrc32       proc near  
.text:00410734            push   ebx
.text:00410735            push   esi
.text:00410736            push   edi
.text:00410737            mov    edi, offset crc32_table
.text:0041073C            push   ebp
.text:0041073D            mov    ebp, edx
.text:0041073F            cmp    dword ptr [edi+4], 0
.text:00410743            mov    esi, ecx
.text:00410745            mov    ebx, eax
.text:00410747            jnz    short CRC32TABLE_OK ; 如果CRC32表的第2个dword值为0,
.text:00410747                            ; 则重新构建CRC32表crc32_table
.text:00410747
.text:00410749            call   makeCrc32Table  ; 构建CRC32表
.text:0041074E
.text:0041074E CRC32TABLE_OK:                  ; CODE XREF: NCrc32+13j
.text:0041074E            mov    eax, ebp
.text:00410750            jmp    short FOR1_condition ; FOR1的条件, buffer未处理部分长度大于0,
.text:00410750                            ; 且buffer地址 8字节未对齐, 则继续循环
.text:00410752 ; ---------------------------------------------------------------------------
.text:00410752 FOR1:                        ; CODE XREF: NCrc32+38j
.text:00410752            mov    edx, ebx      ; 第一个for循环处理 buffer地址未对齐部分
.text:00410754            xor    ecx, ecx
.text:00410756            xor    dl, [eax]
.text:00410758            dec    esi
.text:00410759            mov    cl, dl
.text:0041075B            shr    ebx, 8
.text:0041075E            mov    edx, [edi+ecx*4]
.text:00410761            xor    edx, ebx
.text:00410763            inc    eax
.text:00410764            mov    ebx, edx
.text:00410766
.text:00410766 FOR1_condition:                 ; CODE XREF: NCrc32+1Cj
.text:00410766            test   esi, esi      ; FOR1的条件, buffer未处理部分长度大于0,
.text:00410766                            ; 且buffer地址 8字节未对齐, 则继续循环
.text:00410768            jbe    short loc_41076E
.text:00410768
.text:0041076A            test   al, 7
.text:0041076C            jnz    short FOR1    ; 第一个for循环处理 buffer地址未对齐部分
.text:0041076E
.text:0041076E loc_41076E:                    ; CODE XREF: NCrc32+34j
.text:0041076E            cmp    esi, 8
.text:00410771            jb    loc_4107F7    ; 判断buffer 未处理部分的长度是否大于8
.text:00410771
.text:00410777 loc_410777:                    ; CODE XREF: NCrc32+C1j
.text:00410777            xor    ebx, [eax]    ; 每次循环计算8个字节
.text:00410779            xor    ecx, ecx
.text:0041077B            mov    cl, bl
.text:0041077D            sub    esi, 8
.text:00410780            shr    ebx, 8
.text:00410783            mov    edx, [edi+ecx*4]
.text:00410786            xor    edx, ebx
.text:00410788            xor    ecx, ecx
.text:0041078A            mov    ebx, edx
.text:0041078C            mov    cl, bl
.text:0041078E            shr    ebx, 8
.text:00410791            mov    edx, [edi+ecx*4]
.text:00410794            xor    edx, ebx
.text:00410796            xor    ecx, ecx
.text:00410798            mov    ebx, edx
.text:0041079A            mov    cl, bl
.text:0041079C            shr    ebx, 8
.text:0041079F            mov    edx, [edi+ecx*4]
.text:004107A2            xor    edx, ebx
.text:004107A4            xor    ecx, ecx
.text:004107A6            mov    ebx, edx
.text:004107A8            mov    cl, bl
.text:004107AA            shr    ebx, 8
.text:004107AD            mov    edx, [edi+ecx*4]
.text:004107B0            xor    edx, ebx
.text:004107B2            xor    ecx, ecx
.text:004107B4            mov    ebx, edx
.text:004107B6            xor    ebx, [eax+4]
.text:004107B9            add    eax, 8
.text:004107BC            mov    cl, bl
.text:004107BE            shr    ebx, 8
.text:004107C1            mov    edx, [edi+ecx*4]
.text:004107C4            xor    edx, ebx
.text:004107C6            xor    ecx, ecx
.text:004107C8            mov    ebx, edx
.text:004107CA            mov    cl, bl
.text:004107CC            shr    ebx, 8
.text:004107CF            mov    edx, [edi+ecx*4]
.text:004107D2            xor    edx, ebx
.text:004107D4            xor    ecx, ecx
.text:004107D6            mov    ebx, edx
.text:004107D8            mov    cl, bl
.text:004107DA            shr    ebx, 8
.text:004107DD            mov    edx, [edi+ecx*4]
.text:004107E0            xor    edx, ebx
.text:004107E2            xor    ecx, ecx
.text:004107E4            mov    ebx, edx
.text:004107E6            mov    cl, bl
.text:004107E8            shr    ebx, 8
.text:004107EB            mov    edx, [edi+ecx*4]
.text:004107EE            xor    edx, ebx
.text:004107F0            cmp    esi, 8
.text:004107F3            mov    ebx, edx
.text:004107F5            jnb    short loc_410777 ; 每次循环计算8个字节
.text:004107F5
.text:004107F7 loc_4107F7:                    ; CODE XREF: NCrc32+3Dj
.text:004107F7            xor    edx, edx
.text:004107F9            jmp    short loc_410811
.text:004107FB ; ---------------------------------------------------------------------------
.text:004107FB loc_4107FB:                    ; CODE XREF: NCrc32+DFj
.text:004107FB            mov    ecx, ebx      ; 处理剩余部分
.text:004107FD            xor    cl, [eax+edx]
.text:00410800            and    ecx, 0FFh
.text:00410806            shr    ebx, 8
.text:00410809            mov    ecx, [edi+ecx*4]
.text:0041080C            xor    ecx, ebx
.text:0041080E            mov    ebx, ecx
.text:00410810            inc    edx
.text:00410811
.text:00410811 loc_410811:                    ; CODE XREF: NCrc32+C5j
.text:00410811            cmp    esi, edx
.text:00410813            ja    short loc_4107FB ; 处理剩余部分
.text:00410815            mov    eax, ebx
.text:00410817            pop    ebp
.text:00410818            pop    edi
.text:00410819            pop    esi
.text:0041081A            pop    ebx
.text:0041081B            retn
.text:0041081B
.text:0041081B NCrc32       endp

用C简单实现了一下:

unsigned int
NCrc32 (unsigned int crc32, unsigned char *buff, unsigned int len)
{
      if (crc32Table[1] == 0)
           makeCrc32Table();

      for ( ; (len>0) && (((unsigned int)buff & 7)!=0); --len) // 8字节对齐
      {
           crc32 = crc32Table[(crc32 ^ *buff++) & 0xFF] ^ (crc32>>8);
      }
      // 每次计算8个字节
      while (len >8)
      {
           crc32 ^= *(unsigned int*)buff;
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);

           crc32 ^= *(unsigned int*)(buff+4);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
           crc32 = crc32Table[crc32 & 0xFF] ^(crc32>>8);
      
           len -= 8;
           buff += 8;
      }
      // 计算剩余部分
      for (unsigned int i=0; len>i; i++)
      {
           crc32 = crc32Table[(crc32 ^ buff) & 0xFF] ^ (crc32>>8);
      }
      return crc32;
}

下面的代码用到一个结构 REG_DATA:

typedef struct _reg_data
{
      char usrname[256];
      char licType[256];
      char snCode1[256];
      char snCode4[256];
      char snCode3[256];
      char snCode2[256];
      u_int32_t crc32;
}REG_DATA;


Proxies::CreateSubClass不知道做什么的, 大概是构造子串的, 既然是库函数直接看结果了,
结果在edx指向的内存产生一个字符串, 每次都是rarreg.key文件的一行(在这之前文件的内
容已经读到内存了, 读文件部分被略了:)).


.text:0040E6F8 HCODE0_CRC_OK:
.text:0040E6F8            push   604h        ; len
.text:0040E6FD            push   0          ; c
.text:0040E6FF            push   regData      ; REG_DATA
.text:0040E700            call   _memset          ; 结构清零
.text:0040E700
.text:0040E705            add    esp, 0Ch
.text:0040E708            jmp    short loc_40E715
.text:0040E70A ; ---------------------------------------------------------------------------
.text:0040E70A loc_40E70A:                    ; CODE XREF: check_regdata+63j
.text:0040E70A            xor    edx, edx
.text:0040E70C            mov    dl, byte ptr [esp+0E88h+strTemp]
.text:0040E710            cmp    edx, 23h
.text:0040E713            jnz    short loc_40E729
.text:0040E715 loc_40E715:                    ; CODE XREF: check_regdata+44j
.text:0040E715            lea    edx, [esp+0E88h+strTemp]
.text:0040E719            mov    ecx, 400h
.text:0040E71E            mov    eax, edi   ;
.text:0040E720            call   Proxies::CreateSubClass(System::TObject *,System::AnsiString,System::TMetaClass *)
.text:0040E725            test   al, al
.text:0040E727            jnz    short loc_40E70A
.text:0040E729
.text:0040E729 loc_40E729:                    ; CODE XREF: check_regdata+4Fj
.text:0040E729            mov    edx, regData
.text:0040E72B            mov    ecx, 100h
.text:0040E730            mov    eax, edi
.text:0040E732            call   Proxies::CreateSubClass(System::TObject *,System::AnsiString,System::TMetaClass *)
.text:0040E737            lea    edx, [regData+REG_DATA.licType]
.text:0040E73D            mov    ecx, 100h
.text:0040E742            mov    eax, edi
.text:0040E744            call   Proxies::CreateSubClass(System::TObject *,System::AnsiString,System::TMetaClass *)
.text:0040E749            lea    edx, [esp+0E88h+regUid]
.text:0040E750            mov    ecx, 80h
.text:0040E755            mov    eax, edi
.text:0040E757            call   Proxies::CreateSubClass(System::TObject *,System::AnsiString,System::TMetaClass *)
.text:0040E75C            xor    ebx, ebx
.text:0040E75E
.text:0040E75E loc_40E75E:                    ; CODE XREF: check_regdata+B7j
.text:0040E75E            imul   eax, ebx, 36h
.text:0040E761            mov    ecx, 400h
.text:0040E766            mov    edx, eax
.text:0040E768            sub    ecx, eax
.text:0040E76A            lea    eax, [esp+0E88h+strTemp]
.text:0040E76E            add    edx, eax
.text:0040E770            mov    eax, edi
.text:0040E772            call   Proxies::CreateSubClass(System::TObject *,System::AnsiString,System::TMetaClass *)
.text:0040E777            inc    ebx
.text:0040E778            cmp    ebx, 7
.text:0040E77B            jl    short loc_40E75E   ;把UID行以后的, 当一串字符串(去掉CR/LF)


按字符串的前10位(snHead="6412212250" )分割字符串
.text:0040E77D            lea    eax, [esp+0E88h+strTemp]
.text:0040E781            mov    edx, 2
.text:0040E786            call   rar_atoi    ;rar实现的把指定长度的字符串转换位整型
.text:0040E78B            mov    [esp+0E88h+snCode1_len], eax
.text:0040E78F            lea    eax, [esp+0E88h+strTemp+2]
.text:0040E793            mov    edx, 3
.text:0040E798            call   rar_atoi
.text:0040E79D            mov    edi, eax
.text:0040E79F            lea    eax, [esp+0E88h+strTemp+5]
.text:0040E7A3            mov    edx, 3
.text:0040E7A8            call   rar_atoi
.text:0040E7AD            mov    ebp, eax
.text:0040E7AF            lea    eax, [esp+0E88h+strTemp+8]
.text:0040E7B3            mov    edx, 2
.text:0040E7B8            call   rar_atoi
.text:0040E7BD            mov    [esp+0E88h+snCode4_len], eax
.text:0040E7C1            cmp    edi, 100h     ; [Check2] 比较[注册字符串2]的长度
.text:0040E7C7            jg    short loc_40E7D1 ; [Check2]
.text:0040E7C7
.text:0040E7C9            cmp    ebp, 100h     ; [Check2] 比较[注册字符串3]的长度
.text:0040E7CF            jle    short loc_40E7D8
.text:0040E7D1
.text:0040E7D1 loc_40E7D1:                    ; CODE XREF: check_regdata+103j
.text:0040E7D1            xor    eax, eax      ; [Check2]
.text:0040E7D3            jmp    REG_FAIL_EXIT  ; 如果 [注册字符串2], [注册字符串3] 有一个长度大于 256,
.text:0040E7D3                            ; 注册失败!
.text:0040E7D8 ; ---------------------------------------------------------------------------

[跳过一段代码, 这段代码是检查黑名单, 稍后讲]


下面这部分是按照snHead="6412212250", 把字符串分割后拷贝到regData结构中, 分割方法
就是按上面的 2, 3, 3, 2, 即64, 122, 122, 50(十进制)长分割.


.text:0040E882 loc_40E882:                    ; CODE XREF: check_regdata+1A5j
.text:0040E882                            ; check_regdata+1B5j
.text:0040E882            inc    ebx
.text:0040E883            cmp    ebx, 6
.text:0040E886            jl    short NEXT_UBLACK
.text:0040E888            lea    ebx, [esp+0E88h+strTemp+0Ah]
.text:0040E88C            push   [esp+0E88h+snCode1_len] ; snCode1_len
.text:0040E890            push   ebx         ; src
.text:0040E891            lea    eax, [regData+REG_DATA.snCode1]
.text:0040E897            push   eax         ; dest
.text:0040E898            call   _strncpy
.text:0040E89D            add    esp, 0Ch
.text:0040E8A0            add    ebx, [esp+0E88h+snCode1_len]
.text:0040E8A4            push   edi         ; snCode2_len
.text:0040E8A5            push   ebx         ; src
.text:0040E8A6            lea    edx, [regData+REG_DATA.snCode2]
.text:0040E8AC            push   edx         ; dest
.text:0040E8AD            call   _strncpy
.text:0040E8B2            add    esp, 0Ch
.text:0040E8B5            add    ebx, edi
.text:0040E8B7            push   ebp         ; snCode3_len
.text:0040E8B8            push   ebx         ; src
.text:0040E8B9            lea    ecx, [regData+REG_DATA.snCode3]
.text:0040E8BF            push   ecx         ; dest
.text:0040E8C0            call   _strncpy
.text:0040E8C5            add    esp, 0Ch
.text:0040E8C8            add    ebx, ebp
.text:0040E8CA            push   [esp+0E88h+snCode4_len] ; maxlen
.text:0040E8CE            push   ebx         ; src
.text:0040E8CF            lea    eax, [regData+REG_DATA.snCode4]
.text:0040E8D5            push   eax         ; dest
.text:0040E8D6            call   _strncpy
.text:0040E8DB            add    esp, 0Ch
.text:0040E8DE            add    ebx, [esp+0E88h+snCode4_len]
.text:0040E8E2            push   0Ah         ; radix
.text:0040E8E4            push   0          ; endptr
.text:0040E8E6            push   ebx         ; s
.text:0040E8E7            call   _strtoul      ; 把最后10位转化作为CRC32
.text:0040E8EC            add    esp, 0Ch
.text:0040E8EF            mov    [regData+REG_DATA.crc32], eax

分割后各部分数据如下:

regHead       = "RAR registration data"
regData.username = "Carol Thompson"   //
regData.licType  = "Single PC usage license"  // regData.licType 注册类型
regUid        = "UID=b8bc6fb0a8094b9eeb29"  //
snHead        = "6412212250"    // [注册字符串头] len = 10
regData.snCode1  = "eb294bd5b605e535f7334b6e2e56a9e405a044f60225c843a161a156aa01684c" // [注册字符串1] len = 64
regData.snCode2  = "6035c6ab9048e2c5c62f0238f183d28519aa87488bf38f5b634cf28190bdf438ac593b1857cdb55a7fcb0eb0c3e4c2736090b3dfa45384e08e9de05c58" //  [注册字符串2] len = 122
regData.snCode3  = "60ae8049eaa9443b44f9faac06b7ced5f95ab06b40a99e850616dc92fc5301fe63c674ea553971fefd9e10f300d2a515c74b02f673b7fe5a89fa92f512" //  [注册字符串3] len = 122
regData.snCode4  = "60a5af78a306093f5763d6acc779488f5d42e9b044836a837c", // [注册字符串4] len = 50
regData.crc32   = "0424153795"; // [头和1,2,3部分的 NCRC32值(10进制)] len = 10


下面代码是计算 regData.licType + username + regData.snCode1 + .snCode2 + .snCode3 + .snCode4
的~CRC32值, 判断是否等于regData.crc32, 不等注册失败!

.text:0040E8F5            lea    edx, [regData+REG_DATA.licType]
.text:0040E8FB            push   edx         ; s
.text:0040E8FC            call   _strlen
.text:0040E901            pop    ecx
.text:0040E902            mov    ecx, eax      ; len
.text:0040E904            lea    edx, [regData+REG_DATA.licType] ; buffer
.text:0040E90A            or    eax, 0FFFFFFFFh ; crc32
.text:0040E90D            call   NCrc32
.text:0040E912            mov    ebx, eax
.text:0040E914            push   regData      ; s
.text:0040E915            call   _strlen
.text:0040E91A            pop    ecx
.text:0040E91B            mov    ecx, eax      ; len
.text:0040E91D            mov    edx, regData   ; buffer
.text:0040E91F            mov    eax, ebx      ; crc32
.text:0040E921            call   NCrc32
.text:0040E926            mov    ebx, eax
.text:0040E928            lea    eax, [regData+REG_DATA.snCode1]
.text:0040E92E            push   eax         ; s
.text:0040E92F            call   _strlen
.text:0040E934            pop    ecx
.text:0040E935            mov    ecx, eax      ; len
.text:0040E937            lea    edx, [regData+REG_DATA.snCode1] ; buffer
.text:0040E93D            mov    eax, ebx      ; crc32
.text:0040E93F            call   NCrc32
.text:0040E944            mov    ebx, eax
.text:0040E946            lea    edx, [regData+REG_DATA.snCode2]
.text:0040E94C            push   edx         ; s
.text:0040E94D            call   _strlen
.text:0040E952            pop    ecx
.text:0040E953            mov    ecx, eax      ; len
.text:0040E955            lea    edx, [regData+REG_DATA.snCode2] ; buffer
.text:0040E95B            mov    eax, ebx      ; crc32
.text:0040E95D            call   NCrc32
.text:0040E962            mov    ebx, eax
.text:0040E964            lea    ecx, [regData+REG_DATA.snCode3]
.text:0040E96A            push   ecx         ; s
.text:0040E96B            call   _strlen
.text:0040E970            pop    ecx
.text:0040E971            mov    ecx, eax      ; len
.text:0040E973            lea    edx, [regData+REG_DATA.snCode3] ; buffer
.text:0040E979            mov    eax, ebx      ; crc32
.text:0040E97B            call   NCrc32
.text:0040E980            mov    ebx, eax
.text:0040E982            lea    eax, [regData+REG_DATA.snCode4]
.text:0040E988            push   eax         ; s
.text:0040E989            call   _strlen
.text:0040E98E            pop    ecx
.text:0040E98F            mov    ecx, eax      ; len
.text:0040E991            lea    edx, [regData+REG_DATA.snCode4] ; buffer
.text:0040E997            mov    eax, ebx      ; crc32
.text:0040E999            call   NCrc32
.text:0040E99E            mov    ebx, eax
.text:0040E9A0            cmp    ebx, [regData+REG_DATA.crc32] ; [Check5] licType + username + snCode1/2/3/4 的~CRC32值,
                                              ; 和regData.crc32比较, 不相等注册失败!
.text:0040E9A6            jz    short loc_40E9AF
.text:0040E9A8            xor    eax, eax
.text:0040E9AA            jmp    REG_FAIL_EXIT
.text:0040E9AF ; ---------------------------------------------------------------------------

到此基本了解了文件的rarreg.key的结构, 下一篇介绍黑名单部分.
Heaven is a place nearby so I won't be so far away and if you try and look for me maybe you'll find me someday

TOP

发新话题