010Editor-14.0.1 破解思路及其注册机编写,全网最详细注册机代码教学讲解

010Editor-14.0.1 破解思路及其注册机编写,注册机代码逆推讲解🐶

参考博客: https://bbs.kanxue.com/thread-270316.htm

010Editor官网下载链接: https://www.sweetscape.com/010editor/

010Editor是一款较为强大的16进制编辑器

破解思路分析

查壳

通过Exeinfo PE工具进行查壳操作,可以直接看到010Editor的.text,因此其010没有加壳。

img

运行010Editor.exe,尝试输入随机的name以及License可以看到,010Editor将会在界面上显示错误的提示信息,因此可以尝试搜索字符串常量,以进行下一步的分析。

img

反汇编分析

使用IDA pro进行逆向分析,将010Editor.exe拖入IDA pro,等待IDA将其分析完毕后,检索字符串“Invalid name or license”,成功找到我们想要的与错误信息有关的字符串

img

右键其地址,按Ctrl+x可以看到程序中只有一个地方引用了这个字符串,在函数sub_140213400中

img

将函数sub_140213400改名为Licese_judeg,同时查看函数的执行流程,可以看到,引用该字符串的位置为该函数众多分支中的一个极小的代码块,执行该代码块后,函数会退出,因此可以大致确定,该函数应该为软件的License验证模块,因此可以在该函数的其他分支中找到通过验证的代码块,然后根据数据的流向,找到计算License合法性的代码,然后对代码进行逆推,完成注册机的编写。

img

成功在函数中找到通过License验证的代码块

img

查看代码块在函数中的位置

img

根据程序控制逻辑,找到控制其跳转的代码

img

根据数据流,找到edi的值由哪个函数计算得到,可以看到,不管jz loc_140213865走哪条分支,要想到达通过验证的代码块,最终都要通过cmp edi,0xDB的判断,且此时edi的值都由函数sub_140008652计算得到,0xDB十进制为219,因此我们需要函数sub_140008652返回219,将函数改名为need_back_219

img

进入函数后发现,其仅仅起一个jump的作用,真真返回计算结果的是a_func_need_back_219的函数,进一步分析return语句,可以看到,要想返回219,需要函数sub_14000600F返回45,从case:45返回219

img

此时为了验证我们的分析思路是否正确,调试程序,当程序运行到这几处关键跳转时,采用强制修改寄存器的方法,观察程序是否会达到我们预期的License accept的位置

第一处可以到达cmp edi,0xDB的跳转

img

此时需要修改ZF的值,否则程序将会执行到输出Invalid License的代码端

img

可以看到修改cmp edi,0xDB的比较结果后,程序运行到我们预期的位置

img

继续运行,程序结束后,可以看到010Editor激活成功,说明我们找到的关键跳转的位置正确,此时可以将cmp edi,0xDB的跳转条件取反,使用patch后,修改程序,即可完成破解

img

回到原来的分析思路,进一步跟进函数

img

分析起return语句可以看到,result的值由赋值语句指定,分析其数据流,发现,只有程序经过LABEL_33,函数才会返回45,

img

可以看到,在函数中,只有一处引用了LABEL_33

img

分析其跳转逻辑,可以发现有两条路径到达LABEL_33,一条为不通过1处判断,但是通过2处判断,另一条为不通过1处判断,但是通过3处判断

img

进一步分析函数,可以看到,其后半段内容为一个switch,有三个case分别为0x9C,0xAC,0xFC,三个case最终都会go to到LABEL_26,然后根据0x9C,0xAC,0xFC的不同执行不同的判断逻辑

img
img

但是在执行LABEL_26时,需要通过一个条件判断,如果不通过直接返回219,退出函数,所以为了达成我们的目的,需要通过这个判,进一步观察可以发现该判断通过的条件为函数sub_14000492B的运算结果的由低到高以字节为单位,分别与v43,v14,v45,v46相等

img

到此继续静态分析,已经无法得到有效的信息了,所以此时需要进行动态调试,在动态调试的过程中,关注关键的函数与跳转,追踪我们输入的Name与License进行了哪些操作与计算。
构造一个具有规律性的数字输入

img

经过在关键函数内的单步调试,找到了华点

img

