Contents

FastStone Image Viewer注册算法分析+KeyGen

文章发布至看雪《FastStone Image Viewer注册算法分析+KeyGen》

前言

FastStone Image Viewer是一款老牌看图软件,附带编辑/转换图片功能,软件针对个人用户免费,适用于Windows,官方下载:https://www.faststone.org/FSViewerDetail.htm

出于兴趣研究,本文以最新版本v7.5为例,使用IDA和Windbg分析注册算法并实现KeyGen。:)

找验证点

首先查看区段/导入表比较完整,看上去没加壳,资源发现RCData,可能是Delphi/BC++开发。PE头里DllCharacteristics是0(无IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE),不支持ASLR(后面用IDA/Windbg不用ReBase,直接定位,方便分析)。

/posts/2020/faststone-keygen/resources/section.png

接下来开始分析注册码判断方式,本文从输入框跟踪用户名引用入手(以前写的《AudioSrv音频服务故障》有说如何找窗口处理过程)。首先运行程序到注册窗口,Windbg附加,输入假码,查看栈回溯。观察之后没有太明显的地方,查看注册框Edit句柄,bp GetWindowTextW条件断点未断下,随后bp NtUserMessageCall拦截WM_GETTEXT,断点命中,查看对应参数获取到用户名,ba r1下内存访问断点,跟踪用户名拷贝,尝试设置多次断点验证,最后定位到关键函数sub_72F248。

消息参数:
0x000D (13) WM_GETTEXT

函数原型:
NtStatus (*NtUserMessageCall)(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi);

调试前注意函数调用约定,EAX=param1,EDX=param2,ECX=param3,其它参数栈传递。(ADC)
https://en.wikipedia.org/wiki/X86_calling_conventions#cite_note-14

算法分析

IDA加载完毕,Shift+F5打开signature,应用bcb5rt(CBuilder 5 runtime),识别到3000多个函数。sub_72F248函数代码如下:

CODE:0072F282                 call    @Controls@TControl@GetText$qqrv ; Controls::TControl::GetText(void)
CODE:0072F287                 mov     eax, [ebp+var_18]
CODE:0072F28A                 lea     edx, [ebp+name] ; 用户名
CODE:0072F28D                 call    @Sysutils@Trim$qqrx17System@AnsiString ; Sysutils::Trim(System::AnsiString)
CODE:0072F292                 lea     edx, [ebp+var_20]
CODE:0072F295                 mov     eax, [esi+308h]
CODE:0072F29B                 call    @Controls@TControl@GetText$qqrv ; Controls::TControl::GetText(void)
CODE:0072F2A0                 mov     eax, [ebp+var_20]
CODE:0072F2A3                 lea     edx, [ebp+var_1C]
CODE:0072F2A6                 call    @Sysutils@Trim$qqrx17System@AnsiString ; Sysutils::Trim(System::AnsiString)
CODE:0072F2AB                 mov     eax, [ebp+var_1C] ; 注册码
CODE:0072F2AE                 lea     edx, [ebp+regcode]
CODE:0072F2B1                 call    @Sysutils@UpperCase$qqrx17System@AnsiString ; Sysutils::UpperCase(System::AnsiString)
CODE:0072F2B6                 lea     eax, [ebp+var_10] ; 字符串变量 = '' clear
CODE:0072F2B9                 call    @System@@LStrClr$qqrr17System@AnsiString ; System::__linkproc__ LStrClr(System::AnsiString &)
CODE:0072F2BE                 mov     eax, [ebp+regcode]
CODE:0072F2C1                 call    strlen          ; CBuilder 5 runtime
....
CODE:0072F2FC                 mov     eax, [ebp+var_10]
CODE:0072F2FF                 call    strlen          ; CBuilder 5 runtime
CODE:0072F304                 cmp     eax, 5          ; 5个一组
CODE:0072F307                 jz      short loc_72F323
CODE:0072F309                 mov     eax, [ebp+var_10]
CODE:0072F30C                 call    strlen          ; CBuilder 5 runtime
CODE:0072F311                 cmp     eax, 0Bh
CODE:0072F314                 jz      short loc_72F323
CODE:0072F316                 mov     eax, [ebp+var_10]
CODE:0072F319                 call    strlen          ; CBuilder 5 runtime
CODE:0072F31E                 cmp     eax, 11h
CODE:0072F321                 jnz     short loc_72F330
....
CODE:0072F326                 mov     edx, offset dword_72F70C ; strcat(regcode, '-')
CODE:0072F32B                 call    @System@@LStrCat$qqrv ; System::__linkproc__ LStrCat(void)

