Nisy 发表于 2006-12-28 15:51:57

[转自一萧烟雨]WinRAR 3.62注册算法 - CRC校验 + 黑名单 + SHA1 hash算法

标题: WinRAR 3.62注册算法 - CRC校验 + 黑名单 + SHA1 hash算法
发表于: 2006-12-24 12:02   BY:icytear
--------------------------------------------------------------------------------

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   , 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 ; 如果硬编码串的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 , 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,
.text:00410758               dec   esi
.text:00410759               mov   cl, dl
.text:0041075B               shr   ebx, 8
.text:0041075E               mov   edx,
.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,       ; 每次循环计算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,
.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,
.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,
.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,
.text:004107B0               xor   edx, ebx
.text:004107B2               xor   ecx, ecx
.text:004107B4               mov   ebx, edx
.text:004107B6               xor   ebx,
.text:004107B9               add   eax, 8
.text:004107BC               mov   cl, bl
.text:004107BE               shr   ebx, 8
.text:004107C1               mov   edx,
.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,
.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,
.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,
.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,
.text:00410800               and   ecx, 0FFh
.text:00410806               shr   ebx, 8
.text:00410809               mov   ecx,
.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 == 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>>8);
                crc32 = crc32Table ^(crc32>>8);
                crc32 = crc32Table ^(crc32>>8);
                crc32 = crc32Table ^(crc32>>8);

                crc32 ^= *(unsigned int*)(buff+4);
                crc32 = crc32Table ^(crc32>>8);
                crc32 = crc32Table ^(crc32>>8);
                crc32 = crc32Table ^(crc32>>8);
                crc32 = crc32Table ^(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;
      char licType;
      char snCode1;
      char snCode4;
      char snCode3;
      char snCode2;
      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
.text:0040E710               cmp   edx, 23h
.text:0040E713               jnz   short loc_40E729
.text:0040E715 loc_40E715:                           ; CODE XREF: check_regdata+44j
.text:0040E715               lea   edx,
.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,
.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,
.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,
.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,
.text:0040E781               mov   edx, 2
.text:0040E786               call    rar_atoi                        ;rar实现的把指定长度的字符串转换位整型
.text:0040E78B               mov   , eax
.text:0040E78F               lea   eax,
.text:0040E793               mov   edx, 3
.text:0040E798               call    rar_atoi
.text:0040E79D               mov   edi, eax
.text:0040E79F               lea   eax,
.text:0040E7A3               mov   edx, 3
.text:0040E7A8               call    rar_atoi
.text:0040E7AD               mov   ebp, eax
.text:0040E7AF               lea   eax,
.text:0040E7B3               mov   edx, 2
.text:0040E7B8               call    rar_atoi
.text:0040E7BD               mov   , eax
.text:0040E7C1               cmp   edi, 100h       ; 比较[注册字符串2]的长度
.text:0040E7C7               jg      short loc_40E7D1 ;
.text:0040E7C7
.text:0040E7C9               cmp   ebp, 100h       ; 比较[注册字符串3]的长度
.text:0040E7CF               jle   short loc_40E7D8
.text:0040E7D1
.text:0040E7D1 loc_40E7D1:                           ; CODE XREF: check_regdata+103j
.text:0040E7D1               xor   eax, eax      ;
.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,
.text:0040E88C               push    ; snCode1_len
.text:0040E890               push    ebx             ; src
.text:0040E891               lea   eax,
.text:0040E897               push    eax             ; dest
.text:0040E898               call    _strncpy
.text:0040E89D               add   esp, 0Ch
.text:0040E8A0               add   ebx,
.text:0040E8A4               push    edi             ; snCode2_len
.text:0040E8A5               push    ebx             ; src
.text:0040E8A6               lea   edx,
.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,
.text:0040E8BF               push    ecx             ; dest
.text:0040E8C0               call    _strncpy
.text:0040E8C5               add   esp, 0Ch
.text:0040E8C8               add   ebx, ebp
.text:0040E8CA               push    ; maxlen
.text:0040E8CE               push    ebx             ; src
.text:0040E8CF               lea   eax,
.text:0040E8D5               push    eax             ; dest
.text:0040E8D6               call    _strncpy
.text:0040E8DB               add   esp, 0Ch
.text:0040E8DE               add   ebx,
.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   , 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,
.text:0040E8FB               push    edx             ; s
.text:0040E8FC               call    _strlen
.text:0040E901               pop   ecx
.text:0040E902               mov   ecx, eax      ; len
.text:0040E904               lea   edx, ; 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,
.text:0040E92E               push    eax             ; s
.text:0040E92F               call    _strlen
.text:0040E934               pop   ecx
.text:0040E935               mov   ecx, eax      ; len
.text:0040E937               lea   edx, ; buffer
.text:0040E93D               mov   eax, ebx      ; crc32
.text:0040E93F               call    NCrc32
.text:0040E944               mov   ebx, eax
.text:0040E946               lea   edx,
.text:0040E94C               push    edx             ; s
.text:0040E94D               call    _strlen
.text:0040E952               pop   ecx
.text:0040E953               mov   ecx, eax      ; len
.text:0040E955               lea   edx, ; buffer
.text:0040E95B               mov   eax, ebx      ; crc32
.text:0040E95D               call    NCrc32
.text:0040E962               mov   ebx, eax
.text:0040E964               lea   ecx,
.text:0040E96A               push    ecx             ; s
.text:0040E96B               call    _strlen
.text:0040E970               pop   ecx
.text:0040E971               mov   ecx, eax      ; len
.text:0040E973               lea   edx, ; buffer
.text:0040E979               mov   eax, ebx      ; crc32
.text:0040E97B               call    NCrc32
.text:0040E980               mov   ebx, eax
.text:0040E982               lea   eax,
.text:0040E988               push    eax             ; s
.text:0040E989               call    _strlen
.text:0040E98E               pop   ecx
.text:0040E98F               mov   ecx, eax      ; len
.text:0040E991               lea   edx, ; buffer
.text:0040E997               mov   eax, ebx      ; crc32
.text:0040E999               call    NCrc32
.text:0040E99E               mov   ebx, eax
.text:0040E9A0               cmp   ebx, ; 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的结构, 下一篇介绍黑名单部分.

野猫III 发表于 2006-12-28 20:01:17

牛人的一个算法。。。

wan 发表于 2006-12-29 09:34:55

非一般的牛人啊...

≡ 一 蓑 烟 雨 ≡ 哦...

internetzwm 发表于 2006-12-29 13:26:14

好强啊,谢谢

xuhw 发表于 2006-12-29 13:57:45

真是老牛了/find

wyh1983 发表于 2006-12-30 01:29:00

哎,这个更加晕了,

现在WINRAR越来越厉害了,,,,,,,深

8568864_cn 发表于 2006-12-30 11:50:00

x谢谢分享致辞了,呵呵

Lancia 发表于 2006-12-31 16:18:53

谢谢分享支持支持!!!

have 发表于 2007-1-5 11:39:17

真是强,望尘莫及哦,资料收藏了,以后慢慢学

月之精灵 发表于 2007-1-5 18:05:43

厉害,看得不是很白,
页: [1] 2
查看完整版本: [转自一萧烟雨]WinRAR 3.62注册算法 - CRC校验 + 黑名单 + SHA1 hash算法