v39,v40,v41,v42分别存储了我输入密码的前8个数字,转到其存储地址,可以看到我构造的密码,被顺序存储为HEX的格式,同时结合该函数的局部变量声明的顺序,可知v39~v48存储了输入的20位密码的HEX形式

img

经过前面的分析,想要进一步分析逻辑信息,需要匹配进入三个case分别为0x9C,0xAC,0xFC,此处选择了AC的情况,构造License,再次调试,可是仍旧无法进入到LABEL_26代码块,此时选择修改寄存器的值,改变跳转的结果,使得程序进一步深入

img

此时修改ZF的值为1

img

程序成功运行到LABEL_26,此时可以看到程序获取了我输入的用户名,作为函数sub_7FF639EB492B的一个输入

img
img

最终经过反复的调试与分析,恢复变量的名称与类型,函数的名称与定义后,终于梳理清楚了这个核心函数的算法流程,只需要关注与AC分支有关的程序执行逻辑即可

img
img
img

可以看到程序的流程图中,关键函数有三个,关键跳转也有三个,逆推程序的执行流程即可,完成注册机的编写

img

注册机编写

由于dotheuser函数的参数的都可以有一个确定的可行范围,if_AC_always_one为一个标志位,当passwd属于AC版本时,其值恒为1

a_32bit_var_from_username = j_dotheuser(username, if_AC_always_one, the_ACflag, *(_DWORD *)(array + 48));// 
// 函数将一个字符串以及一些标志位作为输入,最后输出一个64位的数据,猜测该函数可能为一个自定义的hash函数
// array+48为函数do_a_16bit的16bit返回值

参数the_ACflag要求大于一个特定的时间戳:0x4c71u 如果以天为单位表示 2023-07-31 00:00:00,但是在其实际运算过程中,ACflag的取值还需要满足一些特定条件,在后面分析计算ACflag的函数会进一步说明

if ( the_least_time > ACflag )    
// the_least_time是系统传递进来的一个恒定的值,可能是一个时间戳temp_a3=0x4c71u
{
  the_vable_con_return_45 = 78;
  goto the_lable_return;
}
goto the_lable_get_45;

*(_DWORD *)(array + 48)是函数do_a_16bit的输出结果,程序逻辑要求其小于1000,同样的,var_from_do_a_16_bit的取值还需要满足一些特定条件,在后面分析计算var_from_do_a_16_bit的函数时会进一步说明

*(_DWORD *)(array + 48) = var_from_do_a_16_bit;
if ( (unsigned int)var_from_do_a_16_bit - 1 <= 999 ){
    //省略其他操作
}

ida 反编译得到的dotheuser函数

__int64 __cdecl dotheuser(char *username, int alwaya_one, int ACflag, int var_from_do_a_16_bit)
{
  unsigned int flag; // ebp
  __int64 number_of_the_username; // rax
  __int64 number_the_username; // r13
  int index; // ebx
  unsigned __int8 v9; // r14
  unsigned __int8 v10; // si
  unsigned __int8 v11; // r15
  unsigned __int8 v12; // di
  int v13; // eax
  _DWORD *v14; // r9
  unsigned int v15; // r11d
  _DWORD *v16; // r10
  int v17; // ebp
  __int64 v18; // rcx
  __int64 v19; // rax

  flag = 0;
  number_of_the_username = -1i64;
  do
    ++number_of_the_username;
  while ( username[number_of_the_username] );
  number_the_username = (int)number_of_the_username;
  if ( (int)number_of_the_username > 0 )
  {
    *(_QWORD *)&index = 0i64;
    v9 = 0;
    v10 = 15 * var_from_do_a_16_bit;
    v11 = 0;
    v12 = 17 * ACflag;
    do                                          // 用username做了一个表代替
    {
      v13 = toupper((unsigned __int8)username[*(_QWORD *)&index]);// 将每个用户名字母变为大写
      v14 = &flagarray[v12];
      v15 = flag + flagarray[v13];
      v16 = &flagarray[v10];
      if ( alwaya_one )
      {
        v17 = flagarray[(unsigned __int8)(v13 + 13)];
        v18 = (unsigned __int8)(v13 + 47);
        v19 = v9;
      }
      else
      {
        v17 = flagarray[(unsigned __int8)(v13 + 63)];
        v18 = (unsigned __int8)(v13 + 23);
        v19 = v11;
      }
      v12 += 9;
      v10 += 13;
      v9 += 19;
      v11 += 7;
      ++*(_QWORD *)&index;
      flag = *v16 + *v14 + flagarray[v19] + flagarray[v18] * (v15 ^ v17);
    }
    while ( *(__int64 *)&index < number_the_username );
  }
  return flag;
}