首先检查注册码是否满足23位,由4组5个大写字母组成,中间横杠分隔,校验时会过滤掉分隔符,最后拆分成8位/8位/4位,(如:ABCDE-FGHIJ-KLMNO-PQRST => ABCDEFG HIJKLMN OPQR),下面观察两个关键函数。

if ( sub_72E770((int)v3, name, code, 0) && sub_72EBFC((int)v3, name, code, 0)
    || sub_72E770((int)v3, name, code, 1) && sub_72EBFC((int)v3, name, code, 1) ) {
        // 成功
    } else {
        // 失败
    }

由此可知满足其中一种条件即可注册成功,注意函数第四个参数有0和1,具体区别稍后说明。接下来分析sub_72E770和sub_72EBFC这两个函数。

注:使用Windbg调试查看参数及返回值,有些简单的字符串处理,观察数据就能看出来,做个标记即可,就不浪费时间分析了。

sub_72E770函数:

System::__linkproc__ LStrAddRef(a2);
System::__linkproc__ LStrAddRef(v50);
...
// 大小写转换,StrTrim,StrCat等操作
Sysutils::UpperCase(v51, (_BYTE **)&username);
Sysutils::Trim(v50, &v42);
Sysutils::UpperCase(v42, (_BYTE **)&v43);
...
System::__linkproc__ LStrCat(&v47, v37);
...

// 调试可知,这个函数是字符串拼接
sub_405004(
    &v35,
    3,
    v12,
    "me4T6cBLV",
    v48,
    "CpCwxrvCJZ30pKLu8Svxjhnhut437glCpofVssnFeBh2G0ekUq4VcxFintMix52vL0iJNbdtWqHPyeumkDUC+4AaoSX+xpl56Esonk4=");

// sub_71FB90 需要重点关注
sub_71FB90(*(_BYTE **)(v52 + 788), v35, (int)off_722720); //调用1

// 参数4便是上面说的0 和 1,区别在于连接的字符串不同,生成注册码时选一种即可。
if ( a4 )
{
    pop_window(&v32, v48);
    v20 = v32;
    v19 = *(void **)(*off_98A548[0] + 0x7A8);
    pop_window(&v31, v47);
    v18 = v31;
    System::__linkproc__ WStrCatN(&v33, 3);     //连接字符串
    System::__linkproc__ LStrFromWStr(&v34, v33);   //unicode字符转换
    sub_71FB90(*(_BYTE **)(v52 + 784), v34, (int)off_723D50);  //调用2
}
else
{
    pop_window(&v28, v48);
    v20 = v28;
    v19 = *(void **)(*off_98A548[0] + 0x7A4);
    pop_window(&v27, v47);
    v18 = v27;
    System::__linkproc__ WStrCatN(&v29, 3); //连接字符串
    System::__linkproc__ LStrFromWStr(&v30, v29); //unicode字符转换
    sub_71FB90(*(_BYTE **)(v52 + 784), v30, (int)off_723D50); //调用3
}

发现sub_71FB90函数由3处调用,跟进该函数:

