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没有加壳。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

此时修改ZF的值为1

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


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



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

注册机编写
由于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下来。

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的生成。
运行样例


注册机代码
#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的方法,改变通往网络验证的模块,使其不跳转



浙公网安备 33010602011771号