欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]IcoFx2.11算法分析&keygen编写

[原创]IcoFx2.11算法分析&keygen编写

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:[eax]
 00A3ADC1    mov        dword ptr fs:[eax],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,[ebp-4C]
 00A3ADE2    call       TClipboard.GetAsText //获取剪切板内容
 00A3ADE7    mov        eax,dword ptr [ebp-4C]
 00A3ADEA    lea        edx,[ebp-48]
 00A3ADED    call       Trim        //去掉空格等.
 00A3ADF2    mov        edx,dword ptr [ebp-48]
 00A3ADF5    mov        eax,dword ptr [ebp-4]
 00A3ADF8    mov        eax,dword ptr [eax+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3ADFE    add        eax,8; TPKVSCheckKey.FKey:string
 00A3AE01    call       @UStrAsg //这是字符串复制用的函数.
 00A3AE06    mov        eax,dword ptr [ebp-4]
 00A3AE09    mov        eax,dword ptr [eax+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3AE0F    mov        dword ptr [ebp-0C],eax
 00A3AE12    lea        eax,[ebp-10]
 00A3AE15    mov        edx,dword ptr [ebp-4]
 00A3AE18    mov        edx,dword ptr [edx+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3AE1E    mov        edx,dword ptr [edx+8]; TPKVSCheckKey.FKey:string
 00A3AE21    call       @UStrLAsg
 00A3AE26    mov        byte ptr [ebp-11],0
 00A3AE2A    mov        al,[00A3B144]; 0x1
 00A3AE2F    push       eax
 00A3AE30    lea        eax,[ebp-50]
 00A3AE33    push       eax
 00A3AE34    xor        ecx,ecx
 00A3AE36    mov        edx,0A3B154; '-'
 00A3AE3B    mov        eax,dword ptr [ebp-10]
 00A3AE3E    call       StringReplace //去掉'-'
 00A3AE43    mov        eax,dword ptr [ebp-50]
 00A3AE46    lea        edx,[ebp-18]
 00A3AE49    call       UpperCase
 00A3AE4E    mov        eax,dword ptr [ebp-18]
 00A3AE51    mov        dword ptr [ebp-20],eax
 00A3AE54    cmp        dword ptr [ebp-20],0 //去掉'-',空格等之后,判断是否为空.
>00A3AE58    je         00A3AE65
 00A3AE5A    mov        eax,dword ptr [ebp-20]
 00A3AE5D    sub        eax,4
 00A3AE60    mov        eax,dword ptr [eax]
 00A3AE62    mov        dword ptr [ebp-20],eax
 00A3AE65    cmp        dword ptr [ebp-20],19 //判断是否是25个字符,测试的时候输入假验证码不就是25个嘛,要联想.
>00A3AE69    jne        00A3AEFE
 00A3AE6F    lea        eax,[ebp-1C]
 00A3AE72    push       eax
 00A3AE73    mov        ecx,3
 00A3AE78    mov        edx,17
 00A3AE7D    mov        eax,dword ptr [ebp-18]
 00A3AE80    call       @UStrCopy  //字符串复制到 dword ptr [ebp-18]
 00A3AE85    lea        eax,[ebp-18]
 00A3AE88    mov        ecx,3
 00A3AE8D    mov        edx,17
 00A3AE92    call       @UStrDelete //删除第23,24,25个字符.也就是删除最后三个字符.汇编中数字都是16进制的!
 00A3AE97    lea        eax,[ebp-54]
 00A3AE9A    push       eax
 00A3AE9B    lea        ecx,[ebp-58]
 00A3AE9E    mov        edx,dword ptr [ebp-18]
 00A3AEA1    mov        eax,dword ptr [ebp-0C]
 00A3AEA4    call       0091D170 
 00A3AEA9    mov        edx,dword ptr [ebp-58]
 00A3AEAC    mov        ecx,3
 00A3AEB1    mov        eax,dword ptr [ebp-0C]
 00A3AEB4    call       00921B44
 00A3AEB9    mov        edx,dword ptr [ebp-54]
 00A3AEBC    mov        eax,dword ptr [ebp-1C]
 00A3AEBF    call       @UStrEqual //比较字符串
>00A3AEC4    je         00A3AEF9 //相等就跳,看下面跳的地方有TfrmRegister.btnPaste:TPngSpeedButton,TControl.SetEnabled
 00A3AEC6    lea        eax,[ebp-5C] //就知道让粘贴的图标按钮有效.
 00A3AEC9    push       eax
 00A3AECA    lea        ecx,[ebp-60]
 00A3AECD    mov        edx,dword ptr [ebp-18]
 00A3AED0    mov        eax,dword ptr [ebp-0C]
 00A3AED3    call       0091D338
 00A3AED8    mov        edx,dword ptr [ebp-60]
 00A3AEDB    mov        ecx,3
 00A3AEE0    mov        eax,dword ptr [ebp-0C]
 00A3AEE3    call       00921B44
 00A3AEE8    mov        edx,dword ptr [ebp-5C]
 00A3AEEB    mov        eax,dword ptr [ebp-1C]
 00A3AEEE    call       @UStrEqual
>00A3AEF3    je         00A3AEF9
 00A3AEF5    xor        eax,eax
>00A3AEF7    jmp        00A3AEFB
 00A3AEF9    mov        al,1
 00A3AEFB    mov        byte ptr [ebp-11],al
 00A3AEFE    mov        al,byte ptr [ebp-11]
 00A3AF01    mov        byte ptr [ebp-5],al
 00A3AF04    mov        eax,dword ptr [ebp-4]
 00A3AF07    mov        eax,dword ptr [eax+3D0]; TfrmRegister.btnPaste:TPngSpeedButton
 00A3AF0D    mov        dl,byte ptr [ebp-5]
 00A3AF10    mov        ecx,dword ptr [eax]
 00A3AF12    call       dword ptr [ecx+80]; TControl.SetEnabled
>00A3AF18    jmp        00A3AF2D
 00A3AF1A    mov        eax,dword ptr [ebp-4]
 00A3AF1D    mov        eax,dword ptr [eax+3D0]; TfrmRegister.btnPaste:TPngSpeedButton
 00A3AF23    xor        edx,edx
 00A3AF25    mov        ecx,dword ptr [eax]
 00A3AF27    call       dword ptr [ecx+80]; TControl.SetEnabled
 00A3AF2D    xor        eax,eax
 00A3AF2F    pop        edx
 00A3AF30    pop        ecx
 00A3AF31    pop        ecx
 00A3AF32    mov        dword ptr fs:[eax],edx
>00A3AF35    jmp        00A3AF54
<00A3AF37    jmp        @HandleAnyException
 00A3AF3C    mov        eax,dword ptr [ebp-4]
 00A3AF3F    mov        eax,dword ptr [eax+3D0]; TfrmRegister.btnPaste:TPngSpeedButton
 00A3AF45    xor        edx,edx
 00A3AF47    mov        ecx,dword ptr [eax]
 00A3AF49    call       dword ptr [ecx+80]; TControl.SetEnabled
 00A3AF4F    call       @DoneExcept
 00A3AF54    lea        edx,[ebp-64]
 00A3AF57    mov        eax,dword ptr [ebp-4]
 00A3AF5A    mov        eax,dword ptr [eax+390]; TfrmRegister.KeyEdit1:TKeyEdit
 00A3AF60    call       TKeyEdit.GetKey
 00A3AF65    mov        edx,dword ptr [ebp-64]
 00A3AF68    mov        eax,dword ptr [ebp-4]
 00A3AF6B    mov        eax,dword ptr [eax+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3AF71    add        eax,8; TPKVSCheckKey.FKey:string
 00A3AF74    call       @UStrAsg
 00A3AF79    lea        edx,[ebp-6C]
 00A3AF7C    mov        eax,dword ptr [ebp-4]
 00A3AF7F    mov        eax,dword ptr [eax+3A8]; TfrmRegister.edtName:TEdit
 00A3AF85    call       TControl.GetText
 00A3AF8A    mov        eax,dword ptr [ebp-6C]
 00A3AF8D    lea        edx,[ebp-68]
 00A3AF90    call       Trim
 00A3AF95    cmp        dword ptr [ebp-68],0
>00A3AF99    je         00A3B0A5
 00A3AF9F    mov        eax,dword ptr [ebp-4]
 00A3AFA2    mov        eax,dword ptr [eax+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3AFA8    mov        dword ptr [ebp-28],eax
 00A3AFAB    lea        eax,[ebp-2C]
 00A3AFAE    mov        edx,dword ptr [ebp-4]
 00A3AFB1    mov        edx,dword ptr [edx+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3AFB7    mov        edx,dword ptr [edx+8]; TPKVSCheckKey.FKey:string
 00A3AFBA    call       @UStrLAsg
 00A3AFBF    mov        byte ptr [ebp-2D],0
 00A3AFC3    mov        al,[00A3B144]; 0x1
 00A3AFC8    push       eax
 00A3AFC9    lea        eax,[ebp-70]
 00A3AFCC    push       eax
 00A3AFCD    xor        ecx,ecx
 00A3AFCF    mov        edx,0A3B154; '-'
 00A3AFD4    mov        eax,dword ptr [ebp-2C]
 00A3AFD7    call       StringReplace
 00A3AFDC    mov        eax,dword ptr [ebp-70]
 00A3AFDF    lea        edx,[ebp-34]
 00A3AFE2    call       UpperCase
 00A3AFE7    mov        eax,dword ptr [ebp-34]
 00A3AFEA    mov        dword ptr [ebp-3C],eax
 00A3AFED    cmp        dword ptr [ebp-3C],0
>00A3AFF1    je         00A3AFFE
 00A3AFF3    mov        eax,dword ptr [ebp-3C]
 00A3AFF6    sub        eax,4
 00A3AFF9    mov        eax,dword ptr [eax]
 00A3AFFB    mov        dword ptr [ebp-3C],eax
 00A3AFFE    cmp        dword ptr [ebp-3C],19
>00A3B002    jne        00A3B097
 00A3B008    lea        eax,[ebp-38]
 00A3B00B    push       eax
 00A3B00C    mov        ecx,3
 00A3B011    mov        edx,17
 00A3B016    mov        eax,dword ptr [ebp-34]
 00A3B019    call       @UStrCopy  //复制最后三位到dword ptr [ebp-34]
 00A3B01E    lea        eax,[ebp-34]
 00A3B021    mov        ecx,3
 00A3B026    mov        edx,17
 00A3B02B    call       @UStrDelete
 00A3B030    lea        eax,[ebp-74]
 00A3B033    push       eax
 00A3B034    lea        ecx,[ebp-78]
 00A3B037    mov        edx,dword ptr [ebp-34] //放的是验证注册码的最后三位
 00A3B03A    mov        eax,dword ptr [ebp-28]
 00A3B03D    call       0091D170  //比较关键的call
 00A3B042    mov        edx,dword ptr [ebp-78]
 00A3B045    mov        ecx,3
 00A3B04A    mov        eax,dword ptr [ebp-28]
 00A3B04D    call       00921B44//比较关键的call
 00A3B052    mov        edx,dword ptr [ebp-74]
 00A3B055    mov        eax,dword ptr [ebp-38]
 00A3B058    call       @UStrEqual  //上面一段和一开始的分析是一样的,所以就省略了.
>00A3B05D    je         00A3B092 //同理,这里跳转的话,注册按钮就会被激活.
 00A3B05F    lea        eax,[ebp-7C]
 00A3B062    push       eax
 00A3B063    lea        ecx,[ebp-80]
 00A3B066    mov        edx,dword ptr [ebp-34]
 00A3B069    mov        eax,dword ptr [ebp-28]
 00A3B06C    call       0091D338 //比较关键的call
 00A3B071    mov        edx,dword ptr [ebp-80]
 00A3B074    mov        ecx,3
 00A3B079    mov        eax,dword ptr [ebp-28]
 00A3B07C    call       00921B44 //比较关键的call
 00A3B081    mov        edx,dword ptr [ebp-7C]
 00A3B084    mov        eax,dword ptr [ebp-38]
 00A3B087    call       @UStrEqual
>00A3B08C    je         00A3B092 //同理,这里跳转的话,注册按钮就会被激活.
 00A3B08E    xor        eax,eax
>00A3B090    jmp        00A3B094
 00A3B092    mov        al,1
 00A3B094    mov        byte ptr [ebp-2D],al
 00A3B097    mov        al,byte ptr [ebp-2D]
 00A3B09A    mov        byte ptr [ebp-21],al
 00A3B09D    mov        al,byte ptr [ebp-21]
 00A3B0A0    mov        byte ptr [ebp-3D],al
>00A3B0A3    jmp        00A3B0A9
 00A3B0A5    mov        byte ptr [ebp-3D],0
 00A3B0A9    cmp        byte ptr [ebp-3D],0
>00A3B0AD    je         00A3B0C4
 00A3B0AF    mov        eax,dword ptr [ebp-4]
 00A3B0B2    mov        eax,dword ptr [eax+3D8]; TfrmRegister.KeyChecker:TPKVSCheckKey
 00A3B0B8    call       TPKVSCheckKey.IsBlacklisted
 00A3B0BD    xor        al,1
 00A3B0BF    mov        byte ptr [ebp-3E],al
>00A3B0C2    jmp        00A3B0C8
 00A3B0C4    mov        byte ptr [ebp-3E],0
 00A3B0C8    mov        eax,dword ptr [ebp-4]
 00A3B0CB    mov        eax,dword ptr [eax+3A0]; TfrmRegister.btnRegister:TButton
 00A3B0D1    mov        dl,byte ptr [ebp-3E]
 00A3B0D4    mov        ecx,dword ptr [eax]
 00A3B0D6    call       dword ptr [ecx+80]; TControl.SetEnabled
 00A3B0DC    xor        eax,eax
 00A3B0DE    pop        edx
 00A3B0DF    pop        ecx
 00A3B0E0    pop        ecx
 00A3B0E1    mov        dword ptr fs:[eax],edx
 00A3B0E4    push       0A3B13D
 00A3B0E9    lea        eax,[ebp-80]
 00A3B0EC    mov        edx,5
 00A3B0F1    call       @UStrArrayClr
 00A3B0F6    lea        eax,[ebp-6C]
 00A3B0F9    call       @UStrClr
 00A3B0FE    lea        eax,[ebp-68]
 00A3B101    mov        edx,9
 00A3B106    call       @UStrArrayClr
 00A3B10B    lea        eax,[ebp-38]
 00A3B10E    mov        edx,2
 00A3B113    call       @UStrArrayClr
 00A3B118    lea        eax,[ebp-2C]
 00A3B11B    call       @UStrClr
 00A3B120    lea        eax,[ebp-1C]
 00A3B123    mov        edx,2
 00A3B128    call       @UStrArrayClr
 00A3B12D    lea        eax,[ebp-10]
 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) [COLOR="Red"]//参数个数要自己分析指定[/COLOR]
{
  void *v3; // edx@14
  unsigned __int32 v5; // [sp-Ch] [bp-48h]@1
  _UNKNOWN *v6; // [sp-8h] [bp-44h]@1
  int *v7; // [sp-4h] [bp-40h]@1
  char *Source; // [sp+4h] [bp-38h]@1
  unsigned int MaxLen; // [sp+8h] [bp-34h]@1
  int v10[2]; // [sp+Ch] [bp-30h]@13
  char *v11; // [sp+18h] [bp-24h]@4
  int v12; // [sp+1Ch] [bp-20h]@1
  char *v13; // [sp+20h] [bp-1Ch]@7
  int v14; // [sp+24h] [bp-18h]@7
  unsigned __int16 v15; // [sp+2Ah] [bp-12h]@13
  unsigned __int16 v16; // [sp+2Ch] [bp-10h]@1
  unsigned __int16 v17; // [sp+2Eh] [bp-Eh]@1
  char *v19; // [sp+34h] [bp-8h]@1
  int v20; // [sp+38h] [bp-4h]@1
  int savedregs; // [sp+3Ch] [bp+0h]@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[2 * v14 - 2];
        if ( v16 > 0xFFu )
          v16 -= 255;
        v17 += v16;
        if ( v17 > 0xFFu )
          v17 -= 255;
        ++v14;
        --v13;
      }
      while ( v13 );
    }
  }
  v15 = (unsigned __int16)(v16 + (v17 << 8)) % 0xB63Fu;
  v10[0] = v15;
  v10[1] = 0;
  UStrClr(&MaxLen);
  do
  {
    v3 = L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    LOWORD(v3) = a0123456789abcd[(unsigned int)(*(_QWORD *)v10 % 36i64)];[COLOR="Red"] //64位求余,这里注意了,
	// 函数原型为__int64 __usercall _llmod@<edx:eax>(int numer_low@<eax>, int numer_hight@<edx>, int denom_low, int denom_hight);
	//IDA自己分析是失败的,所以要自己手动设置函数原型.[/COLOR]
    UStrFromWChar(&Source, v3);
    UStrCat3((wchar_t *)&MaxLen, (const wchar_t *)Source, MaxLen);
    *(_QWORD *)v10 /= 36i64;[COLOR="Red"]//64位除法
	//此处也要注意,IDA自己分析是失败的
	//函数原型为 __int64 __usercall _lldiv@<edx:eax>(int numer_low@<eax>, int numer_hight@<edx>, int denom_low, int denom_hight);[/COLOR]
  }
  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[iter - 1];
			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[COLOR="Red"] //注意,下面多处用到这里的代码.[/COLOR]
	{
		result.insert(0, 1, m_alphabet[numr % 36i64]);
		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[0]);
    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[0]);
    sub_A0A6F8(v51, (int)&v93);
    ExtractFileDir(v93, v94);
    Dialogs::TOpenDialog::SetInitialDir(*(_DWORD *)(v349 + 1140), *(int *)v94);
    v344 = 0;
    v52 = TWindowManager_Selected(*TWindowManager[0]);
    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_[v330], 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] = 0;
    hight[1] = 0;
    *(_DWORD *)&v318[2] = *(_DWORD *)v173;
    if ( *(_DWORD *)v173 )
      *(_DWORD *)&v318[2] = *(_DWORD *)(*(_DWORD *)&v318[2] - 4);
    if ( *(_DWORD *)&v318[2] > 0 )
    {
      v319 = *(_DWORD *)&v318[2]; //上面复制的注册码长度.
      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[i] & 0xffdf
        }
        LOWORD(v8) = *(_WORD *)v318;
        UStrFromWChar(&v172, v8);
        v9 = Pos(v172, (int)L"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - 1;[COLOR="Red"]// wrrong here! 查找字符位置
		//注意这里,IDA函数分析在我修正之后依然无法分析出来这里的正确结果
		//正确的应该是*(_QWORD *)hight = 36i64 * *(_QWORD *)hight + v9;[/COLOR]
        *(_QWORD *)hight = 36i64 * *(_QWORD *)hight + 36;
        ++v320;
        --v319;
      }
      while ( v319 );
    }
    v331[0] = hight[0];
    v331[1] = hight[1];
    UStrCopy(v333, 9, 2, v332);
    v329 = sub_91D500(a1a, 10, 0, v331[1], v331[0], 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[1], v331[0], 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[1], v331[0], 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[1], v331[0], 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; // [sp+4h] [bp-10h]@2
  int v10; // [sp+8h] [bp-Ch]@1
  int v11; // [sp+Ch] [bp-8h]@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 [COLOR="Red"]//模拟调用的假返回地址,原汇编肯定没有这句话.这里是为了让下面的本地变量存储正常.返回前需要清除.[/COLOR]
			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[ebp - 0Ch], ecx
			mov dword ptr[ebp - 08h], edx
			mov dword ptr[ebp - 04h], eax
			mov ecx, 00000019h
			mov eax, dword ptr[ebp - 08h]
			cdq
			idiv ecx
			mov dword ptr[ebp - 08h], edx
			mov ecx, 00000003h
			mov eax, dword ptr[ebp - 0Ch]
			cdq
			idiv ecx
			mov dword ptr[ebp - 0Ch], edx
			mov eax, dword ptr[ebp - 08h]
			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[ebp + 0Ch]
			mov edx, dword ptr[ebp + 10h]
			mov ecx, dword ptr[ebp - 08h]
			call LOC_0x0040B67C
			mov ebx, eax
			and ebx, 000000FFh
			mov eax, dword ptr[ebp + 0Ch]
			mov edx, dword ptr[ebp + 10h]
			mov ecx, dword ptr[ebp - 0Ch]
			call LOC_0x0040B67C
			or eax, dword ptr[ebp + 08h]
			xor ebx, eax
			mov dword ptr[ebp - 10h], ebx
			jmp LOC_0x0091D599
		LOC_0x0091D56D :
		mov eax, dword ptr[ebp + 0Ch]
			mov edx, dword ptr[ebp + 10h]
			mov ecx, dword ptr[ebp - 08h]
			call LOC_0x0040B67C
			mov ebx, eax
			and ebx, 000000FFh
			mov eax, dword ptr[ebp + 0Ch]
			mov edx, dword ptr[ebp + 10h]
			mov ecx, dword ptr[ebp - 0Ch]
			call LOC_0x0040B67C
			and eax, dword ptr[ebp + 08h]
			xor ebx, eax
			mov dword ptr[ebp - 10h], ebx
		LOC_0x0091D599 :
		mov ecx, 0000050Fh
			mov eax, dword ptr[ebp - 10h]
			cdq
			idiv ecx
			mov dword ptr[ebp - 10h], edx
			mov eax, dword ptr[ebp - 10h]
			pop ebx
			mov esp, ebp
			pop ebp
			add esp, 10h [COLOR="Red"]//add esp, 0Ch+04h 把模拟的假返回地址清除,原始的是add esp, 0Ch.[/COLOR]
	}
}

总结一下上面分析到的算法:
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的.
注册机仅供学习研究用,出于其他目的,就不要下载了.

上传的附件 注册.png
试用期.png
成功.png
IcoFXKeyGen.7z

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功