CODE:0071FBE3                 call    dword ptr [edx+40h] ; edx是虚表,hash初始化,obj->init() => sha*_init
CODE:0071FBE6                 mov     edx, [ebp+var_4]
CODE:0071FBE9                 mov     eax, edi
CODE:0071FBEB                 call    sub_71FA88
CODE:0071FBF0                 mov     edx, esi
CODE:0071FBF2                 mov     eax, edi
CODE:0071FBF4                 mov     ecx, [eax]
CODE:0071FBF6                 call    dword ptr [ecx+44h] ; 计算hash值,obj->final() => sha*_final
CODE:0071FBF9                 mov     eax, edi
CODE:0071FBFB                 call    sub_403E2C
CODE:0071FC00                 mov     eax, ebx
CODE:0071FC02                 mov     edx, [eax]
CODE:0071FC04                 call    dword ptr [edx+38h] ; 获取hash值位数,obj->hash_bits() => sha*_bits
CODE:0071FC07                 mov     edi, eax
CODE:0071FC09                 mov     eax, [ebp+var_8]  ; 参数1
CODE:0071FC0C                 call    dword ptr [eax+38h] ; 由此可知,参数1也是也是虚表,obj2->key_bits(),返回其函数长度0xA0(160)
CODE:0071FC0F                 cmp     edi, eax  ; hash值位数是否大于等于key_bits(160)
CODE:0071FC11                 jge     short loc_71FC29
CODE:0071FC13                 push    0
CODE:0071FC15                 mov     eax, ebx
CODE:0071FC17                 mov     edx, [eax]
CODE:0071FC19                 call    dword ptr [edx+38h] ; sha1 hash值作为输入
CODE:0071FC1C                 mov     ecx, eax
CODE:0071FC1E                 mov     edx, esi
CODE:0071FC20                 mov     eax, ebx
CODE:0071FC22                 mov     ebx, [eax]
CODE:0071FC24                 call    dword ptr [ebx+40h] ;密钥初始化
CODE:0071FC27                 jmp     short loc_71FC3C
CODE:0071FC29 ; ---------------------------------------------------------------------------
CODE:0071FC29
CODE:0071FC29 loc_71FC29:                             ; CODE XREF: sub_71FB90+81↑j
CODE:0071FC29                 push    0
CODE:0071FC2B                 mov     eax, [ebp+var_8]
CODE:0071FC2E                 call    dword ptr [eax+38h] ;sha512 hash值作为输入
CODE:0071FC31                 mov     ecx, eax
CODE:0071FC33                 mov     edx, esi
CODE:0071FC35                 mov     eax, ebx
CODE:0071FC37                 mov     ebx, [eax]
CODE:0071FC39                 call    dword ptr [ebx+40h] ; 密钥初始化
CODE:0071FC3C
CODE:0071FC3C loc_71FC3C:                             ; CODE XREF: sub_71FB90+97↑j
CODE:0071FC3C                 mov     eax, [ebp+var_8]
CODE:0071FC3F                 call    dword ptr [eax+38h]

上面的obj->init()由常量特征可知是SHA1和SHA512,对应的hash长度也分别是0x14(160位) 和 0x40(512位)

CODE:00723A04 sha1_init       proc near               ; DATA XREF: CODE:00722760↑o
CODE:0  0723A04                 push    ebx
CODE:00723A05                 mov     ebx, eax
CODE:00723A07                 mov     eax, ebx
CODE:00723A09                 mov     edx, [eax]
CODE:00723A0B                 call    dword ptr [edx+48h]
CODE:00723A0E                 mov     dword ptr [ebx+40h], 67452301h
CODE:00723A15                 mov     dword ptr [ebx+44h], 0EFCDAB89h
CODE:00723A1C                 mov     dword ptr [ebx+48h], 98BADCFEh
CODE:00723A23                 mov     dword ptr [ebx+4Ch], 10325476h
CODE:00723A2A                 mov     dword ptr [ebx+50h], 0C3D2E1F0h
CODE:00723A31                 mov     byte ptr [ebx+30h], 1
CODE:00723A35                 pop     ebx
CODE:00723A36                 retn
CODE:00723A36 sha1_init       end