可以看到,其实dotheuser函数本质上只是做了一个简单的表代替功能,由于IDA反编译时,识别出来很多的无用参数,使得函数看起来很冗长,但是其核心算法并不复杂,基于IDA反编译的结果进行改写后的dotheuser函数;
另外需要注意的一点是,可以看到在IDA反编译的代码中,有很多的强制类型转换,这些强制类型转换都是有用的,因为IDA其实比较笨,不能很好的识别每个变量的类型,所以要借助用来规定每个变量的范围,因此,在转换代码时,遇到类型转换的逻辑应该多思考后再改写代码,防止出现数据溢出或数组越界的情况。其中flagarray的数组可以使用插件Lazy将其作为dword的格式dump下来。
img

uint32_t dotheuser(char *username, unsigned int always_one, unsigned int ACflag,unsigned int var_from_do_a_16_bit)
{
  unsigned int flag = 0;
  size_t number_of_the_username = 0;
  size_t index = 0;
  unsigned int v9 = 0;
  unsigned int v10 = (15 * var_from_do_a_16_bit)&0xff;
  unsigned int v11 = 0;
  unsigned int v12 = (17 * ACflag)&0xff;
  unsigned int v13 = 0;
  unsigned int v14 = 0;
  unsigned int v15 = 0;
  unsigned int v16 = 0;
  unsigned int v17 = 0;
  unsigned int v18 = 0;
  unsigned int v19 = 0;
  // 计算username的长度
  while (username[number_of_the_username])
    ++number_of_the_username;

  if (number_of_the_username > 0)
  {
    do
    {
      v13 = toupper((unsigned char)username[index]);
      v14=flagarray[(unsigned char)v10];
      v15 = flag + flagarray[(unsigned char)v13];
      v16=flagarray[(unsigned char)v12];
      v17 = flagarray[(unsigned char)(v13 + 13)];
      v18 = (unsigned char)(v13 + 47);
      v19 = v9;
      v12 += 9;
      v10 += 13;
      v9 += 19;
      v11 += 7;
      ++index; 
      flag = v14 + v16 + flagarray[(unsigned char)v19] + flagarray[(unsigned char)v18] * (v15 ^ v17);
      printf("flag: ");
      for (int i = 3; i >= 0; --i) {
      printf("%02x", (unsigned int)(flag >> (i * 8)) & 0xFF);
      }
      printf("\n");
    }
    while (index < number_of_the_username);
  }
  return flag;
}

运行改写后的dotheuser函数就可以得到passwd[4],passwd[5],passwd[6],passwd[7]的取值,后需要通过变量

unsigned int ACflag, unsigned int var_from_do_a_16_bit

及其相应函数

//a1=(r[6]^r[0])+((r[5]^r[9])<<16)+((r[8]^r[4])<<8),a2=5999655i64/0x5B8C27
__int64 __fastcall get_the_ACflag(int a1, int a2)
{
  unsigned int v2; // ecx
  __int64 result; // rax
  v2 = (((a2 ^ a1 ^ 0x22C078) - 180597) ^ 0xFFE53167) & 0xFFFFFF;
  result = 0;
  if ( v2 == 17 * (v2 / 0x11) )
    return v2 / 0x11;
  return result;
}

逆推函数得到a1的值,可以求得passwd[0],passwd[9],passwd[8]的取值;同时,通过函数的最后一个if判断可知,我们自己设置的ACflag应该为17的倍数,否则,按照函数的计算逻辑,经过函数计算得到的ACflag应该为0。

