红绡枫叶 发表于 2015-5-26 11:18

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的.
注册机仅供学习研究用,出于其他目的,就不要下载了.

Hmily 发表于 2015-5-28 11:52

284406022 发表于 2015-5-28 09:26
IDA的F5我用过,IDR是什么来的?    map又是什么来的?映射文件?

这是个好东西,要善用搜索!http://www.52pojie.cn/thread-178147-1-1.html

kill 发表于 2015-6-2 19:54

月之恨 发表于 2015-6-2 11:03
注册成功
需要2个文件,放在软件目录里就可以,可在网上搜索查找。
msvcp120d.dll


非常感谢,测试了,有这两个文件跟注册机同一个目录下,就能正常运行了。

bytzhh 发表于 2015-5-26 12:59

太厉害了。。。

Hmily 发表于 2015-5-26 17:26

IDR的map给IDA F5用很叼炸天啊!

红绡枫叶 发表于 2015-5-26 17:31

Hmily 发表于 2015-5-26 17:26
IDR的map给IDA F5用很叼炸天啊!

本来想做个xe2的sig签名,但是感觉有些麻烦,就放弃了.老大是刚下班吧

asd9988 发表于 2015-5-26 18:37

这么快这么多回复了?
先留名,肥了再看

vigers 发表于 2015-5-26 19:12

本帖最后由 vigers 于 2015-5-26 19:50 编辑

好长了,留下慢慢看{:1_921:}注册机怎么运行不了??

ljrlove2008 发表于 2015-5-26 20:16

留名,慢慢欣赏。。。

zz100179 发表于 2015-5-26 21:08

注册码怎么复制出来啊,全是dos界面好陌生

MT小青菜 发表于 2015-5-26 21:14

谢谢你兄弟

研究技术 发表于 2015-5-26 23:41

感谢分享,,很牛叉
页: [1] 2 3 4 5 6 7 8
查看完整版本: IcoFx2.11算法分析&keygen编写