CODE:0072E14C sha512_init     proc near               ; DATA XREF: CODE:00723D90↑o
CODE:0072E14C                 push    ebx
CODE:0072E14D                 mov     ebx, eax
CODE:0072E14F                 mov     eax, ebx
CODE:0072E151                 mov     edx, [eax]
CODE:0072E153                 call    dword ptr [edx+48h]
CODE:0072E156                 mov     dword ptr [ebx+50h], 0F3BCC908h
CODE:0072E15D                 mov     dword ptr [ebx+54h], 6A09E667h
CODE:0072E164                 mov     dword ptr [ebx+58h], 84CAA73Bh
CODE:0072E16B                 mov     dword ptr [ebx+5Ch], 0BB67AE85h
CODE:0072E172                 mov     dword ptr [ebx+60h], 0FE94F82Bh
CODE:0072E179                 mov     dword ptr [ebx+64h], 3C6EF372h
CODE:0072E180                 mov     dword ptr [ebx+68h], 5F1D36F1h
CODE:0072E187                 mov     dword ptr [ebx+6Ch], 0A54FF53Ah
CODE:0072E18E                 mov     dword ptr [ebx+70h], 0ADE682D1h
CODE:0072E195                 mov     dword ptr [ebx+74h], 510E527Fh
CODE:0072E19C                 mov     dword ptr [ebx+78h], 2B3E6C1Fh
CODE:0072E1A3                 mov     dword ptr [ebx+7Ch], 9B05688Ch
CODE:0072E1AA                 mov     dword ptr [ebx+80h], 0FB41BD6Bh
CODE:0072E1B4                 mov     dword ptr [ebx+84h], 1F83D9ABh
CODE:0072E1BE                 mov     dword ptr [ebx+88h], 137E2179h
CODE:0072E1C8                 mov     dword ptr [ebx+8Ch], 5BE0CD19h
CODE:0072E1D2                 mov     byte ptr [ebx+30h], 1
CODE:0072E1D6                 pop     ebx
CODE:0072E1D7                 retn
CODE:0072E1D7 sha512_init     endp

获取密钥位数

CODE:0071FC0C                 call    dword ptr [eax+38h] ; 这里获取密钥位数,可知有448位和128位

接下来则是密钥扩展,首先看448位的情况,跟踪下面的函数发现Blowfish的Pbox和Sbox,密钥扩展算法以及密钥长度448都满足标准Blowfish。

0071fc24 ff5340          call    dword ptr [ebx+40h]  ds:002b:00721d28=00720244
....子函数....
  00720264 8b4dfc          mov     ecx,dword ptr [ebp-4]
  00720267 8bc3            mov     eax,ebx
  00720269 8b38            mov     edi,dword ptr [eax]
  0072026b ff575c          call    dword ptr [edi+5Ch]  ds:002b:00720d7c=00720f18 //密钥扩展
  ....子函数....
    CODE:00720F30                 lea     edx, [esi+48h]
    CODE:00720F33                 mov     eax, offset unk_987C04 ; Blowfish P-Box
    CODE:00720F38                 mov     ecx, 1000h  ;4 * 8 * 32 = 1024 = 0x1000
    CODE:00720F3D                 call    memcpy_xxx
    CODE:00720F42                 lea     edx, [esi+1048h]
    CODE:00720F48                 mov     eax, 987BBCh  ; Blowfish S-Box
    CODE:00720F4D                 mov     ecx, 48h    ; 18 * 4 = 72 = 0x48
    CODE:00720F52                 call    memcpy_xxx

S-Box
0:000> dd 00987bbc
00987bbc  243f6a88 85a308d3 13198a2e 03707344
00987bcc  a4093822 299f31d0 082efa98 ec4e6c89
00987bdc  452821e6 38d01377 be5466cf 34e90c6c
00987bec  c0ac29b7 c97c50dd 3f84d5b5 b5470917
00987bfc  9216d5d9 8979fb1b 

P-Box
0:000> dd 00987c04
00987c04  d1310ba6 98dfb5ac 2ffd72db d01adfb7
00987c14  b8e1afed 6a267e96 ba7c9045 f12c7f99
00987c24  24a19947 b3916cf7 0801f2e2 858efc16
00987c34  636920d8 71574e69 a458fea3 f4933d7e
00987c44  0d95748f 728eb658 718bcd58 82154aee
00987c54  7b54a41d c25a59b5 9c30d539 2af26013
00987c64  c5d1b023 286085f0 ca417918 b8db38ef
00987c74  8e79dcb0 603a180e 6c9e0e8b b01e8a3e
....

