\

简单分析:

1、使用读写禁止读取的内存来触发异常,异常中进行反调试检测及相应处理。

代码:
mov     [ebp+SEH_trigger_ep], 29Ch
mov     ecx, [ebp+SEH_trigger]
mov     byte ptr [ecx], 6
地址[29c]内写入6,来触发异常。

2、反调试,使用查找父进程的方法,如找到父进程非explorer.exe,则终止进程。同时代码中,
有反HideOD的Process32NextW的代码,当发现Process32NextW返回异常,同样终止进程,因此无
法使用HideOD来bypass该反调试。
   处理方式为修改一个跳转使程序不要TerminateProcess就可。
   
3、花指令
  通过花指令来阻碍调试。花指令只使用了一种。
  
代码:
__asm
{
        jmp dword ptr[label1]
label2:  stc
        jnb dword ptr[label3]
        retn
label3:__emit 0xff
label1:  call dword ptr[label2]
}
处理方式为,OD中在label1处f8,就可越过。IDA也可以正确识别该部分代码。

4、MessageBoxA
在通往成功的路径上,使用MessageBoxA拦截
代码:
  if ( MessageBoxA((HWND)(Regcode[10] == 'U'), "继续努力", "错了", 0); )
  {
    pID_currentprocess = GetCurrentProcess();
    TerminateProcess(pID_currentprocess, 0);
  }
  
如果Regcode[10]=='U'则可跨过此障碍。

5、算法
按钮事件,进行用户名和注册码的合法性初步校验,如合法,进入chkreg()
代码:
int __cdecl onCheckBtnClick()
{
  int ecx0; // ecx@0
  int result; // eax@2
  char *regcode; // ST08_4@1
  int regcodeItem; // eax@1
  int len_regcode; // eax@1
  int regnameItem; // eax@1
  int len_regname; // eax@1
  int regname1; // eax@1
  int this; // [sp+5Ch] [bp-4h]@1
  int regcode1; // [sp+58h] [bp-8h]@1
  char *regname2; // [sp+54h] [bp-Ch]@1
  signed int tmpi; // [sp+50h] [bp-10h]@5
  int v12; // [sp+0h] [bp-60h]@12
  int Func_addr; // [sp+4Ch] [bp-14h]@12

  this = ecx0;
  regcode = (char *)(ecx0 + 0x60);
  regcodeItem = CWnd__GetDlgItem(1001);
  CWnd__GetWindowTextA(regcodeItem, regcode);
  len_regcode = getstrlen1();
  regcode1 = CString__GetBuffer(this + 96, len_regcode);
  regnameItem = CWnd__GetDlgItem(1002);
  CWnd__GetWindowTextA(regnameItem, this + 0x64);
  len_regname = getstrlen1();
  regname1 = CString__GetBuffer(this + 100, len_regname);
  regname2 = (char *)regname1;
  if ( strlen((const char *)regname1) )
  {
    if ( strlen((const char *)regcode1) )
    {
      tmpi = 0;
      while ( regname2[tmpi] && regname2[tmpi] > 'a' && regname2[tmpi] < 'z' )
        ++tmpi;
      if ( tmpi == 6 )
      {
        strcpy(&Name, regname2);
        strcpy(Regcode, (const char *)regcode1);
        Func_addr = (int)&v12;
        result = chkreg((int)&v12, (int)regname2, regcode1);
      }
      else
      {
        result = CWnd__MessageBoxA(this, "非法用户", 0, 0);
      }
    }
    else
    {
      result = CWnd__MessageBoxA(this, "请输入注册码!", 0, 0);
    }
  }
  else
  {
    result = CWnd__MessageBoxA(this, "请输入用户名", 0, 0);
  }
  return result;
}
第一级算法
要求:regcode[2n+1]+0x1B=regname[n];
     regcode[2n+2]+0x20<regname[n];