a1=(((ACflag*0x11)^0xFFE53167)+180597)^0x22C078^0x5B8C27&0xffffff;
r6xor0=(unsigned char)(a1 >> (0 * 8)) & 0xFF;
passwd[0]=r6xor0^passwd[6];
r5xor9=(unsigned char)(a1 >> (2 * 8)) & 0xFF;
passwd[9]=r5xor9^passwd[5];
r8xor4=(unsigned char)(a1 >> (1 * 8)) & 0xFF;
passwd[8]=r8xor4^passwd[4];
//a1=(r[5]^r[2])+((r[7]^r[1])<<8)
__int16 __fastcall do_a_16_bit(__int16 a1)
{
  unsigned int v1; // r8d
  v1 = (unsigned __int16)((a1 ^ 0x7892) + 19760) ^ 0x3421;
  if ( v1 % 11 )
    return 0;
  else
    return v1 / 11;
}

逆推函数得到a1的值,可以求得passwd[2],passwd[1]的取值;同时,通过函数的最后一个if判断可知,我们自己设置的var_from_do_a_16_bit不能为11的倍数,否则,按照函数的计算逻辑,经过函数计算得到的var_from_do_a_16_bit应该为0。

a1=((((unsigned int)var_from_do_a_16_bit)*11^0x3421)-19760)^0x7892;
r5xor2=(unsigned char)(a1 >> (0 * 8)) & 0xFF;
passwd[2]=r5xor2^passwd[5];
r7xor1=(unsigned char)(a1 >> (1 * 8)) & 0xFF;
passwd[1]=r7xor1^passwd[7];

最后将passwd[3]的值设置为0xAC即可passwd的生成。

运行样例

img
img

注册机代码