继续看128位的情况, 此时没有特征Box,但是128位密钥/操作长度是word以及shl ecx,9 shr edi,7基本可以确定是IDEA。(后面从虚表里也找到了输出算法名的函数,应该是某加密库)。

0071fc24 ff5340          call    dword ptr [ebx+40h]  ds:002b:00721d28=00720244
....子函数....
  00720262 8bd7            mov     edx,edi
  00720264 8b4dfc          mov     ecx,dword ptr [ebp-4]
  00720267 8bc3            mov     eax,ebx
  00720269 8b38            mov     edi,dword ptr [eax]
  0072026b ff575c          call    dword ptr [edi+5Ch]  ds:002b:00721d44=00721f94 //密钥扩展
  ...子函数....
    00721fe0 8bc6            mov     eax,esi
    00721fe2 48              dec     eax
    00721fe3 c1e003          shl     eax,3
    00721fe6 668b4c434a      mov     cx,word ptr [ebx+eax*2+4Ah]
    00721feb c1e109          shl     ecx,9
    00721fee 0fb77c434c      movzx   edi,word ptr [ebx+eax*2+4Ch]
    00721ff3 c1ef07          shr     edi,7
    00721ff6 660bcf          or      cx,di
    00721ff9 8bd6            mov     edx,esi
    00721ffb c1e203          shl     edx,3
    00721ffe 66894c5348      mov     word ptr [ebx+edx*2+48h],cx
    00722003 668b4c434c      mov     cx,word ptr [ebx+eax*2+4Ch]
    00722008 c1e109          shl     ecx,9
    0072200b 0fb77c434e      movzx   edi,word ptr [ebx+eax*2+4Eh]
    00722010 c1ef07          shr     edi,7
    00722013 660bcf          or      cx,di
    00722016 66894c534a      mov     word ptr [ebx+edx*2+4Ah],cx
    0072201b 668b4c434e      mov     cx,word ptr [ebx+eax*2+4Eh]
    00722020 c1e109          shl     ecx,9
    00722023 0fb77c4350      movzx   edi,word ptr [ebx+eax*2+50h]
    00722028 c1ef07          shr     edi,7
    0072202b 660bcf          or      cx,di
    0072202e 66894c534c      mov     word ptr [ebx+edx*2+4Ch],cx
    00722033 668b4c4350      mov     cx,word ptr [ebx+eax*2+50h]
    00722038 c1e109          shl     ecx,9
    0072203b 0fb77c4352      movzx   edi,word ptr [ebx+eax*2+52h]
    00722040 c1ef07          shr     edi,7
    00722043 660bcf          or      cx,di
    00722046 66894c534e      mov     word ptr [ebx+edx*2+4Eh],cx
    0072204b 668b4c4352      mov     cx,word ptr [ebx+eax*2+52h]

加密函数:

CODE:0072EA41                 mov     eax, [ebp+var_4]
CODE:0072EA44                 mov     eax, [eax+314h]
CODE:0072EA4A                 mov     edx, [ebp+var_18]
CODE:0072EA4D                 mov     ebx, [eax]
CODE:0072EA4F                 call    dword ptr [ebx+54h] ; Blowfish
CODE:0072EA52                 lea     ecx, [ebp+var_6C]
CODE:0072EA55                 mov     eax, [ebp+var_4]
CODE:0072EA58                 mov     eax, [eax+310h]
CODE:0072EA5E                 mov     edx, [ebp+var_20]
CODE:0072EA61                 mov     ebx, [eax]
CODE:0072EA63                 call    dword ptr [ebx+54h] ; IDEA

跟进ebx+54h加密函数

CODE:0071FEFE                 call    dword ptr [esi+7Ch] ;加密封装函数
CODE:0071FF01                 lea     edx, [ebp+var_4]
CODE:0071FF04                 mov     eax, [ebx]
CODE:0071FF06                 call    sub_71F2E4  ;Base64

