currwin 发表于 2014-6-2 10:21

[算法分析] 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:本来想写好在儿童节发的啊,没想到真的写好了的时候已经到了端午节了啊。
      今天有没有粽子收啊粽子。
放一份到论坛上面备份:













Passerby 发表于 2014-6-2 10:34

{:301_971:}粽子都没吃上,感谢楼主的分析,算法一直都是我的硬伤

Some 发表于 2014-6-2 10:38

不敢吃粽子。

currwin 发表于 2014-6-2 10:40

Passerby 发表于 2014-6-2 10:34
粽子都没吃上,感谢楼主的分析,算法一直都是我的硬伤

也是我的硬伤{:301_999:}不过不玩算法的话就一直成为不了大牛

Avenshy 发表于 2014-6-2 13:56

啧啧!对于注册码分析之类的是我的软肋啊!特别是注册机什么的及就更不会了!

lao_jin 发表于 2014-6-2 15:01

虽然很详细我这个入门尚浅的学童表示吃不消。
页: [1]
查看完整版本: [算法分析] XorRanger's KeygenME #5