#include<bits/stdc++.h>
using namespace std;
unsigned int flagarray[256] = {
    0x39CB44B8, 0x23754F67, 0x5F017211, 0x3EBB24DA, 0x351707C6, 0x63F9774B, 0x17827288, 0x0FE74821, 
    0x5B5F670F, 0x48315AE8, 0x785B7769, 0x2B7A1547, 0x38D11292, 0x42A11B32, 0x35332244, 0x77437B60, 
    0x1EAB3B10, 0x53810000, 0x1D0212AE, 0x6F0377A8, 0x43C03092, 0x2D3C0A8E, 0x62950CBF, 0x30F06FFA, 
    0x34F710E0, 0x28F417FB, 0x350D2F95, 0x5A361D5A, 0x15CC060B, 0x0AFD13CC, 0x28603BCF, 0x3371066B, 
    0x30CD14E4, 0x175D3A67, 0x6DD66A13, 0x2D3409F9, 0x581E7B82, 0x76526B99, 0x5C8D5188, 0x2C857971, 
    0x15F51FC0, 0x68CC0D11, 0x49F55E5C, 0x275E4364, 0x2D1E0DBC, 0x4CEE7CE3, 0x32555840, 0x112E2E08, 
    0x6978065A, 0x72921406, 0x314578E7, 0x175621B7, 0x40771DBF, 0x3FC238D6, 0x4A31128A, 0x2DAD036E, 
    0x41A069D6, 0x25400192, 0x00DD4667, 0x6AFC1F4F, 0x571040CE, 0x62FE66DF, 0x41DB4B3E, 0x3582231F, 
    0x55F6079A, 0x1CA70644, 0x1B1643D2, 0x3F7228C9, 0x5F141070, 0x3E1474AB, 0x444B256E, 0x537050D9, 
    0x0F42094B, 0x2FD820E6, 0x778B2E5E, 0x71176D02, 0x7FEA7A69, 0x5BB54628, 0x19BA6C71, 0x39763A99, 
    0x178D54CD, 0x01246E88, 0x3313537E, 0x2B8E2D17, 0x2A3D10BE, 0x59D10582, 0x37A163DB, 0x30D6489A, 
    0x6A215C46, 0x0E1C7A76, 0x1FC760E7, 0x79B80C65, 0x27F459B4, 0x799A7326, 0x50BA1782, 0x2A116D5C, 
    0x63866E1B, 0x3F920E3C, 0x55023490, 0x55B56089, 0x2C391FD1, 0x2F8035C2, 0x64FD2B7A, 0x4CE8759A, 
    0x518504F0, 0x799501A8, 0x3F5B2CAD, 0x38E60160, 0x637641D8, 0x33352A42, 0x51A22C19, 0x085C5851, 
    0x032917AB, 0x2B770AC7, 0x30AC77B3, 0x2BEC1907, 0x035202D0, 0x0FA933D3, 0x61255DF3, 0x22AD06BF, 
    0x58B86971, 0x5FCA0DE5, 0x700D6456, 0x56A973DB, 0x5AB759FD, 0x330E0BE2, 0x5B3C0DDD, 0x495D3C60, 
    0x53BD59A6, 0x4C5E6D91, 0x49D9318D, 0x103D5079, 0x61CE42E3, 0x7ED5121D, 0x14E160ED, 0x212D4EF2, 
    0x270133F0, 0x62435A96, 0x1FA75E8B, 0x6F092FBE, 0x4A000D49, 0x57AE1C70, 0x004E2477, 0x561E7E72, 
    0x468C0033, 0x5DCC2402, 0x78507AC6, 0x58AF24C7, 0x0DF62D34, 0x358A4708, 0x3CFB1E11, 0x2B71451C, 
    0x77A75295, 0x56890721, 0x0FEF75F3, 0x120F24F1, 0x01990AE7, 0x339C4452, 0x27A15B8E, 0x0BA7276D, 
    0x60DC1B7B, 0x4F4B7F82, 0x67DB7007, 0x4F4A57D9, 0x621252E8, 0x20532CFC, 0x6A390306, 0x18800423, 
    0x19F3778A, 0x462316F0, 0x56AE0937, 0x43C2675C, 0x65CA45FD, 0x0D604FF2, 0x0BFD22CB, 0x3AFE643B, 
    0x3BF67FA6, 0x44623579, 0x184031F8, 0x32174F97, 0x4C6A092A, 0x5FB50261, 0x01650174, 0x33634AF1, 
    0x712D18F4, 0x6E997169, 0x5DAB7AFE, 0x7C2B2EE8, 0x6EDB75B4, 0x5F836FB6, 0x3C2A6DD6, 0x292D05C2, 
    0x052244DB, 0x149A5F4F, 0x5D486540, 0x331D15EA, 0x4F456920, 0x483A699F, 0x3B450F05, 0x3B207C6C, 
    0x749D70FE, 0x417461F6, 0x62B031F1, 0x2750577B, 0x29131533, 0x588C3808, 0x1AEF3456, 0x0F3C00EC, 
    0x7DA74742, 0x4B797A6C, 0x5EBB3287, 0x786558B8, 0x00ED4FF2, 0x6269691E, 0x24A2255F, 0x62C11F7E, 
    0x2F8A7DCD, 0x643B17FE, 0x778318B8, 0x253B60FE, 0x34BB63A3, 0x5B03214F, 0x5F1571F4, 0x1A316E9F, 
    0x7ACF2704, 0x28896838, 0x18614677, 0x1BF569EB, 0x0BA85EC9, 0x6ACA6B46, 0x1E43422A, 0x514D5F0E, 
    0x413E018C, 0x307626E9, 0x01ED1DFA, 0x49F46F5A, 0x461B642B, 0x7D7007F2, 0x13652657, 0x6B160BC5, 
    0x65E04849, 0x1F526E1C, 0x5A0251B6, 0x2BD73F69, 0x2DBF7ACD, 0x51E63E80, 0x5CF2670F, 0x21CD0A03, 
    0x5CFF0261, 0x33AE061E, 0x3BB6345F, 0x5D814A75, 0x257B5DF4, 0x0A5C2C5B, 0x16A45527, 0x16F23945, 
};