esi+7Ch加密封装函数,初始明文都是8个00(Blowfish和IDEA分组块都是8字节)

CODE:00720602                 mov     [ebp+var_8], edx
CODE:00720605                 mov     esi, [ebp+var_4]  ;明文(例如:用户名和注册码交叉组成的第一段数据)
CODE:00720608                 mov     eax, [ebp+arg_0]  ;明文长度
CODE:0072060B                 test    eax, eax
CODE:0072060D                 jbe     short loc_720647
CODE:0072060F                 mov     [ebp+var_14], eax
CODE:00720612
CODE:00720612 loc_720612:                             ; CODE XREF: sub_7205D8+6D↓j
CODE:00720612                 lea     ecx, [ebp+var_10]
CODE:00720615                 lea     edx, [ebx+40h]
CODE:00720618                 mov     eax, ebx
CODE:0072061A                 mov     edi, [eax]
CODE:0072061C                 call    dword ptr [edi+6Ch] ;Blowfish/IDEA加密明文
CODE:0072061F                 mov     eax, [ebp+var_8]
CODE:00720622                 mov     al, [eax] ; 取加密后的首字节
CODE:00720624                 xor     al, [ebp+var_10] ; 异或明文
CODE:00720627                 mov     [esi], al   ; 结果暂存
CODE:00720629                 lea     edx, [ebx+40h]
CODE:0072062C                 lea     eax, [ebx+41h]
CODE:0072062F                 mov     ecx, 7
CODE:00720634                 call    memcpy_xxx  ; 左移1个字节
CODE:00720639                 mov     al, [esi]   ; 取出暂存字节
CODE:0072063B                 mov     [ebx+47h], al ;将暂存字节替换最右边的数据
CODE:0072063E                 inc     [ebp+var_8]
CODE:00720641                 inc     esi
CODE:00720642                 dec     [ebp+var_14]
CODE:00720645                 jnz     short loc_720612 ;循环处理明文

最后CODE:0072EA7C

CODE:0072EA7C
v13 = strlen(v45);
if ( v13 > 0 )
{
  v14 = 1;
  do
  {
    // 第二段注册码,取最终Base64的大写字符,8个
    if ( *(_BYTE *)(v45 + v14 - 1) >= 0x41u && *(_BYTE *)(v45 + v14 - 1) <= 0x5Au && strlen(v44) < 8 )
    {
      v15 = *(_BYTE *)(v45 + v14 - 1);
      unknown_libname_17((int)&v25);
      System::__linkproc__ LStrCat(&v44, v25);
    }
    ++v14;
    --v13;
  }
  while ( v13 );
}
System::__linkproc__ LStrCopy(v43, 9, 8, (int)&v24);
System::__linkproc__ LStrCmp(v44, v24); //检查第二段注册码是否合法
if ( v16 )
  v49 = 1;	//验证通过

到此第一个注册码验证函数sub_72E770大致分析完,现在来看第二个验证函数sub_72EBFC。

函数2和函数1使用的对象和加密函数基本一致,此处不再赘述,直接上代码。

sub_71FB90(
	*(_BYTE **)(v54 + 0x314),
	(int)"09232849248398340903834873297239340547237623242043324398489390309284343843223493299435",
	(int)sha512_class);	//初始化Blowfish密钥
