IcoFx2.11算法分析&keygen编写
本帖最后由 红绡枫叶 于 2015-5-26 11:25 编辑IcoFX是我个人比较喜欢的软件,做图标很方便.语言:delphi xe2(2012).
它的注册方式是本地验证.只不过本地验证有些陷阱,有虚假注册(只是验证一部分注册码,在其他地方才验证全部).
首先应该明白delphi编译器的特点.参数三个之内用寄存器,多的用堆栈.即
delphi(eax,edx,ecx,stack...). 在 IDA中的调用叫做 __fastcall.
所有的符号分析由IDR完成,生成map给IDA和od用.
注册按钮是灰色的.在IDR里面很容易找到注册所在的form:
_TfrmRegister_btnBuyClick
_TfrmRegister_btnCancelClick
_TfrmRegister_btnPasteClick
_TfrmRegister_btnRegisterClick
_TfrmRegister_FormClose
_TfrmRegister_FormCreate
_TfrmRegister_FormShow
_TfrmRegister_Timer1Timer
其中有一个Timer1Timer,很可能就是验证数据是否合法来激活注册按钮的.
其实就是定时对剪切板内容进行提取,符合验证就让paste按钮有效.
看Timer1Timer代码:
_Unit216::TfrmRegister.Timer1Timer
....
00A3ADB8 push ebp
00A3ADB9 push 0A3AF37
00A3ADBE push dword ptr fs:
00A3ADC1 mov dword ptr fs:,esp
00A3ADC4 call Clipboard
00A3ADC9 mov dx,1
00A3ADCD call TClipboard.HasFormat //剪切板是否是一定的格式.初始化的时候他设置的格式为字符串不为空.具体地方自己找吧.
00A3ADD2 test al,al
>00A3ADD4 je 00A3AF1A //剪切板没有有字符串就不用验证.
00A3ADDA call Clipboard
00A3ADDF lea edx,
00A3ADE2 call TClipboard.GetAsText //获取剪切板内容
00A3ADE7 mov eax,dword ptr
00A3ADEA lea edx,
00A3ADED call Trim //去掉空格等.
00A3ADF2 mov edx,dword ptr
00A3ADF5 mov eax,dword ptr
00A3ADF8 mov eax,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3ADFE add eax,8; TPKVSCheckKey.FKey:string
00A3AE01 call @UStrAsg //这是字符串复制用的函数.
00A3AE06 mov eax,dword ptr
00A3AE09 mov eax,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3AE0F mov dword ptr ,eax
00A3AE12 lea eax,
00A3AE15 mov edx,dword ptr
00A3AE18 mov edx,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3AE1E mov edx,dword ptr ; TPKVSCheckKey.FKey:string
00A3AE21 call @UStrLAsg
00A3AE26 mov byte ptr ,0
00A3AE2A mov al,; 0x1
00A3AE2F push eax
00A3AE30 lea eax,
00A3AE33 push eax
00A3AE34 xor ecx,ecx
00A3AE36 mov edx,0A3B154; '-'
00A3AE3B mov eax,dword ptr
00A3AE3E call StringReplace //去掉'-'
00A3AE43 mov eax,dword ptr
00A3AE46 lea edx,
00A3AE49 call UpperCase
00A3AE4E mov eax,dword ptr
00A3AE51 mov dword ptr ,eax
00A3AE54 cmp dword ptr ,0 //去掉'-',空格等之后,判断是否为空.
>00A3AE58 je 00A3AE65
00A3AE5A mov eax,dword ptr
00A3AE5D sub eax,4
00A3AE60 mov eax,dword ptr
00A3AE62 mov dword ptr ,eax
00A3AE65 cmp dword ptr ,19 //判断是否是25个字符,测试的时候输入假验证码不就是25个嘛,要联想.
>00A3AE69 jne 00A3AEFE
00A3AE6F lea eax,
00A3AE72 push eax
00A3AE73 mov ecx,3
00A3AE78 mov edx,17
00A3AE7D mov eax,dword ptr
00A3AE80 call @UStrCopy//字符串复制到 dword ptr
00A3AE85 lea eax,
00A3AE88 mov ecx,3
00A3AE8D mov edx,17
00A3AE92 call @UStrDelete //删除第23,24,25个字符.也就是删除最后三个字符.汇编中数字都是16进制的!
00A3AE97 lea eax,
00A3AE9A push eax
00A3AE9B lea ecx,
00A3AE9E mov edx,dword ptr
00A3AEA1 mov eax,dword ptr
00A3AEA4 call 0091D170
00A3AEA9 mov edx,dword ptr
00A3AEAC mov ecx,3
00A3AEB1 mov eax,dword ptr
00A3AEB4 call 00921B44
00A3AEB9 mov edx,dword ptr
00A3AEBC mov eax,dword ptr
00A3AEBF call @UStrEqual //比较字符串
>00A3AEC4 je 00A3AEF9 //相等就跳,看下面跳的地方有TfrmRegister.btnPaste:TPngSpeedButton,TControl.SetEnabled
00A3AEC6 lea eax, //就知道让粘贴的图标按钮有效.
00A3AEC9 push eax
00A3AECA lea ecx,
00A3AECD mov edx,dword ptr
00A3AED0 mov eax,dword ptr
00A3AED3 call 0091D338
00A3AED8 mov edx,dword ptr
00A3AEDB mov ecx,3
00A3AEE0 mov eax,dword ptr
00A3AEE3 call 00921B44
00A3AEE8 mov edx,dword ptr
00A3AEEB mov eax,dword ptr
00A3AEEE call @UStrEqual
>00A3AEF3 je 00A3AEF9
00A3AEF5 xor eax,eax
>00A3AEF7 jmp 00A3AEFB
00A3AEF9 mov al,1
00A3AEFB mov byte ptr ,al
00A3AEFE mov al,byte ptr
00A3AF01 mov byte ptr ,al
00A3AF04 mov eax,dword ptr
00A3AF07 mov eax,dword ptr ; TfrmRegister.btnPaste:TPngSpeedButton
00A3AF0D mov dl,byte ptr
00A3AF10 mov ecx,dword ptr
00A3AF12 call dword ptr ; TControl.SetEnabled
>00A3AF18 jmp 00A3AF2D
00A3AF1A mov eax,dword ptr
00A3AF1D mov eax,dword ptr ; TfrmRegister.btnPaste:TPngSpeedButton
00A3AF23 xor edx,edx
00A3AF25 mov ecx,dword ptr
00A3AF27 call dword ptr ; TControl.SetEnabled
00A3AF2D xor eax,eax
00A3AF2F pop edx
00A3AF30 pop ecx
00A3AF31 pop ecx
00A3AF32 mov dword ptr fs:,edx
>00A3AF35 jmp 00A3AF54
<00A3AF37 jmp @HandleAnyException
00A3AF3C mov eax,dword ptr
00A3AF3F mov eax,dword ptr ; TfrmRegister.btnPaste:TPngSpeedButton
00A3AF45 xor edx,edx
00A3AF47 mov ecx,dword ptr
00A3AF49 call dword ptr ; TControl.SetEnabled
00A3AF4F call @DoneExcept
00A3AF54 lea edx,
00A3AF57 mov eax,dword ptr
00A3AF5A mov eax,dword ptr ; TfrmRegister.KeyEdit1:TKeyEdit
00A3AF60 call TKeyEdit.GetKey
00A3AF65 mov edx,dword ptr
00A3AF68 mov eax,dword ptr
00A3AF6B mov eax,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3AF71 add eax,8; TPKVSCheckKey.FKey:string
00A3AF74 call @UStrAsg
00A3AF79 lea edx,
00A3AF7C mov eax,dword ptr
00A3AF7F mov eax,dword ptr ; TfrmRegister.edtName:TEdit
00A3AF85 call TControl.GetText
00A3AF8A mov eax,dword ptr
00A3AF8D lea edx,
00A3AF90 call Trim
00A3AF95 cmp dword ptr ,0
>00A3AF99 je 00A3B0A5
00A3AF9F mov eax,dword ptr
00A3AFA2 mov eax,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3AFA8 mov dword ptr ,eax
00A3AFAB lea eax,
00A3AFAE mov edx,dword ptr
00A3AFB1 mov edx,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3AFB7 mov edx,dword ptr ; TPKVSCheckKey.FKey:string
00A3AFBA call @UStrLAsg
00A3AFBF mov byte ptr ,0
00A3AFC3 mov al,; 0x1
00A3AFC8 push eax
00A3AFC9 lea eax,
00A3AFCC push eax
00A3AFCD xor ecx,ecx
00A3AFCF mov edx,0A3B154; '-'
00A3AFD4 mov eax,dword ptr
00A3AFD7 call StringReplace
00A3AFDC mov eax,dword ptr
00A3AFDF lea edx,
00A3AFE2 call UpperCase
00A3AFE7 mov eax,dword ptr
00A3AFEA mov dword ptr ,eax
00A3AFED cmp dword ptr ,0
>00A3AFF1 je 00A3AFFE
00A3AFF3 mov eax,dword ptr
00A3AFF6 sub eax,4
00A3AFF9 mov eax,dword ptr
00A3AFFB mov dword ptr ,eax
00A3AFFE cmp dword ptr ,19
>00A3B002 jne 00A3B097
00A3B008 lea eax,
00A3B00B push eax
00A3B00C mov ecx,3
00A3B011 mov edx,17
00A3B016 mov eax,dword ptr
00A3B019 call @UStrCopy//复制最后三位到dword ptr
00A3B01E lea eax,
00A3B021 mov ecx,3
00A3B026 mov edx,17
00A3B02B call @UStrDelete
00A3B030 lea eax,
00A3B033 push eax
00A3B034 lea ecx,
00A3B037 mov edx,dword ptr //放的是验证注册码的最后三位
00A3B03A mov eax,dword ptr
00A3B03D call 0091D170//比较关键的call
00A3B042 mov edx,dword ptr
00A3B045 mov ecx,3
00A3B04A mov eax,dword ptr
00A3B04D call 00921B44//比较关键的call
00A3B052 mov edx,dword ptr
00A3B055 mov eax,dword ptr
00A3B058 call @UStrEqual//上面一段和一开始的分析是一样的,所以就省略了.
>00A3B05D je 00A3B092 //同理,这里跳转的话,注册按钮就会被激活.
00A3B05F lea eax,
00A3B062 push eax
00A3B063 lea ecx,
00A3B066 mov edx,dword ptr
00A3B069 mov eax,dword ptr
00A3B06C call 0091D338 //比较关键的call
00A3B071 mov edx,dword ptr
00A3B074 mov ecx,3
00A3B079 mov eax,dword ptr
00A3B07C call 00921B44 //比较关键的call
00A3B081 mov edx,dword ptr
00A3B084 mov eax,dword ptr
00A3B087 call @UStrEqual
>00A3B08C je 00A3B092 //同理,这里跳转的话,注册按钮就会被激活.
00A3B08E xor eax,eax
>00A3B090 jmp 00A3B094
00A3B092 mov al,1
00A3B094 mov byte ptr ,al
00A3B097 mov al,byte ptr
00A3B09A mov byte ptr ,al
00A3B09D mov al,byte ptr
00A3B0A0 mov byte ptr ,al
>00A3B0A3 jmp 00A3B0A9
00A3B0A5 mov byte ptr ,0
00A3B0A9 cmp byte ptr ,0
>00A3B0AD je 00A3B0C4
00A3B0AF mov eax,dword ptr
00A3B0B2 mov eax,dword ptr ; TfrmRegister.KeyChecker:TPKVSCheckKey
00A3B0B8 call TPKVSCheckKey.IsBlacklisted
00A3B0BD xor al,1
00A3B0BF mov byte ptr ,al
>00A3B0C2 jmp 00A3B0C8
00A3B0C4 mov byte ptr ,0
00A3B0C8 mov eax,dword ptr
00A3B0CB mov eax,dword ptr ; TfrmRegister.btnRegister:TButton
00A3B0D1 mov dl,byte ptr
00A3B0D4 mov ecx,dword ptr
00A3B0D6 call dword ptr ; TControl.SetEnabled
00A3B0DC xor eax,eax
00A3B0DE pop edx
00A3B0DF pop ecx
00A3B0E0 pop ecx
00A3B0E1 mov dword ptr fs:,edx
00A3B0E4 push 0A3B13D
00A3B0E9 lea eax,
00A3B0EC mov edx,5
00A3B0F1 call @UStrArrayClr
00A3B0F6 lea eax,
00A3B0F9 call @UStrClr
00A3B0FE lea eax,
00A3B101 mov edx,9
00A3B106 call @UStrArrayClr
00A3B10B lea eax,
00A3B10E mov edx,2
00A3B113 call @UStrArrayClr
00A3B118 lea eax,
00A3B11B call @UStrClr
00A3B120 lea eax,
00A3B123 mov edx,2
00A3B128 call @UStrArrayClr
00A3B12D lea eax,
00A3B130 call @UStrClr
00A3B135 ret
<00A3B136 jmp @HandleFinally
<00A3B13B jmp 00A3B0E9
00A3B13D pop edi
00A3B13E pop esi
00A3B13F pop ebx
00A3B140 mov esp,ebp
00A3B142 pop ebp
00A3B143 ret
看汇编挺累的,直接用IDA吧.
进入关键call 0091D170:
int __fastcall sub_91D170(int key_class, int key_1_23, int dest) //参数个数要自己分析指定
{
void *v3; // edx@14
unsigned __int32 v5; // @1
_UNKNOWN *v6; // @1
int *v7; // @1
char *Source; // @1
unsigned int MaxLen; // @1
int v10; // @13
char *v11; // @4
int v12; // @1
char *v13; // @7
int v14; // @7
unsigned __int16 v15; // @13
unsigned __int16 v16; // @1
unsigned __int16 v17; // @1
char *v19; // @1
int v20; // @1
int savedregs; // @1
Source = 0;
MaxLen = 0;
v19 = (char *)key_1_23;
v20 = key_class;
v7 = &savedregs;
v6 = &loc_91D2D2;
v5 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v5); //和异常处理有关
v17 = 86;
v16 = 175;
v12 = key_1_23;
if ( key_1_23 )
v12 = *(_DWORD *)(v12 - 4); //获取字符串长度
if ( v12 > 0 )
{
v11 = v19;
if ( v19 )
v11 = (char *)*((_DWORD *)v11 - 1); // 获取字符串长度
if ( (signed int)v11 > 0 )
{
v13 = v11;
v14 = 1;
do
{
v16 += (unsigned __int8)v19;
if ( v16 > 0xFFu )
v16 -= 255;
v17 += v16;
if ( v17 > 0xFFu )
v17 -= 255;
++v14;
--v13;
}
while ( v13 );
}
}
v15 = (unsigned __int16)(v16 + (v17 << 8)) % 0xB63Fu;
v10 = v15;
v10 = 0;
UStrClr(&MaxLen);
do
{
v3 = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
LOWORD(v3) = a0123456789abcd[(unsigned int)(*(_QWORD *)v10 % 36i64)]; //64位求余,这里注意了,
// 函数原型为__int64 __usercall _llmod@<edx:eax>(int numer_low@<eax>, int numer_hight@<edx>, int denom_low, int denom_hight);
//IDA自己分析是失败的,所以要自己手动设置函数原型.
UStrFromWChar(&Source, v3);
UStrCat3((wchar_t *)&MaxLen, (const wchar_t *)Source, MaxLen);
*(_QWORD *)v10 /= 36i64;//64位除法
//此处也要注意,IDA自己分析是失败的
//函数原型为 __int64 __usercall _lldiv@<edx:eax>(int numer_low@<eax>, int numer_hight@<edx>, int denom_low, int denom_hight);
}
while ( *(_QWORD *)v10 );
UStrAsg((wchar_t *)dest, MaxLen); //复制给参数
__writefsdword(0, 0x24u);
return UStrArrayClr((int)&Source, 2, (int)&loc_91D2D9);
}
我再还原优化一下就是这样的:
unsigned int sum2 = 86;
unsigned int sum1 = 175;
if (int len = _key.length())
{
int iter = 1;
do
{
sum1 += _key;
if (sum1 > 0xFFu)
sum1 -= 255;
sum2 += sum1;
if (sum2 > 0xFFu)
sum2 -= 255;
++iter;
--len;
} while (len);
}
__int64 numr = (unsigned __int16)(sum1 + (sum2 << 8)) % 0xB63Fu;
std::string result;
do //注意,下面多处用到这里的代码.
{
result.insert(0, 1, m_alphabet);
numr /= 36i64;
} while (numr);
return result;
很简单吧? 根据前22位得出最后3位的字符,最后三位和此处得到的3位字符相等,注册按钮就会被激活.
call 00921B44//比较关键的call 此处只是防止生成的字符不足3位,不足的话用'0'填充.这里就不分析了.
call 0091D338 //比较关键的call他只是上面分析的算法的一个变种,非常相似,可以自己分析练习练习.
此处我计算了一个:M62DI-DQ0D4-D869F-7U8XA-D3A9M.
注册后发现,关闭再打开又是没注册的了.我这里试用期已过,所以用上面的注册码注册的,虽然
提示注册成功,但是在保存的时候又提示试用期已过,请注册.
看来上面的验证只是正确注册码的一部分啊.而且验证注册码的地方很多.patch不方便.
于是,找到菜单的另存为事件处理地址:_TfrmMain_actSaveAsExecute
此段代码非常长,我先上一个缩减了的代码:
int __usercall TfrmMain_actSaveAsExecute@<eax>(int a1@<eax>, int a2@<edx>, int a3@<ebx>, int a4@<edi>, int a5@<esi>)
{
//IDA变量声明部分略掉.
v348 = a2;
v349 = a1;
v92 = &savedregs;
v91 = &loc_A52C5C;
v90 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v90);
v342 = 1;
v341 = 1; //注意,这里是打开存放图标的对话框的判断标志,看下面的分析.
LOBYTE(a2) = 1;
v340 = TPKVSCheckKey_Create(TPKVSCheckKey, a2);
v89 = &savedregs;
v88 = &loc_A5251B;
v87 = __readfsdword(0);
__writefsdword(0, (unsigned int)&v87);
UStrAsg((wchar_t *)(v340 + 8), *(_DWORD *)(*(_DWORD *)TGlobalSetting + 60));
v338 = v340;
v337 = 0;
*(_BYTE *)(v340 + 4) = 0;
*(_BYTE *)(v338 + 5) = 0;
a1a = v338;
UStrLAsg((int *)&v335, *(_DWORD *)(v338 + 8));
v334 = 1;
v328 = a1a;
UStrLAsg((int *)&v327, v335);
v326 = 0;
LOBYTE(v5) = 1;
v86 = v5;
Sysutils::StringReplace(v327, (int)"-", 0, v179);
UpperCase(*(int *)v179, (int *)&src);
v323 = src;
if ( src )
v323 = (wchar_t *)*((_DWORD *)v323 - 1);
if ( v323 == (wchar_t *)25 )
{
UStrCopy(src, 23, 3, v324);
UStrDelete(&src, 23, 3);
sub_91D170(v328, (int)src, (int)&v177);
sub_921B44(v328, v177, 3, (int)&v178);
UStrEqual(*(wchar_t **)v324, v178);
v7 = 1;
if ( !v6 )
{
sub_91D338(v328, (int)src, (int)&v175, (int)&v176);
sub_921B44(v328, v175, 3, v86);
UStrEqual(*(wchar_t **)v324, v176);
if ( !v6 )
v7 = 0;
}
v326 = v7;
}
if... // business license
LABEL_31:
v337 = v334 == 0; //从下面的分析来看,这里v334必须等于0.可以看下面的分析,上面折叠了的if语句是更改了v334的.
if... // site license
if... // home license
if... // business license
if... // site license
if... // home license
if ( !v337 && *(_DWORD *)(*(_DWORD *)TGlobalSetting + 116) <= 0 ) //v337必须为1,不然存放图标的对话框的判断标志被置为0.也就是说出现试用期过期的提示.
{
if ( v342 )
{
TLanguage_ReadMessage(*TLanguage, (int)"m", (int)&off_A52CE4, (int)&v339); //试用期过期的提示
UStrLAsg((int *)&cchLength, v339);
UStrLAsg(&v185, (int)"|");
UStrLAsg(&v184, (int)L"\r\n");
AnsiReplaceStr(cchLength, v185, v184, (unsigned int)v183);
UStrLAsg(&v339, *(int *)v183);
LOWORD(v48) = 3;
LOBYTE(v49) = 2;
if ( sub_776E98(v339, v49, v48, 0) == 6 )
CallDynaInst(*(_DWORD *)(dword_C31358 + 976));
}
v341 = 0; //只有这里是让v341=0,然后就不会出现存放图标的对话框.
}
__writefsdword(0, v79);
TObject_Free(v340, &loc_A52522);
__writefsdword(0, v79);
TObject_Free(v340, &loc_A52522);
if ( v341 ) // v343==1 --> 存放图标的对话框 ,目标明确了 v341必须为1
{
v50 = TWindowManager_Selected(*TWindowManager);
sub_A0A6F8(v50, (int)&v95);
Sysutils::ExtractFileName(v95, v346); //从函数名称可以看出,这里是打开存放图标的对话框.
ExtractFileExt(*(wchar_t **)v346, v345);
if...
TOpenDialog_SetFileName(*(_DWORD *)(v349 + 1140), *(_DWORD *)v346);
v51 = TWindowManager_Selected(*TWindowManager);
sub_A0A6F8(v51, (int)&v93);
ExtractFileDir(v93, v94);
Dialogs::TOpenDialog::SetInitialDir(*(_DWORD *)(v349 + 1140), *(int *)v94);
v344 = 0;
v52 = TWindowManager_Selected(*TWindowManager);
if...
*(_DWORD *)(*(_DWORD *)(v349 + 1140) + 124) = v344;
if...
}
__writefsdword(0, v80);
UStrArrayClr((int)&v93, 87, (int)&loc_A52C66);
UStrClr(v312);
......
}
上面注释里说的business license是我后面加上去的,这得要自己分析.分析后面再说.
上面的分析是逆向思维式的分析方法,从头往下顺序分析会花掉很多时间并且找不到核心思想.
现在我们分析第一个折叠的if语句:
if ( src )
v323 = (wchar_t *)*((_DWORD *)v323 - 1);
if ( v323 == (wchar_t *)25 )
{
UStrCopy(src, 23, 3, v324);
UStrDelete(&src, 23, 3);
sub_91D170(v328, (int)src, (int)&v177);
sub_921B44(v328, v177, 3, (int)&v178);
UStrEqual(*(wchar_t **)v324, v178);
v7 = 1;
if ( !v6 )
{
sub_91D338(v328, (int)src, (int)&v175, (int)&v176); //此处我们之前分析过
sub_921B44(v328, v175, 3, v86);
UStrEqual(*(wchar_t **)v324, v176); //验证最后三位是否与用前22位计算出来的字符串相等.
if ( !v6 )
v7 = 0;
}
v326 = v7;
}
if ( v326 ) // business license ,第一个折叠了的if语句.
{
Sysutils::StringReplace(v335, (int)"-", 0, v174); //去掉'-'.
UpperCase(*(int *)v174, (int *)&v333);
v330 = 0;
do
{
if ( checkinlist(a1a, (wchar_t *)string_array_24_, v333) ) //这个有点意思,分析起来很简单,只是一个黑名单验证.
{
v334 = 2;
goto LABEL_31;
}
++v330;
}
while ( v330 != 24 );
v334 = 3;
UStrCopy(v333, 1, 6, v173); //复制1-6位注册码到v173用于下面的计算
v322 = *(_DWORD *)v173;
hight = 0;
hight = 0;
*(_DWORD *)&v318 = *(_DWORD *)v173;
if ( *(_DWORD *)v173 )
*(_DWORD *)&v318 = *(_DWORD *)(*(_DWORD *)&v318 - 4);
if ( *(_DWORD *)&v318 > 0 )
{
v319 = *(_DWORD *)&v318; //上面复制的注册码长度.
v320 = 1;
do // sum
{
*(_WORD *)v318 = *(_WORD *)(v322 + 2 * v320 - 2);
HIWORD(v8) = HIWORD(v320);
if ( (unsigned __int16)(*(_WORD *)(v322 + 2 * v320 - 2) - 'a') < 0x1Au )// if bigger than 'z',check if clause
{
HIWORD(v8) = HIWORD(v320);
*(_WORD *)v318 = *(_WORD *)(v322 + 2 * v320 - 2) & 0xFFDF;// = key & 0xffdf
}
LOWORD(v8) = *(_WORD *)v318;
UStrFromWChar(&v172, v8);
v9 = Pos(v172, (int)L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - 1;// wrrong here! 查找字符位置
//注意这里,IDA函数分析在我修正之后依然无法分析出来这里的正确结果
//正确的应该是*(_QWORD *)hight = 36i64 * *(_QWORD *)hight + v9;
*(_QWORD *)hight = 36i64 * *(_QWORD *)hight + 36;
++v320;
--v319;
}
while ( v319 );
}
v331 = hight;
v331 = hight;
UStrCopy(v333, 9, 2, v332);
v329 = sub_91D500(a1a, 10, 0, v331, v331, 0x5C04); //根据1-6位注册码计算出来的结果再次计算出一个数字.
numer_low = v329;
UStrClr(&v316);
do //前面已经给出这个循环的C++源码了.o_o忘记了?
{
v10 = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
LOWORD(v10) = a0123456789a_12[(unsigned int)(numer_low % 36)];
UStrFromWChar(&Source, v10);
UStrCat3((wchar_t *)&v316, (const wchar_t *)Source, v316); //生成字符串
numer_low /= 36i64;
}
while ( numer_low );
sub_921B44(a1a, v316, 2, (int)&v170); //此处只是防止生成的字符不足,不足的话用'0'填充.这里就不分析了.第三个参数为期待的长度.
UStrEqual(*(wchar_t **)v332, v170); //必须相等.
if ( v6 ) //这里v6是zf标志,由UStrEqual来决定,IDA分析不正确.IDA无法设置函数的返回在为zf标志,所以这里有问题,下同.
{
UStrCopy(v333, 11, 2, v332); //复制11-12位注册码到v332用于下面的最终比较
v329 = sub_91D500(a1a, 1, 2, v331, v331, 0x1513C);//根据1-6位注册码计算出来的结果再次计算出一个数字.
*(_QWORD *)numer_hight = v329;
UStrClr(Dest);
do//前面已经给出这个循环的C++源码了.o_o忘记了?
{
v11 = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
LOWORD(v11) = a0123456789a_12[(unsigned int)(*(_QWORD *)numer_hight % 36i64)];
UStrFromWChar(&v169, v11);
UStrCat3((wchar_t *)Dest, v169, *(unsigned int *)Dest);
*(_QWORD *)numer_hight /= 36i64;
}
while ( *(_QWORD *)numer_hight );
sub_921B44(a1a, *(signed __int32 *)Dest, 2, (int)&v168);//此处只是防止生成的字符不足,不足的话用'0'填充.这里就不分析了.第三个参数为期待的长度.
UStrEqual(*(wchar_t **)v332, v168);
if ( v6 )
{
UStrCopy(v333, 17, 2, v332);//复制17-18位注册码到v332用于下面最终比较
v329 = sub_91D500(a1a, 13, 5, v331, v331, 0x9CF84);//根据1-6位注册码计算出来的结果再次计算出一个数字.
*(_QWORD *)v313 = v329;
UStrClr(v312);
do//前面已经给出这个循环的C++源码了.o_o忘记了?
{
v12 = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
LOWORD(v12) = a0123456789a_12[(unsigned int)(*(_QWORD *)v313 % 36i64)];
UStrFromWChar(&v167, v12);
UStrCat3(v312, v167, *(unsigned int *)v312);
*(_QWORD *)v313 /= 36i64;
}
while ( *(_QWORD *)v313 );
sub_921B44(a1a, *(signed __int32 *)v312, 2, (int)&v166);//此处只是防止生成的字符不足,不足的话用'0'填充.这里就不分析了.第三个参数为期待的长度.
UStrEqual(*(wchar_t **)v332, v166);
if ( v6 )
v334 = 0; //看到了吧,这里v334等于0,也就是说下面的v337等于1-->不提示过期-->打开存储对话框.这是我们想要的效果.
}
}
}
LABEL_31:
v337 = v334 == 0;
...下面省略.
现在的重头戏就是 sub_91D500(a1a, 13, 5, v331, v331, 0x9CF84)的算法了.
同样用IDA:
int __fastcall sub_91D500(int key_class, int a2, int a3, int a4, int a5, int a6)
{
int v6; // ebx@2
int v7; // ebx@3
int v9; // @2
int v10; // @1
int v11; // @1
v11 = a2 % 25;
v10 = a3 % 3;
if ( a2 % 25 % 2 )
{
v7 = (unsigned __int8)sub_40B67C(a5, a4, v11);
v9 = a6 & sub_40B67C(a5, a4, v10) ^ v7;
}
else
{
v6 = (unsigned __int8)sub_40B67C(a5, a4, v11);
v9 = (a6 | sub_40B67C(a5, a4, v10)) ^ v6;
}
return v9 % 0x50F;
}
再看sub_40B67C(a5, a4, v11)里面的反汇编代码结果:
// local variable allocation has failed, the output may be wrong!IDA的可能出错提示
int __fastcall sub_40B67C(int result, int a2, int a3)
{
char v3; // cl@1
unsigned int v4; // edx@2
v3 = a3 & 0x3F;
if ( v3 < 32 )
*(_QWORD *)&result >>= v3 & 0x1F;
else
result = v4 >> v3;
return result;
}
其实IDA对于处理64位运算的时候很容易出错,这些代码直接使用是有问题的.怎么办?我的确不想再分析了....直接用程序的汇编代码
写个内联的汇编函数来还原算法.如下:
__int64 KeyGenerator::KeyGen::sum_mapping(int key_class, int a2, int a3, int a4, int a5, int a6)
{
//汇编代码是用OD的ollyext插件弄下来的,注意选择recursive.
__asm
{
push a4
push a5
push a6
push 0h //模拟调用的假返回地址,原汇编肯定没有这句话.这里是为了让下面的本地变量存储正常.返回前需要清除.
mov eax, key_class
mov edx, a2
mov ecx, a3 //上面这几句就是模拟调用原函数用的,下面的代码才是ollyext插件弄下来的.
LOC_FUNCTION_ENTRY_POINT :
jmp LOC_0x0091D500
LOC_0x0040B67C :
and cl, 3Fh
cmp cl, 20h
jl LOC_0x0040B68B
mov eax, edx
xor edx, edx
shr eax, cl
ret
LOC_0x0040B68B :
shrd eax, edx, cl
shr edx, cl
ret
LOC_0x0091D500 :
push ebp
mov ebp, esp
add esp, 0FFFFFFF0h
push ebx
mov dword ptr, ecx
mov dword ptr, edx
mov dword ptr, eax
mov ecx, 00000019h
mov eax, dword ptr
cdq
idiv ecx
mov dword ptr, edx
mov ecx, 00000003h
mov eax, dword ptr
cdq
idiv ecx
mov dword ptr, edx
mov eax, dword ptr
and eax, 80000001h
jns LOC_0x0091D53B
dec eax
or eax, 0FFFFFFFEh
inc eax
LOC_0x0091D53B :
test eax, eax
jne LOC_0x0091D56D
mov eax, dword ptr
mov edx, dword ptr
mov ecx, dword ptr
call LOC_0x0040B67C
mov ebx, eax
and ebx, 000000FFh
mov eax, dword ptr
mov edx, dword ptr
mov ecx, dword ptr
call LOC_0x0040B67C
or eax, dword ptr
xor ebx, eax
mov dword ptr, ebx
jmp LOC_0x0091D599
LOC_0x0091D56D :
mov eax, dword ptr
mov edx, dword ptr
mov ecx, dword ptr
call LOC_0x0040B67C
mov ebx, eax
and ebx, 000000FFh
mov eax, dword ptr
mov edx, dword ptr
mov ecx, dword ptr
call LOC_0x0040B67C
and eax, dword ptr
xor ebx, eax
mov dword ptr, ebx
LOC_0x0091D599 :
mov ecx, 0000050Fh
mov eax, dword ptr
cdq
idiv ecx
mov dword ptr, edx
mov eax, dword ptr
pop ebx
mov esp, ebp
pop ebp
add esp, 10h //add esp, 0Ch+04h 把模拟的假返回地址清除,原始的是add esp, 0Ch.
}
}
总结一下上面分析到的算法:
1.根据1-6位注册码计算出来一个数字A.
2.用数字A经过sub_91D500变换得出另外的一个数字B.
3.将B经过一个循环转换为字符串(2-3个字符).并且与注册码的9-10,11-12,17-18,23-25分别比较.
可以知道,一开始的虚假注册只是验证了注册码的23-25位,重启或者其他功能会进行全部的验证.
这只是business license的注册算法.要问我lincense type在哪里分析得到的?自己动手,很快就知道.
HOME license 和 SITE license 会对business license进行一个可逆变换,从而得出HOME license 和 SITE license.
具体地方自己找吧.我在上面的IDA代码已经有过注释.
不要好奇为什么没怎么用OD.OD在应对大规模的逆向分析时,是个很棒的辅助工具,但是做主要工具很吃力.
附上IcoFX注册码前6位的黑名单(前六位在黑名单里一定不能注册):
ZZZZZZ
71XRKL
AD4QVP
23G0SP
C0HOCT
J3RAOV
DO9L9E
8PPP66
AD91NY
C0SE5Q
7221TF
FBON7B
C0LYAJ
C0HOFN
3QMIEO
72221T
23KB4D
8PH4J5
3QX84U
5EGKS6
DNYVGN
5EGKH3
23I5I1
000000
根据上面的分析和还原了的源代码,是不难写出keygen的.
注册机仅供学习研究用,出于其他目的,就不要下载了.
284406022 发表于 2015-5-28 09:26
IDA的F5我用过,IDR是什么来的? map又是什么来的?映射文件?
这是个好东西,要善用搜索!http://www.52pojie.cn/thread-178147-1-1.html 月之恨 发表于 2015-6-2 11:03
注册成功
需要2个文件,放在软件目录里就可以,可在网上搜索查找。
msvcp120d.dll
非常感谢,测试了,有这两个文件跟注册机同一个目录下,就能正常运行了。 太厉害了。。。 IDR的map给IDA F5用很叼炸天啊! Hmily 发表于 2015-5-26 17:26
IDR的map给IDA F5用很叼炸天啊!
本来想做个xe2的sig签名,但是感觉有些麻烦,就放弃了.老大是刚下班吧 这么快这么多回复了?
先留名,肥了再看
本帖最后由 vigers 于 2015-5-26 19:50 编辑
好长了,留下慢慢看{:1_921:}注册机怎么运行不了??
留名,慢慢欣赏。。。 注册码怎么复制出来啊,全是dos界面好陌生 谢谢你兄弟 感谢分享,,很牛叉