[算法分析] XorRanger's KeygenME #5
本帖最后由 currwin 于 2014-6-2 14:59 编辑【文章作者】:currwin
【软件名称】:XorRanger's KeygenME #5
【下载地址】:因为是从外国网站上面趴下来的。直接发百度盘吧:链接: http://pan.baidu.com/s/1pJx4mFp 密码: mk25
【加密方式】:PEcompact ver.2.78a ~ 3.0.3.9- www.bitsum.com ( aPlib/Lzma )
【使用工具】:IDA、OD
蛋疼,IDA,OD都不会用,结果在奇怪的地方浪费了很多时间。小菜鸟写得不好希望大家不要嫌弃。因为这个KM本身并不难,柿子还得挑软的捏,难一点的KM都玩不动了 。小菜鸟玩玩简单的KM,练习一下软件的使用而已。写得不好的地方,还望各位大大能赐教。
脱壳部分:
没有什么好说的了,PEcompact本身就不难脱,单步也好,什么也好,反正很快就会到达OEP的了。
用IDA结合OD分析到的具体KeygenMe流程是:
①判断输入的SerialKey的长度是否等于12,不是则失败。
②对输入的Name进行处理得到GlobalKey。这里有一个陷阱,如果输入的Name不合法,不会马上提示错误,而是继续执行函数,但是到后面与GlobalKey比较的地方就会过不去的。
Name的要求是:
①长度大于12,并且参与运算的只有前12个字符。
②由数字1~9组成。
③.结合前面运算得到的GlobalKey,对SerialKey进行加密。
如果加密运算的结果等于0x0F78ECDE ,便会成功。
函数分析部分:
下面是按钮事件的判断部分:
; int __cdecl CheckIt()
.text:005B5138 CheckIt proc near
.text:005B5138
.text:005B5138 PassW_10 = dword ptr -64h
.text:005B5138 Pass = dword ptr -60h
.text:005B5138 PassW_9 = dword ptr -5Ch
.text:005B5138 Pass = dword ptr -58h
.text:005B5138 PassW_8 = dword ptr -54h
.text:005B5138 Pass = dword ptr -50h
.text:005B5138 PassW_7 = dword ptr -4Ch
.text:005B5138 Pass = dword ptr -48h
.text:005B5138 PassW_6 = dword ptr -44h
.text:005B5138 Pass = dword ptr -40h
.text:005B5138 PassW_5 = dword ptr -3Ch
.text:005B5138 Pass = dword ptr -38h
.text:005B5138 PassW_4 = dword ptr -34h
.text:005B5138 Pass = dword ptr -30h
.text:005B5138 PassW_3 = dword ptr -2Ch
.text:005B5138 Pass = dword ptr -28h
.text:005B5138 PassW_2 = dword ptr -24h
.text:005B5138 Pass = dword ptr -20h
.text:005B5138 NameW = dword ptr -1Ch
.text:005B5138 PassW_1 = dword ptr -18h
.text:005B5138 var_14 = dword ptr -14h
.text:005B5138 iPassLen = dword ptr -10h
.text:005B5138 var_C = dword ptr -0Ch
.text:005B5138 var_8 = dword ptr -8
.text:005B5138 var_4 = dword ptr -4
.text:005B5138
.text:005B5138 push ebp
.text:005B5139 mov ebp, esp
.text:005B513B mov ecx, 0Ch
.text:005B5140
.text:005B5140 loc_5B5140: ; CODE XREF: CheckIt+Dj
.text:005B5140 push 0
.text:005B5142 push 0
.text:005B5144 dec ecx
.text:005B5145 jnz short loc_5B5140
.text:005B5147 push ecx
.text:005B5148 mov , edx
.text:005B514B mov , eax
.text:005B514E xor eax, eax
.text:005B5150 push ebp
.text:005B5151 push offset loc_5B5405 ; buffer
.text:005B5156 push dword ptr fs: ; buffer
.text:005B5159 mov fs:, esp
.text:005B515C lea edx, ; PassW_i 均表示这里是用来存储Pass的
.text:005B515F mov eax,
.text:005B5162 mov eax, ; 3B0为PassWord的Hwnd
.text:005B5168 call WGetDlgItemText ; 取Serial的值
.text:005B516D mov eax,
.text:005B5170 mov , eax
.text:005B5173 mov eax,
.text:005B5176 mov , eax
.text:005B5179 cmp , 0
.text:005B517D jz short loc_5B518A ; 密码的长度为0xC
.text:005B517F mov eax,
.text:005B5182 sub eax, 4 ; String的结构为
.text:005B5182 ; int iLen
.text:005B5182 ; wchar* buffer
.text:005B5185 mov eax, ; 这里是取长度
.text:005B5187 mov , eax
.text:005B518A
.text:005B518A loc_5B518A: ; CODE XREF: CheckIt+45j
.text:005B518A cmp , 0Ch ; 密码的长度为0xC
.text:005B518E jnz loc_5B535A
.text:005B5194 lea edx,
.text:005B5197 mov eax,
.text:005B519A mov eax, ; 3A8为Name的hwnd
.text:005B51A0 call WGetDlgItemText
.text:005B51A5 mov eax,
.text:005B51A8 call VerifyName_1 ; 利用Name来对Grobal_Key进行赋值
.text:005B51AD lea edx,
.text:005B51B0 mov eax,
.text:005B51B3 mov eax,
.text:005B51B9 call WGetDlgItemText
.text:005B51BE mov eax,
.text:005B51C1 mov dx,
.text:005B51C5 lea eax, ] ; Pass表示该内存用来存储第i个密码
.text:005B51C8 call NewChar__ ; 新建空间来保存传递进入的Char
.text:005B51CD mov eax, ]
.text:005B51D0 mov edx, GlobalKey_0_ ; 比较Pass与GlobalKey是否相等
.text:005B51D6 call CmpDword
.text:005B51DB jnz loc_5B535A
.text:005B51E1 lea edx,
.text:005B51E4 mov eax,
.text:005B51E7 mov eax,
.text:005B51ED call WGetDlgItemText
.text:005B51F2 mov eax,
.text:005B51F5 mov dx,
.text:005B51F9 lea eax, ]
.text:005B51FC call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B5201 mov eax, ]
.text:005B5204 mov edx, GlobalKey_1_
.text:005B520A call CmpDword ; 比较Pass与GlobalKey是否相等
.text:005B520F jnz loc_5B535A
.text:005B5215 lea edx,
.text:005B5218 mov eax,
.text:005B521B mov eax,
.text:005B5221 call WGetDlgItemText
.text:005B5226 mov eax,
.text:005B5229 mov dx,
.text:005B522D lea eax, ]
.text:005B5230 call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B5235 mov eax, ]
.text:005B5238 mov edx, GlobalKey_2_
.text:005B523E call CmpDword ; 比较Pass与GlobalKey是否相等
.text:005B5243 jnz loc_5B535A
.text:005B5249 lea edx,
.text:005B524C mov eax,
.text:005B524F mov eax,
.text:005B5255 call WGetDlgItemText
.text:005B525A mov eax,
.text:005B525D mov dx,
.text:005B5261 lea eax, ]
.text:005B5264 call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B5269 mov eax, ]
.text:005B526C call CharToHex ; 把输入的字符直接转换为16进制,如数字 9 变成 内存中:09 00 00 00
.text:005B5271 push eax ; buffer
.text:005B5272 lea edx,
.text:005B5275 mov eax,
.text:005B5278 mov eax,
.text:005B527E call WGetDlgItemText
.text:005B5283 mov eax,
.text:005B5286 mov dx,
.text:005B528A lea eax, ]
.text:005B528D call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B5292 mov eax, ]
.text:005B5295 call CharToHex
.text:005B529A push eax ; buffer
.text:005B529B lea edx,
.text:005B529E mov eax,
.text:005B52A1 mov eax,
.text:005B52A7 call WGetDlgItemText
.text:005B52AC mov eax,
.text:005B52AF mov dx,
.text:005B52B3 lea eax, ]
.text:005B52B6 call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B52BB mov eax, ]
.text:005B52BE call CharToHex
.text:005B52C3 push eax ; buffer
.text:005B52C4 lea edx,
.text:005B52C7 mov eax,
.text:005B52CA mov eax,
.text:005B52D0 call WGetDlgItemText
.text:005B52D5 mov eax,
.text:005B52D8 mov dx,
.text:005B52DC lea eax, ]
.text:005B52DF call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B52E4 mov eax, ]
.text:005B52E7 call CharToHex
.text:005B52EC push eax ; buffer
.text:005B52ED lea edx,
.text:005B52F0 mov eax,
.text:005B52F3 mov eax,
.text:005B52F9 call WGetDlgItemText
.text:005B52FE mov eax,
.text:005B5301 mov dx,
.text:005B5305 lea eax, ]
.text:005B5308 call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B530D mov eax, ]
.text:005B5310 call CharToHex
.text:005B5315 push eax ; hwnd
.text:005B5316 lea edx,
.text:005B5319 mov eax,
.text:005B531C mov eax,
.text:005B5322 call WGetDlgItemText
.text:005B5327 mov eax,
.text:005B532A mov dx,
.text:005B532D lea eax, ]
.text:005B5330 call NewChar__ ; BDS 2005-2007 and Delphi6-7 Visual Component Library
.text:005B5335 mov eax, ]
.text:005B5338 call CharToHex
.text:005B533D pop edx
.text:005B533E pop ecx ;主要的处理函数。。。。。
.text:005B533F call DealKey ; DealKey(P,P,P,P,P,P)
.text:005B5344 mov , eax ; return F78ECDE,success
.text:005B5347 cmp , 0F78ECDEh
.text:005B534E jnz short loc_5B535A
.text:005B5350 mov eax, offset aSuperbJobNowPl ; "Superb Job \r\nNow Please Write a Keygen "...
中间的一大段都是可以忽略的,他们的作用都是读取SerialKey,然后取SerialKey中的第i个数,转换为DWORD的值,最重要的是三个函数的使用
VerifyName_1(wchar* Name);
CmpDowrd(DWORD *Pass, DWORD *GlobalKey);
DealKey(P,P,P,P,P,P);
好看一点的代码就要靠IDA的插件来搞起了。
int __cdecl CheckIt()
{
int v0; // eax@0
int v1; // edx@0
int v2; // edx@4
char v3; // zf@4
int v4; // edx@5
int v5; // edx@6
int v6; // edx@7
int v7; // ST10_4@7
int v8; // edx@7
int v9; // ST0C_4@7
int v10; // edx@7
int v11; // ST08_4@7
int v12; // edx@7
int v13; // ST04_4@7
int v14; // edx@7
int v15; // ST00_4@7
int v16; // edx@7
int v17; // eax@7
unsigned int v19; // @1
WCHAR *v20; // @1
int (*v21)(); // @1
int PassW_10; // @7
int Pass; // @7
int PassW_9; // @7
int Pass; // @7
int PassW_8; // @7
int Pass; // @7
int PassW_7; // @7
int Pass; // @7
int PassW_6; // @7
int Pass; // @7
int PassW_5; // @7
int Pass; // @7
int PassW_4; // @6
int Pass; // @6
int PassW_3; // @5
int Pass; // @5
int PassW_2; // @4
int Pass; // @4
WCHAR *NameW; // @4
int PassW_1; // @1
int v42; // @1
int iPassLen; // @1
int v44; // @1
int v45; // @7
int v46; // @1
int v47; // @1
v42 = v1;
v46 = v0;
v21 = (int (*)())&v47;
v20 = &loc_5B5405;
v19 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v19);
WGetDlgItemText();
v44 = PassW_1;
iPassLen = PassW_1;
if ( PassW_1 )
iPassLen = *(_DWORD *)(iPassLen - 4); // 获取密码长度
if ( iPassLen == 12 ) //密码长度等于12时继续
{
WGetDlgItemText(); // 取用户名
VerifyName_1(NameW); // 利用用户名生成GlobalKey
WGetDlgItemText(); // 取密码
LOWORD(v2) = *(_WORD *)(PassW_2 + 2); // 取密码第 2 / 2 = 1个
NewChar__(&Pass, v2); // 转化为新的wchar
CmpDword(Pass, GlobalKey_0_); // 与GlobalKey比较
if ( v3 )
{
WGetDlgItemText(); // 取密码,下面都是不断的取密码
LOWORD(v4) = *(_WORD *)(PassW_3 + 6);
NewChar__(&Pass, v4);
CmpDword(Pass, GlobalKey_1_); // 比较P 与 GlobalKey
if ( v3 )
{
WGetDlgItemText();
LOWORD(v5) = *(_WORD *)(PassW_4 + 10);
NewChar__(&Pass, v5);
CmpDword(Pass, GlobalKey_2_);
if ( v3 )
{
WGetDlgItemText(); // 不断的取serialKey
LOWORD(v6) = *(_WORD *)(PassW_5 + 12);// 然后取Key相应的数据
NewChar__(&Pass, v6); // 转换为Hex的值
v7 = CharToHex();
WGetDlgItemText();
LOWORD(v8) = *(_WORD *)(PassW_6 + 16);
NewChar__(&Pass, v8);
v9 = CharToHex();
WGetDlgItemText();
LOWORD(v10) = *(_WORD *)(PassW_7 + 20);
NewChar__(&Pass, v10);
v11 = CharToHex();
WGetDlgItemText();
LOWORD(v12) = *(_WORD *)(PassW_8 + 8);
NewChar__(&Pass, v12);
v13 = CharToHex();
WGetDlgItemText();
LOWORD(v14) = *(_WORD *)(PassW_9 + 4);
NewChar__(&Pass, v14);
v15 = CharToHex();
WGetDlgItemText();
LOWORD(v16) = *(_WORD *)PassW_10;
NewChar__(&Pass, v16);
v17 = CharToHex();
v45 = DealKey(v17, v15, v13, v7, v9, v11); //DealKey(P,P,P,P,P,P);
if ( v45 == 259583198 ) // 结果等于这个就成功了
Dialogs::ShowMessage(L"Superb Job \r\nNow Please Write a Keygen and Tutorial");
}
}
}
}
... //此处省略一大堆delete的代码
return sub_40930C(&NameW, 2);
}
因为CmpDword里面使用到了GlobalKey,所以我们需要知道VerifyName_1到底做了些什么。可惜的是这个函数并不重要啊,就直接贴C代码了
int __fastcall VerifyName_1(WCHAR *a1)
{
unsigned int v2; // @1
WCHAR *v3; // @1
int (*v4)(); // @1
WCHAR *v5; // @4
WCHAR *INameLen; // @1
WCHAR *v7; // @1
int v8; // @1
v7 = a1;
System::__linkproc___LStrAddRef(0);
v4 = (int (*)())&v8;
v3 = &loc_5B512A;
v2 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v2);
INameLen = v7;
if ( v7 )
INameLen = (WCHAR *)*((_DWORD *)INameLen - 1);
if ( (signed int)INameLen >= 12 ) // 用户名长度要大于12,实际上,只有前12个会参加计算
{
WGetDlgItemText(); // 再次取用户名
VerifyName_2(v5); // 进行第二次验证
}
__writefsdword(0, v2);
v4 = loc_5B5131;
Delete(&v5);
return Delete(&v7);
}
函数作用,判断用户名是否大于12个,是的话,传递给VerifyName_2进行下一轮判断。 接着来看VerifyName_2到底做了些什么吧,同样的,直接贴C++代码
int __fastcall VerifyName_2(WCHAR *a1)
{
WCHAR v1; // ax@5
unsigned __int8 v2; // cf@5
int v3; // edx@8
int v4; // edx@9
int v5; // ST2C_4@9
int v6; // edx@9
int v7; // ST28_4@9
int v8; // edx@9
int v9; // ST24_4@9
int v10; // edx@9
int v11; // ST20_4@9
int v12; // edx@9
int v13; // ST1C_4@9
int v14; // edx@9
int v15; // ST18_4@9
int v16; // edx@9
int v17; // ST14_4@9
int v18; // edx@9
int v19; // ST10_4@9
int v20; // edx@9
int v21; // ST0C_4@9
int v22; // edx@9
int v23; // ST04_4@9
int v24; // edx@9
int v25; // ST00_4@9
int v26; // edx@9
int v27; // eax@9
unsigned int v29; // @1
int (*v30)(); // @1
int (*v31)(); // @1
int v32; // @1
int v33; // @9
int v34; // @9
int v35; // @9
int v36; // @9
int v37; // @9
int v38; // @9
int v39; // @9
int v40; // @9
int v41; // @9
int v42; // @9
int v43; // @9
char v44; // @9
int v45; // @8
unsigned __int8 v46; // @7
int *v47; // @5
WCHAR *iNameLen; // @1
WCHAR *v49; // @4
int v50; // @1
int i; // @4
WCHAR *v52; // @1
int v53; // @1
v52 = a1;
System::__linkproc___LStrAddRef(v32);
v31 = (int (*)())&v53;
v30 = loc_5B5085;
v29 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v29);
Delete(&v50);
iNameLen = v52;
if ( v52 )
iNameLen = (WCHAR *)*((_DWORD *)iNameLen - 1);// 取用户名长度
if ( (signed int)iNameLen <= 0 )
{
LABEL_9: // 这里IDA卖萌了,反正先看下面的就对了
HIWORD(v4) = HIWORD(v50);
LOWORD(v4) = *(_WORD *)(v50 + 6);
NewChar__(&v43, v4);
v5 = CharToHex(); // 把对应的用户名转换为Hex入堆栈
HIWORD(v6) = HIWORD(v50); // 比如说数字9,就入堆栈 00000009
LOWORD(v6) = *(_WORD *)(v50 + 8);
NewChar__(&v42, v6);
v7 = CharToHex();
HIWORD(v8) = HIWORD(v50);
LOWORD(v8) = *(_WORD *)(v50 + 10);
NewChar__(&v41, v8);
v9 = CharToHex();
HIWORD(v10) = HIWORD(v50);
LOWORD(v10) = *(_WORD *)(v50 + 12);
NewChar__(&v40, v10);
v11 = CharToHex();
HIWORD(v12) = HIWORD(v50);
LOWORD(v12) = *(_WORD *)(v50 + 14);
NewChar__(&v39, v12);
v13 = CharToHex();
HIWORD(v14) = HIWORD(v50);
LOWORD(v14) = *(_WORD *)(v50 + 16);
NewChar__(&v38, v14);
v15 = CharToHex();
HIWORD(v16) = HIWORD(v50);
LOWORD(v16) = *(_WORD *)(v50 + 18);
NewChar__(&v37, v16);
v17 = CharToHex();
HIWORD(v18) = HIWORD(v50);
LOWORD(v18) = *(_WORD *)(v50 + 20);
NewChar__(&v36, v18);
v19 = CharToHex();
HIWORD(v20) = HIWORD(v50);
LOWORD(v20) = *(_WORD *)(v50 + 22);
NewChar__(&v35, v20);
v21 = CharToHex();
HIWORD(v22) = HIWORD(v50);
LOWORD(v22) = *(_WORD *)(v50 + 4);
NewChar__(&v34, v22);
v23 = CharToHex();
HIWORD(v24) = HIWORD(v50);
LOWORD(v24) = *(_WORD *)(v50 + 2);
NewChar__(&v33, v24);
v25 = CharToHex();
HIWORD(v26) = HIWORD(v50);
LOWORD(v26) = *(_WORD *)v50;
NewChar__(&v32, v26);
v27 = CharToHex();
DealName(v27, v25, v23, v5, v7, v9, v11, v13, v15, v17, v19, v21, (signed int)&v44);// DealName(N1,N2,N3,N4,N5,N6,N7,N8,N9,N10,N11,N12)
}
else
{
v49 = iNameLen;
i = 1;
while ( 1 )
{
v47 = &dword_5B5090; // dword_5B5090 = 00000000 0000FE03
v1 = v52;
v2 = v1 < 0xFFu;
if ( v1 <= 0xFFu )
v2 = _bittest((signed __int32 *)&dword_5B5090, (unsigned __int8)v1);// 这个函数第一次见到,弄到我在这里卡住了。
v46 = v2; // 对应的汇编代码为BT XX,XX
if ( !v2 ) // 反正在这里的作用就是验证用户名是否为数字0~9
break;
HIWORD(v3) = HIWORD(v52);
LOWORD(v3) = v52;
NewChar__(&v45, v3);
sub_409FEC(&v50, v45); // 符合要求的用户名将使用一个新的空间保存
++i;
v49 = (WCHAR *)((char *)v49 - 1); // 继续循环,v46为循环次数
if ( !v49 )
goto LABEL_9; // 对每一个名字都进行了处理后。。。跳转
}
}
__writefsdword(0, v29);
v31 = loc_5B508C;
sub_40930C(&v32, 12);
sub_40B394(&v44, off_5B4824);
Delete(&v45);
Delete(&v50);
return Delete(&v52);
}
这个函数做的东西好像多了一点了,判断Name里面的每一个数据,是否在 1~9 之间,是的话,就转换对应的Hex数值,最后才调用到DealName来对名字进行处理。所以说,怎么搞得这么麻烦。
接着看DealName这一个函数,虽然想同时贴汇编代码的,不过实在是太长太难看了,所以唯有贴C代码了。反正IDA这么强大,不用一下可是对不起自己啊。F5.
int __fastcall DealName(int a1, int a2, signed __int32 a3, signed int a13, signed int a12, signed int a11, signed int a10, signed int a9, signed int a8, signed int a7, signed int a6, signed int a5, int a4)
{
int v13; // eax@1
int v14; // eax@4
int v15; // eax@7
int v16; // eax@10
int v17; // eax@13
int v18; // eax@16
double *v19; // eax@19
double *v20; // edx@19
double *v21; // eax@19
double *v22; // edx@19
double *v23; // eax@19
double *v24; // edx@19
long double v25; // fst7@19
long double v26; // fst7@19
long double v27; // fst7@19
unsigned int v29; // @1
int (__stdcall *v30)(int, int, int, int, int, int, int, int, int, int); // @1
int (__stdcall *v31)(int, int, int, int, int, int, int, int, int, int); // @1
int v32; // @19
int v33; // @19
int v34; // @19
double *v35; // @19
int LoopTime; // @2
int v37; // @1
int v38; // @1
int v39; // @1
int v40; // @19
double *D9ABC; // @1
double *D5678; // @1
double *D1234; // @1
double v44; // @4
double v45; // @4
double v46; // @10
double v47; // @1
int i; // @1
int v49; // @2
signed __int32 v50; // @1
int v51; // @1
int v52; // @1
int v53; // @1
v50 = _InterlockedExchange((signed __int32 *)&v52, a3);
v51 = a2;
v52 = a1;
v31 = (int (__stdcall *)(int, int, int, int, int, int, int, int, int, int))&v53;
v30 = loc_5B4E54;
v29 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v29);
sub_40B394(&v39, off_5B54D8);
System::__linkproc___DynArraySetLength(4); // 这里把Name按4个分组,共分成3个组 (double型)
*(double *)v39 = (long double)v52; //D1234存储用户名的第1,2 ,3,4个数据的Hex值
*(double *)(v39 + 8) = (long double)v51; //D5678存储用户名的第5,6,7,8个数据的Hex值
*(double *)(v39 + 16) = (long double)v50; //D9ABC存储用户名的第9,A,B,C个数据的Hex值
*(double *)(v39 + 24) = (long double)a4; //这样说可能还是有一点不太清楚,反正你当作你输入的每一个字符都转换为了Double的值就是了
System::__linkproc___DynArrayAsg(&D1234, v39, off_5B54D8);
sub_40B394(&v38, off_5B54FC);
System::__linkproc___DynArraySetLength(4);
*(double *)v38 = (long double)a5;
*(double *)(v38 + 8) = (long double)a6;
*(double *)(v38 + 16) = (long double)a7;
*(double *)(v38 + 24) = (long double)a8;
System::__linkproc___DynArrayAsg(&D5678, v38, off_5B54FC);
sub_40B394(&v37, off_5B5520);
System::__linkproc___DynArraySetLength(4);
*(double *)v37 = (long double)a9;
*(double *)(v37 + 8) = (long double)a10;
*(double *)(v37 + 16) = (long double)a11;
*(double *)(v37 + 24) = (long double)a12;
System::__linkproc___DynArrayAsg(&D9ABC, v37, off_5B5520);
i = 0;
*(_QWORD *)&v47 = *(_QWORD *)D1234; // D1234
v13 = GetNumber(D1234); // 获取数组的个数-1,总是返回3
if ( v13 >= 0 )
{
LoopTime = v13 + 1; // +1取回循环的总次数,为4次
v49 = 0;
do
{
D1234 = D1234 / v47; // D1234 = D1234 / D1234
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
i = 0;
*(_QWORD *)&v45 = *(_QWORD *)D5678; // D5678
*(_QWORD *)&v44 = *(_QWORD *)D9ABC; // D9ABC
v14 = GetNumber(D5678);
if ( v14 >= 0 )
{
LoopTime = v14 + 1;
v49 = 0;
do
{
D5678 = D5678 - v45 * D1234; // D5678 = D5678 - D5678 * D1234
D9ABC = D9ABC - v44 * D1234; // D9ABC = D9ABC - D9ABC * D1234
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
i = 0;
*(_QWORD *)&v47 = *((_QWORD *)D5678 + 1); // D5678
v15 = GetNumber(D5678);
if ( v15 >= 0 )
{
LoopTime = v15 + 1;
v49 = 0;
do
{
D5678 = D5678 / v47; // D5678 = D5678 / D5678
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
i = 0;
*(_QWORD *)&v46 = *((_QWORD *)D1234 + 1); // D1234
*(_QWORD *)&v44 = *((_QWORD *)D9ABC + 1); // D9ABC
v16 = GetNumber(D5678);
if ( v16 >= 0 )
{
LoopTime = v16 + 1;
v49 = 0;
do
{
D1234 = D1234 - v46 * D5678; // D1234 = D1234 - D1234 * D5678
D9ABC = D9ABC - v44 * D5678; // D9ABC = D9ABC - D9ABC * D5678
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
i = 0;
*(_QWORD *)&v47 = *((_QWORD *)D9ABC + 2); // D9ABC
v17 = GetNumber(D9ABC);
if ( v17 >= 0 )
{
LoopTime = v17 + 1;
v49 = 0;
do
{
D9ABC = D9ABC / v47; // D9ABC = D9ABC / D9ABC
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
i = 0;
*(_QWORD *)&v46 = *((_QWORD *)D1234 + 2); // D1234
*(_QWORD *)&v45 = *((_QWORD *)D5678 + 2); // D5678
v18 = GetNumber(D9ABC);
if ( v18 >= 0 )
{
LoopTime = v18 + 1;
v49 = 0;
do
{
D1234 = D1234 - v46 * D9ABC; // D1234 = D1234 - D1234 * D9ABC
D5678 = D5678 - v45 * D9ABC; // D5678 = D5678 - D5678 * D9ABC
++i;
++v49;
--LoopTime;
}
while ( LoopTime );
}
sub_40B394(&v35, off_5B5544);
System::__linkproc___DynArraySetLength(3);
v19 = v35;
v20 = D1234;
*(_DWORD *)v35 = *((_DWORD *)D1234 + 6); // V35 = D1234
*((_DWORD *)v19 + 1) = *((_DWORD *)v20 + 7);
v21 = v35;
v22 = D5678;
*((_DWORD *)v35 + 2) = *((_DWORD *)D5678 + 6);// V35 = D5678
*((_DWORD *)v21 + 3) = *((_DWORD *)v22 + 7);
v23 = v35;
v24 = D9ABC;
*((_DWORD *)v35 + 4) = *((_DWORD *)D9ABC + 6);// V35 = D9ABC
*((_DWORD *)v23 + 5) = *((_DWORD *)v24 + 7);
System::__linkproc___DynArrayAsg(&v40, v35, off_5B5544);// 把v35里面的值全部送给v40
v25 = fabs(*(double *)v40); // 取绝对值
dword_5C9344 = System::__linkproc___ROUND(v25);// 四舍五入
v26 = fabs(*(double *)(v40 + 8));
dword_5C9348 = System::__linkproc___ROUND(v26);
v27 = fabs(*(double *)(v40 + 16));
dword_5C934C = System::__linkproc___ROUND(v27);
DwordToString(dword_5C9344, &v34);
sub_40A1B4(&GlobalKey_0_); // GlobalKey = ROUND(fabs(D1234)) + '0'
DwordToString(dword_5C9348, &v33);
sub_40A1B4(&GlobalKey_1_); // GlobalKey = ROUND(fabs(D5678)) + '0'
DwordToString(dword_5C934C, &v32);
sub_40A1B4(&GlobalKey_2_); // GlobalKey = ROUND(fabs(D9ABC)) + '0'
__writefsdword(0, v29);
v31 = loc_5B4E5E;
sub_40930C(&v32, 3);
sub_40B394(&v35, off_5B5544);
sub_40B394(&v37, off_5B5520);
sub_40B394(&v38, off_5B54FC);
sub_40B394(&v39, off_5B54D8);
sub_40B394(&v40, off_5B5544);
sub_40B394(&D9ABC, off_5B5520);
sub_40B394(&D5678, off_5B54FC);
return sub_40B394(&D1234, off_5B54D8);
}
代码可是非常的清晰啊,我也不想再多讲解什么了,注意了,这里在函数头和函数为都使用了一些非常奇怪的函数,反正我是第一次见,在网上也找不到什么资料,不过不太重要,作用大致都可以从名字中猜测出来了,所以不需要害怕的。哈哈。
利用Name生成GlobalKey后,便是比较几个数据:
P 与 GlobalKey , P 与 GlobalKey, P 与 GlobalKey
只有他们都相等的话才会进入到DealKey这个函数中去。
并且,DealKey这个函数并没有用到P,P,P的值,所以这里已经可以确定SerialKey的其中3个值了。
接下来看DealKey函数:
int __fastcall DealKey(int a1, int a2, int a3, int P10, int P8, int P6)
.text:005B4854 DealKey proc near ; CODE XREF: CheckIt+207p
.text:005B4854
.text:005B4854 var_20 = dword ptr -20h
.text:005B4854 var_1C = dword ptr -1Ch
.text:005B4854 K2 = dword ptr -18h
.text:005B4854 K1 = dword ptr -14h
.text:005B4854 result = dword ptr -10h
.text:005B4854 P4 = dword ptr -0Ch
.text:005B4854 P2 = dword ptr -8
.text:005B4854 P0 = dword ptr -4
.text:005B4854 P10 = dword ptr8
.text:005B4854 P8 = dword ptr0Ch
.text:005B4854 P6 = dword ptr10h
.text:005B4854
.text:005B4854 push ebp
.text:005B4855 mov ebp, esp
.text:005B4857 add esp, 0FFFFFFE0h
.text:005B485A push ebx
.text:005B485B xor ebx, ebx
.text:005B485D mov , ebx
.text:005B4860 mov , ebx
.text:005B4863 mov , ecx
.text:005B4866 mov , edx
.text:005B4869 mov , eax
.text:005B486C xor eax, eax
.text:005B486E push ebp
.text:005B486F push offset loc_5B48FB
.text:005B4874 push dword ptr fs:
.text:005B4877 mov fs:, esp ; 把esp指针送入堆栈
.text:005B487A mov , 2413h ; K1 = 0x2414
.text:005B4881 lea edx,
.text:005B4884 mov eax,
.text:005B4887 call DwordToString
.text:005B488C mov eax,
.text:005B488F lea edx,
.text:005B4892 call @Strutils@ReverseString$qqrx17System@AnsiString ; 反转string
.text:005B4897 mov eax,
.text:005B489A call CharToHex
.text:005B489F mov , eax ; K2 = 0x14D1
.text:005B48A2 mov eax,
.text:005B48A5 imul
.text:005B48A8 imul ; temp_1 = P2 * P10 * P8
.text:005B48AB push eax
.text:005B48AC mov eax,
.text:005B48AF pop edx
.text:005B48B0 mov ecx, edx
.text:005B48B2 cdq
.text:005B48B3 idiv ecx ; P0 % temp_1
.text:005B48B5 mov eax,
.text:005B48B8 add eax,
.text:005B48BB xor eax, ; temp_2 = P4 ^ (P0 + K1)
.text:005B48BE imul edx, eax ; temp_3 = temp_1 * temp_2
.text:005B48C1 mov eax,
.text:005B48C4 add eax, ; temp_4 = P0 + P4
.text:005B48C7 mov ecx,
.text:005B48CA imul ecx,
.text:005B48CE imul eax, ecx
.text:005B48D1 xor eax, ; temp_5 = K2 ^ P2 * P6 * temp_1
.text:005B48D4 imul edx, eax
.text:005B48D7 sub edx, 1939h ; temp_6 = temp_3 * temp_5 - 0x1939
.text:005B48DD mov , edx
.text:005B48E0 xor eax, eax
.text:005B48E2 pop edx
.text:005B48E3 pop ecx
.text:005B48E4 pop ecx
.text:005B48E5 mov fs:, edx
.text:005B48E8 push offset loc_5B4902
.text:005B48ED
.text:005B48ED loc_5B48ED: ; CODE XREF: DealKey+ACj
.text:005B48ED lea eax,
.text:005B48F0 mov edx, 2
.text:005B48F5 call sub_40930C
.text:005B48FA retn
幸好其中作为主体的运算部分不算长, 相应的注释也是有的了。看着注释应该是不难理解算法的。
int __fastcall DealKey(int a1, int a2, int a3, int P10, int P8, int P6)
{
int K; // eax@1
unsigned int v8; // @1
int (__stdcall *v9)(int, int, int); // @1
int (__stdcall *v10)(int, int, int); // @1
int v11; // @1
int v12; // @1
int K2; // @1
int K1; // @1
int result; // @1
int P4; // @1
int P2; // @1
int P0; // @1
int v19; // @1
v12 = 0; // 下面,以P代表是密码的第i个字符
v11 = 0;
P4 = a3;
P2 = a2;
P0 = a1;
v10 = (int (__stdcall *)(int, int, int))&v19;
v9 = loc_5B48FB;
v8 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v8);
K1 = 9235;
DwordToString(9235, &v11); // 把9235转换为字符窜
Strutils::ReverseString(v11, &v12); // 把字符窜头尾对调
K = CharToHex(); // 转换回为DWORD
K2 = K; // 最终结果为5329
result = (K ^ P6 * P2 * (P4 + P0)) * (P4 ^ (K1 + P0)) * P0 % (P8 * P10 * P2) - 6457;// 主要的加密地方,一句代码就搞定了。。。。真心的不难
__writefsdword(0, v8);
v10 = loc_5B4902;
return sub_40930C(&v11, 2);
}
实际上,在逆向的开始,是不太可能马上就知道GlobalKey是怎么生成的,不过因为是IDA嘛,各种奇怪的功能都有,在这里附上一张图,
这一张图充分的说明了GlobalKey由哪一些函数调用到了。说实话,这个功能非常的好用。果然IDA是神器啊。
好吧,废话少说,知道了这些,就可以着手写Keygen了,实际上写Keygen就相当于把它的加密代码重新写了一遍啊蛋疼得很。
keygen.cpp:
#include <iostream>
using namespace std;
#define rand10 (rand() % 10)
char Global_Array;
bool IsAuthKeyEffect(char a[]);
void SetGlobalKey(char AuthKey[]);
void SetSerialKey(char SerialKey[]);
int main()
{
char AuthKey;
char SerialKey;
cout<<"*********************************************************************"<<endl
<<"* KeyGen For XorRanger's KeygenME #5 *"<<endl
<<"* by F8LEFT *"<<endl
<<"* 2014.6.1 *"<<endl
<<"*********************************************************************"<<endl;
while(1) {
cout<<"Please input your AuthKey: (1~9,Length: more than 12 character)"<<endl;
cin>>AuthKey;
if(strlen(AuthKey) >= 12) {
if(IsAuthKeyEffect(AuthKey)) {
break;
}
else {
cout<<"Error! Only 1 to 9 is accepted for the AuthKey"<<endl;
}
} else {
cout<<"Error! AuthKey must to be 12 characters long (or longer)"<<endl;
}
}
SetGlobalKey(AuthKey);
SetSerialKey(SerialKey);
cout<<"Your serialKey is:"<<endl
<<SerialKey<<endl
<<"Now please enjoy it"<<endl;
system("pause");
return 0;
}
bool IsAuthKeyEffect(char a[]) //Only '1' to '9' is accepted
{
bool flag = 1;
for(int i = 0; i < 12; i++) {
if(a <= '0' || a > '9') {
flag = 0;
break;
}
}
return flag;
}
void SetGlobalKey(char AuthKey[])
{
double Group_1, Group_2, Group_3, temp_1, temp_2, temp_3;
int i;
for(i = 0; i < 4; i++) { //Change AuthKey To Double
Group_1 = AuthKey - '0';
Group_2 = AuthKey - '0';
Group_3 = AuthKey - '0';
}
//Started to Encrypt the AuthKey
temp_1 = Group_1;
temp_2 = Group_2;
temp_3 = Group_3;
for(i = 0; i < 4; i++) {
Group_1 = Group_1/temp_1;
}
for(i = 0; i < 4; i++) {
Group_2 = Group_2 - temp_2*Group_1;
Group_3 = Group_3 - temp_3*Group_1;
}
temp_1 = Group_1;
temp_2 = Group_2;
temp_3 = Group_3;
for(i = 0; i < 4; i++) {
Group_2 = Group_2/temp_2;
}
for(i = 0;i < 4; i++) {
Group_1 = Group_1 - temp_1*Group_2;
Group_3 = Group_3 - temp_3*Group_2;
}
temp_1 = Group_1;
temp_2 = Group_2;
temp_3 = Group_3;
for(i = 0;i < 4; i++) {
Group_3 = Group_3/temp_3;
}
for(i = 0;i < 4; i++) {
Group_1 = Group_1 - temp_1*Group_3;
Group_2 = Group_2 - temp_2*Group_3;
}
Global_Array = (int)(fabs(Group_1) + 0.5) + '0';
Global_Array = (int)(fabs(Group_2) + 0.5) + '0';
Global_Array = (int)(fabs(Group_3) + 0.5) + '0';
}
bool CheckSerialKey(char SerialKey[])
{
static int result;
static int p;
static int K1 = 0x2413;
static int K2 = 0x14D1;
p = SerialKey;
p = SerialKey;
p = SerialKey;
p = SerialKey;
p = SerialKey;
p= SerialKey;
if(!(p && p && p)) //When one of them is zero, return false
return false;
result = (K2 ^ p * p * (p + p)) *
((p ^ (K1 + p)) * (p % (p * p * p))) - 0x1939;
/*
ps1:
0x1600 > (K2 ^ p * p * (p + p)) > 0x1000(u)
and
0x2500 > (p ^ (K1 + p) > 0x2400
what's more
9 >= (p % (p * p * p)) >= 0
so
(0xF78ECDE / 0x1600 / 0x2500) < 5 <= p < 0xF78ECDE / 0x1000 / 0x2400
ps2:
In fact,p = 5 is the best number to choose;
*/
if(result == 0x0F78ECDE)
return true;
return false;
}
void SetSerialKey(char SerialKey[])
{
int i;
for(i = 0; i < 12; i++)
SerialKey = 0;
SerialKey = 5; //must to be 5
srand(0);
while(1) {
SerialKey = rand10;
SerialKey = rand10;
SerialKey = rand10;
SerialKey = rand10;
SerialKey= rand10;
if(CheckSerialKey(SerialKey))
break;
}
SerialKey = rand10;
SerialKey = rand10;
SerialKey= rand10;
for(i = 0; i < 12; i++)
SerialKey = SerialKey + '0';
SerialKey = Global_Array;
SerialKey = Global_Array;
SerialKey = Global_Array;
SerialKey= 0;
}
KeyGen下载:链接: http://pan.baidu.com/s/1jGLvaom 密码: hydi
最后总结一下吧,个人觉得这个KM做得不好的一点就是AuthKey与SerialKey的关联不大,它对用户名的计算在最后就没有参加到dealKey中去,这样,只要有了一个正确的Key,就可以很简单的模拟出其他的一堆key出来,这样乐趣就少多了。无论如何,这个KM还是挺有趣的,有兴趣的大家可以玩一下。哈哈。
PS:第一次玩KM啊,第一次用IDA啊,两个都不会玩啊,怎么破?自己看着写出来的代码都感到混乱了,求大大支招啊。端午节到了,赶紧潜水了啊。
PS2:软件的参数传递里面使用到了EAX,ECX,EDX哦,分析起来真是难死了,看来下次还是找一个VC的KeygenMe玩了。
PS3:本来想写好在儿童节发的啊,没想到真的写好了的时候已经到了端午节了啊。
今天有没有粽子收啊粽子。
放一份到论坛上面备份:
{:301_971:}粽子都没吃上,感谢楼主的分析,算法一直都是我的硬伤 不敢吃粽子。 Passerby 发表于 2014-6-2 10:34
粽子都没吃上,感谢楼主的分析,算法一直都是我的硬伤
也是我的硬伤{:301_999:}不过不玩算法的话就一直成为不了大牛 啧啧!对于注册码分析之类的是我的软肋啊!特别是注册机什么的及就更不会了! 虽然很详细我这个入门尚浅的学童表示吃不消。
页:
[1]