uint32_t dotheuser(char *username, unsigned int always_one, unsigned int ACflag,unsigned int var_from_do_a_16_bit)
{
  unsigned int flag = 0;
  size_t number_of_the_username = 0;
  size_t index = 0;
  unsigned int v9 = 0;
  unsigned int v10 = (15 * var_from_do_a_16_bit)&0xff;
  unsigned int v11 = 0;
  unsigned int v12 = (17 * ACflag)&0xff;
  unsigned int v13 = 0;
  unsigned int v14 = 0;
  unsigned int v15 = 0;
  unsigned int v16 = 0;
  unsigned int v17 = 0;
  unsigned int v18 = 0;
  unsigned int v19 = 0;

  // 计算username的长度
  while (username[number_of_the_username])
    ++number_of_the_username;

  if (number_of_the_username > 0)
  {
    do
    {
      v13 = toupper((unsigned char)username[index]);
      v14=flagarray[(unsigned char)v10];
      v15 = flag + flagarray[(unsigned char)v13];
      v16=flagarray[(unsigned char)v12];
      v17 = flagarray[(unsigned char)(v13 + 13)];
      v18 = (unsigned char)(v13 + 47);
      v19 = v9;
      v12 += 9;
      v10 += 13;
      v9 += 19;
      v11 += 7;
      ++index; 
      flag = v14 + v16 + flagarray[(unsigned char)v19] + flagarray[(unsigned char)v18] * (v15 ^ v17);
      printf("flag: ");
      for (int i = 3; i >= 0; --i) {
      printf("%02x", (unsigned int)(flag >> (i * 8)) & 0xFF);
      }
      printf("\n");
    }
    while (index < number_of_the_username);
  }
  return flag;
}
//sdsafdf
//0773-ecac-ed07-0aee-adea
int main()
{
  unsigned char passwd[10];
  char user[20];
  printf("Your username : ");
  scanf("%s",user);
  printf("The username you input: %s\n",user);
  uint32_t var_from_username;
  unsigned int always_one=0x1;
  unsigned int ACflag=0x6da1a;
  unsigned int var_from_do_a_16_bit=0x98;
  var_from_username=dotheuser(user,always_one,ACflag,var_from_do_a_16_bit);
  //从最高有效字节到最低有效字节打印每个字节
  printf("Username from the hex string: ");
  for (int i = 7; i >= 0; --i) {
      printf("%02x", (unsigned int)(var_from_username >> (i * 8)) & 0xFF);
  }
  printf("\n");
  for(int i=0;i<4;++i){
        passwd[4+i]=(var_from_username>>(i*8))&0xff;
  }
  passwd[3]=0xAC;
  unsigned int a1;
  unsigned char r6xor0;
  unsigned char r5xor9;
  unsigned char r8xor4;
  unsigned char r5xor2;
  unsigned char r7xor1;
  //函数creat_the_ACflag的逆推
  a1=(((ACflag*0x11)^0xFFE53167)+180597)^0x22C078^0x5B8C27&0xffffff;
  r6xor0=(unsigned char)(a1 >> (0 * 8)) & 0xFF;
  passwd[0]=r6xor0^passwd[6];
  r5xor9=(unsigned char)(a1 >> (2 * 8)) & 0xFF;
  passwd[9]=r5xor9^passwd[5];
  r8xor4=(unsigned char)(a1 >> (1 * 8)) & 0xFF;
  passwd[8]=r8xor4^passwd[4];
  //函数do_a_16_bit的逆推
  a1=((((unsigned int)var_from_do_a_16_bit)*11^0x3421)-19760)^0x7892;
  r5xor2=(unsigned char)(a1 >> (0 * 8)) & 0xFF;
  passwd[2]=r5xor2^passwd[5];
  r7xor1=(unsigned char)(a1 >> (1 * 8)) & 0xFF;
  passwd[1]=r7xor1^passwd[7];
  printf("%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x",passwd[0]&0xff,passwd[1]&0xff,passwd[2]&0xff,passwd[3]&0xff,passwd[4]&0xff,passwd[5]&0xff,passwd[6]&0xff,passwd[7]&0xff,passwd[8]&0xff,passwd[9]&0xff);
  system("pause");
}

附带一个时间戳转换函数

from datetime import datetime, timedelta
# 将十六进制值转换为十进制
days_since_epoch = 45055
# Unix 时间戳的起始日期是 1970-1-1
epoch_start = datetime(1970, 1, 1)
# 计算具体日期
target_date = epoch_start + timedelta(days=days_since_epoch)
print(target_date)

PS:2024.6.8 去除网络验证模块
当使用一段时间的注册机序列号,后软件会联网将该序列号拉黑,此后机器上安装的010Editor都会首先运行网络验证模块,导致我们的注册机验证失效,这里通过修改二进制文件,使用patch的方法,改变通往网络验证的模块,使其不跳转
img

img

img

posted on 2024-05-29 14:32  伤心猪大肠吃吃  阅读(72)  评论(1)    收藏  举报

导航