if ( a4 )	// 参数4是0还是1
{
	pop_window(&v35, v50);
	v12 = *(_DWORD *)(*off_98A548[0] + 0x7A8);
	pop_window(&v34, v49);
	System::__linkproc__ WStrCatN(&v36, 3);
	System::__linkproc__ LStrFromWStr(&v37, v36);
	sub_71FB90(*(_BYTE **)(v54 + 0x310), v37, (int)sha1_class);	//初始化IDEA密钥
}
else
{
	pop_window(&v31, v50);
	v13 = *(_DWORD *)(*off_98A548[0] + 0x7A4);
	pop_window(&v30, v49);
	System::__linkproc__ WStrCatN(&v32, 3);
	System::__linkproc__ LStrFromWStr(&v33, v32);
	sub_71FB90(*(_BYTE **)(v54 + 0x310), v33, (int)sha1_class); //初始化IDEA密钥
}
....
v14 = *v50 - 0x32;		// 循环次数 等于 首字节和'2'的ASCII值之差
if ( v14 >= 0 )	// 循环IDEA加密
{
	v15 = v14 + 1;
	do
	{
	(*(void (__fastcall **)(_DWORD, int, int *))(**(_DWORD **)(v54 + 0x310) + 84))(
		*(_DWORD *)(v54 + 0x310),
		v49,
		&v47);
	--v15;
	}
	while ( v15 );
}
// Blowfish加密
(*(void (__fastcall **)(_DWORD, int, int *))(**(_DWORD **)(v54 + 0x314) + 84))(*(_DWORD *)(v54 + 0x314), v47, &v29);
......
v16 = strlen(v47);
if ( v16 > 0 )
{
	v17 = 1;
	do
	{
		//从加密后的Base64字符串中提取4个大写字符
		if ( *(_BYTE *)(v47 + v17 - 1) >= 0x41u && *(_BYTE *)(v47 + v17 - 1) <= 0x5Au && strlen(v46) < 4 )
		{
			v18 = *(_BYTE *)(v47 + v17 - 1);
			unknown_libname_17((int)&v28);
			System::__linkproc__ LStrCat(&v46, v28);
		}
	++v17;
	--v16;
	}
	while ( v16 );
}
System::__linkproc__ LStrCopy(v45, 17, 4, (int)&v27);
System::__linkproc__ LStrCmp(v46, v27);	//比较第三段注册码是否合法
if ( v19 )
	v51 = 1;	//验证通过

KeyGen实现

在线运行KeyGen

/posts/2020/faststone-keygen/resources/regok.png

最后贴出关键代码,完整代码见附件。

const (
	SingleUserLicense         = 1
	MultipleUserLicense       = 2
	CorporateSiteLicense      = 3
	CorporateWorldwideLicense = 4
)

// 自定义生成
var (
	UserName     = "pediy"                   //用户名
	LicenseCount = 10                        //license组数
	LicenseType  = CorporateWorldwideLicense //license类型
)

func main() {
	fmt.Println(UserName)
	for i := 0; i < LicenseCount; {
		code := Keygen(UserName, LicenseType)
		// 检查长度合法
		if len(code) == 23 {
			fmt.Println(code)
			i++
		}
	}
}

func Keygen(user string, lictype int) string {
	// code1 = 随机8位大写字符
	code1 := GenerateCode1()

	// 检查License(简单粗暴)
	if !VerifyLicense(lictype, code1) {
		return ""
	}

	// 合并用户名和code1
	merged := MergeKeyCode(user, code1)

	// key = code1 + code2 + code3
	v1 := []byte(code1)
	v2 := []byte(merged)
	key := code1 + GenerateCode2(v1, v2) + GenerateCode3(v1, v2)

	// 分4段输出
	for i := 5; i < len(key); i += 6 {
		key = key[:i] + "-" + key[i:]
	}
	return key
}

// 互相交叉
func MergeKeyCode(user, code string) string {
	user = strings.ToUpper(user)
	var count int
	if count = len(user); count > 8 {
		count = 8
	}
	var merged string
	var i int
	for i = 0; i < count; i++ {
		merged += user[i:i+1] + code[i:i+1]
	}
	if i+1 < len(user) {
		merged += user[i:]
	} else {
		merged += code[i:]
	}
	return merged
}

// 检查License格式
func VerifyLicense(lictype int, code1 string) bool {
	temp := []rune(code1)
	x1 := int8(temp[3:4][0]) - int8('M')
	x2 := int8(temp[7:8][0]) - int8('D')
	x3 := int8(temp[5:6][0]) - int8('I')
	x4 := int8(temp[1:2][0]) - int8('O')
	s := fmt.Sprintf("%d%d%d%d", x1, x2, x3, x4)
	nums, _ := strconv.ParseInt(s, 10, 0)
	switch lictype {
	case SingleUserLicense:
		if nums > 1 {
			return false
		}
	case MultipleUserLicense:
		if nums < 1 || nums > 4999 {
			return false
		}
	case CorporateSiteLicense:
		if nums != 4999 {
			return false
		}
	case CorporateWorldwideLicense:
		if nums < 50000 {
			return false
		}
	}
	return true
}

