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,直接定位,方便分析)。
接下来开始分析注册码判断方式,本文从输入框跟踪用户名引用入手(以前写的《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实现
最后贴出关键代码,完整代码见附件。
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))
}