如不符合,修改函数的返回地址为错误提示函数wrong()的地址。
如正确,设置返回地址为chkregcode()。
在函数最后,使用mov [29c],6来触发异常,进入父进程检验的反调试函数。
代码:
int __cdecl chkreg(int Func_addr, int regname, int regcode)
{
  int result; // eax@8
  int v4; // [sp+0h] [bp-6Ch]@1
  int *v5; // [sp+5Ch] [bp-10h]@1
  int chkregcode_addr; // [sp+58h] [bp-14h]@1
  signed int tmpi; // [sp+54h] [bp-18h]@1
  int tmpj; // [sp+50h] [bp-1Ch]@1
  int v9; // [sp+68h] [bp-4h]@8

  v5 = &v4;
  chkregcode_addr = Func_addr - 16;
  *(_DWORD *)(Func_addr - 16) = chkregcode;
  tmpi = 0;
  tmpj = 0;
  loc_401BF4(Func_addr - 16);
  while ( tmpi < 6 )
  {
    if ( *(_BYTE *)(tmpi + regname) != *(_BYTE *)(tmpj + regcode) + 0x1B )
      wrong();
    loc_401C30();
    if ( *(_BYTE *)(tmpi + regname) > *(_BYTE *)(tmpj + regcode + 1) + 0x20 )
      *(_DWORD *)chkregcode_addr = wrong;
    tmpj += 2;
    ++tmpi;
  }
  v9 = 0;
  result = loc_401C7A();
  v29c = 6;
  return result;
}
第二级算法
跨过父进程检测的反调试后,进入第二级算法chkregcode()
代码:
int __cdecl chkregcode()
{
  int tmpB; // ecx@4
  signed int tmpChar; // eax@15
  int v3; // edx@6
  int v4; // eax@10
  HANDLE pID_currentprocess; // eax@11
  signed int v6; // edx@15
  int v7; // ecx@23
  char *charlist; // [sp+88h] [bp-4h]@1
  char *charlist1; // [sp+84h] [bp-8h]@1
  signed int tmpi; // [sp+64h] [bp-28h]@1
  _BYTE chartable[28]; // [sp+68h] [bp-24h]@6
  HANDLE hProcess; // [sp+60h] [bp-2Ch]@11
  signed int v13; // [sp+5Ch] [bp-30h]@12
  signed int tmpj; // [sp+58h] [bp-34h]@12
  int v15; // [sp+54h] [bp-38h]@14
  char *v16; // [sp+50h] [bp-3Ch]@20
  char *v17; // [sp+4Ch] [bp-40h]@20

  charlist  = "ABCDEFGHIJKLMNOPQRSTUVWXY";
  charlist1 = "ABCDEFGHIJKLMNOPQRSTUVWXY";
  tmpi = 0;
  while ( tmpi != 24 )
  {
    loc_40182B();
    if ( Name[0] == *charlist + 32 || Name[5]== *charlist + 32) )
      tmpB = (int)(charlist++ + 1);
    LOBYTE(tmpB) = *charlist;
    chartable[tmpi] = *charlist;
    v3 = tmpi++ + 1;
    charlist += 2;
    loc_401893(tmpB, v3);
    if ( !*charlist )
    {
      if ( tmpi < 24 )
        charlist = charlist1 + 1;
    }
  }
  if ( MessageBoxA((HWND)(Regcode[10] == 'U'), "继续努力", "错了", 0))
  {
    pID_currentprocess = GetCurrentProcess();
    TerminateProcess(pID_currentprocess, 0);
  }
  tmpi = 0;
  v13 = 5;
  tmpj = 0;
  while ( tmpi != 12 )
  {
    v15 = 4 * v13 - 4;
    do
    {
      tmpChar = Regcode[tmpi];
      v6 = chartable[v15++];
      if ( tmpChar == v6 )
        break;
      ++tmpj;
    }
    while ( tmpj <= 4 );
    if ( tmpj == 5 )
    {
      v16 = "ABCDEFGHIJKLMN";
      v17 = "OPQRSTUVWXYZ";
      tmpj = tmpChar;
      if ( tmpChar > 0 )
        wrong();
      wrong();
    }
    tmpi += 2;
    v7 = v13-- - 1;
    loc_4019B2(v7);
    if ( !v13 )
      v13 = 6;
    tmpj = 0;
  }
  return succeed();
}
在第一个循环中为打乱字母表。
取其中的24个字母,每4个为一组,共六组,分别对应名字的6个字符。