// 第一段注册码,随机生成8个大写字符
func GenerateCode1() string {
	code1 := func(n int) string {
		letters := []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
		b := make([]rune, n)
		for i := range b {
			b[i] = letters[rand.Intn(len(letters))]
		}
		return string(b)
	}(8)
	return code1
}

// 第二段注册码,8个大写字符
func GenerateCode2(plain1, plain2 []byte) string {
	c1 := []byte("me4T6cBLV")
	c2 := []byte("CpCwxrvCJZ30pKLu8Svxjhnhut437glCpofVssnFeBh2G0ekUq4VcxFintMix52vL0iJNbdtWqHPyeumkDUC+4AaoSX+xpl56Esonk4=")
	c3 := append(c1, plain1...)
	c3 = append(c3, c2...)
	k1 := KSHA1(c3)

	plain := make([]byte, len(plain2))
	copy(plain, plain2)
	plain = KBlowfish(plain, k1, make([]byte, 8))
	b64 := KBase64(plain)

	pp := append(plain1, []byte("96332")...)
	pp = append(pp, plain2...)
	k2 := KSHA512(pp)[:16]
	cc := KIDEA(b64, k2, make([]byte, 8))
	b64 = KBase64(cc)
	var lic string
	for _, v := range b64 {
		if unicode.IsUpper(rune(v)) {
			lic += string(v)
			if len(lic) >= 8 {
				break
			}
		}
	}
	return lic
}

// 生成第3段注册码,4个大写字符
func GenerateCode3(plain1, plain2 []byte) string {
	temp := append(plain1, []byte("96332")...)
	temp = append(temp, plain2...)
	k := KSHA1(temp)[:16]

	inter := make([]byte, len(plain2))
	round := plain1[0] - '2'
	initial := make([]byte, 8)
	for i := uint8(0); i <= round; i++ {
		copy(inter, plain2)
		KIDEA(inter, k, initial)
	}
	inter = KBase64(inter)

	p1 := []byte("09232849248398340903834873297239340547237623242043324398489390309284343843223493299435")
	hash := KSHA512(p1)[:56]

	inter = KBlowfish(inter, hash, make([]byte, 8))
	b64 := KBase64(inter)

	var lic string
	for _, v := range b64 {
		if unicode.IsUpper(rune(v)) {
			lic += string(v)
		}
		if len(lic) == 4 {
			break
		}
	}
	return lic
}

type EncryptRoutine func([]byte, []byte) []byte

func KAbstractEncrypt(plain, key, initial []byte, routine EncryptRoutine) []byte {
	reset := true
	for _, x := range initial {
		if x != 0 {
			reset = false
			break
		}
	}
	var c []byte
	if reset == true {
		c = routine(initial, key)
	} else {
		c = initial
	}
	// 循环加密/移位/异或明文
	for i := 0; i < len(plain); i++ {
		ec := routine(c, key)
		copy(c, c[1:])
		v := plain[i] ^ ec[0]
		c[7] = v
		plain[i] = v
	}
	copy(initial, c)
	return plain
}

func KBlowfish(plain, key, initial []byte) []byte {
	KAbstractEncrypt(plain, key, initial, blowfishEncrypt)
	return plain
}

func KIDEA(plain, key, initial []byte) []byte {
	KAbstractEncrypt(plain, key, initial, ideaEncrypt)
	return plain
}

func KSHA1(text []byte) []byte {
	h := sha1.New()
	h.Write(text)
	hash := h.Sum(nil)
	return hash
}

func KSHA512(text []byte) []byte {
	h := sha512.New()
	h.Write(text)
	hash := h.Sum(nil)
	return hash
}

func KBase64(text []byte) []byte {
	return []byte(base64.StdEncoding.EncodeToString(text))
}