注册机
根据以上算法,计算得出名字有如下限制:
1、名字必须为6位字母,名字中字母不能为'a'
2、Name[0]必须为defghij,
3、Name[5]必须为p
4、所有字符必须为打乱的字母表对应组(4个1组)中的一个字母.见算法。
由于不是所有名字均有解。因此采用注册机随机生成注册码的方法,每点一次随机生成一个。
具体算法为

代码:
void CkeygenDlg::OnBnClickedOk()
{
  // TODO: 在此添加控件通知处理程序代码
  char Name[7];
  char RegCode[13];
  char Ctable[26]="ABCDEFGHIJKLMNOPQRSTUVWXY";
  char KeyCtable[26]="";
  char NameTable[6][4];
  char tmpB;

  int tmpi,tmpj,tmpk;
  int tmpRand;

  Name[5]='p';
  Name[6]=0x00;

  //GetDlgItemText(IDC_NAME,&Name[0],2);
  srand( (unsigned)time( NULL ) );
  tmpRand=rand()%7;
  Name[0]='d'+tmpRand;
  if(Name[0]<'d' || Name[0]>'j')
  {
    AfxMessageBox("请设置名字的首字母为defghij其中的一个,否则无解");
    return;
  }
  //Create Keytable
  tmpj=0;
  for(tmpi=0;tmpi<=24;tmpi++)
  {
    if(Name[0]==(Ctable[tmpj]+0x20)||Name[5]==(Ctable[tmpj]+0x20))
    {
      tmpj=(tmpj+1)%25;
    }
    tmpB=Ctable[tmpj];
    KeyCtable[tmpi]=tmpB;
    tmpj=(tmpj+2)%25;
  }
  //Create Nametable
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[0][tmpj]=KeyCtable[16+tmpj]+0x1B;
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[1][tmpj]=KeyCtable[12+tmpj]+0x1B;
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[2][tmpj]=KeyCtable[8+tmpj]+0x1B;
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[3][tmpj]=KeyCtable[4+tmpj]+0x1B;
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[4][tmpj]=KeyCtable[0+tmpj]+0x1B;
  for(tmpj=0;tmpj<4;tmpj++)
    NameTable[5][tmpj]=KeyCtable[20+tmpj]+0x1B;
  //Generate Name
  for(tmpi=1;tmpi<5;tmpi++)
  {
    srand( (unsigned)time( NULL ) );
    tmpRand=rand()%4;
    for(tmpj=tmpRand;tmpj<(tmpRand+4);tmpj++)
    {
      if(NameTable[tmpi][tmpj%4]>'a'&&NameTable[tmpi][tmpj%4]<'z')
        Name[tmpi]=NameTable[tmpi][tmpj%4];
    }
  }
  Name[5]='p';
  Name[6]=0x00;
  SetDlgItemText(IDC_NAME,&Name[0]);
  //Generate Regcode
  for(tmpi=0;tmpi<6;tmpi++)
  {
    RegCode[2*tmpi]=Name[tmpi]-0x1B;
    RegCode[2*tmpi+1]=Name[tmpi]-0x20;
  }
  RegCode[12]=0x00;
  SetDlgItemText(IDC_REGCODE,&RegCode[0]);
  SetDlgItemText(IDOK,"NextOne");
}
上传的附件 keygen.rar

  • 标 题:答复
  • 作 者:shellwolf
  • 时 间:2008-07-11 23:17

第一次写keygen。
里面的随机种子用的time取的秒,所以每秒只能生成一组注册码,按钮只能每隔一秒点一次,
当时如果取ms做种子就好了。

  • 标 题:答复
  • 作 者:mstwugui
  • 时 间:2008-07-24 14:05

随机种子用GetTickCount()就好,分析写的不错,赞