1. 关键函数定位
进入register窗口,随便填入name,然后check license,弹框信息”Invalid name or password. Please enter your name and password exactly as given when you purchased 010 Editor (make sure no quotes are included).”,通过该信息在IDA中找到对应函数地址,基本可以确认是关键函数位置,函数居然有名字,这是作者故意留下的吗。下面是整个验证函数流程:
1 | void __usercall chekc(char a1<zf>, int a2<ecx>) |
2. 算法分析
下面看看主要的验证函数myCheck和mySecondCheck,代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17signed int __thiscall mySecondCheck(int this, unsigned int a2, unsigned int a3)
{
int v3; // esi@1
signed int result; // eax@2
int v5; // eax@3
int v6; // eax@6
int v7; // eax@9
v3 = this;
if ( *(_DWORD *)(this + 44) ) // 要让这个值等于0,否则进入网络验证,初始化就是0
return 275;
v5 = myCheck(this, a2, a3); //可以看到,只有返回值是45时,才能返回219,注册成功
if ( v5 == 45 )
{
result = 219; // 返回219, 注册成功
}
}
可以看到,只有返回值是45时,才能返回219,注册成功。
那么返回myCheck看看,怎么才能得到45的返回值,整个返回值查看一下,只有两处位置,可能返回45,如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14if ( v26 == 0x9Cu )
{
v20 = *(_DWORD *)(regdlg + 28) < a2;
return (-v20 & 0x21) + 45; // 成功?
}
if ( v26 != 0xFCu )
{
if ( v26 == 0xACu && v33 )
{
v20 = v33 < a3;
return (-v20 & 0x21) + 45; // 成功?
}
return 231;
}
那么就需要回溯回去,看看v26,是如何得到的,只有在v26等于0x9c或者0xAc时,才有可能注册成功。下面看看完整代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74int __thiscall myCheck(int this, unsigned int a2, unsigned int a3)
{
//name和key长度是否为0
//myPassCheck(this, (int)&v23);//将key字符串转换成数值,每两个字符转化成2为十六进制数,
//xxxx-xxxx-xxxx-xxxx-xxxx分别对应k1k2-k3k4-k5k6-k7k8-k9k10
//v23其实就是一个数组,存的就是k1-k10
//检测name是否等于'999',是,则失败
if ( v26 == 0x9Cu )
{
LOBYTE(v32) = v23 ^ v28; // k1^k7
LOWORD(v6) = (unsigned __int8)(v24 ^ v29); // k2^k8
LOWORD(v7) = (unsigned __int8)(v25 ^ HIBYTE(v27));// k3^k6
v11 = v7 + ((_DWORD)v6 << 8); // v11 = k3^k6 + ((k2^k8)<<8)
*(_DWORD *)(regdlg + 28) = (unsigned __int8)myCal1(v23 ^ v28);// k1^k7 => 不能等于0
v9 = myCal2(v11); // 不能等于0
v10 = *(_DWORD *)(regdlg + 28);
*(_DWORD *)(regdlg + 32) = (unsigned __int16)v9;
// v10==0,v9==0或者v9>0x3e8,返回231
if ( !v10 || !v9 || (unsigned __int16)v9 > 0x3E8u )
return 231;
v12 = v10 < 2 ? v10 : 0; // v12 = 0或者1
}else
{
if(v26 == 0xFC)
{//不可能成功
}esle
{
//v26不等于0xAC,退出,返回231,失败
//myCal2(k3^k6 + ((k2^k8)<<8)) > 0x3E8, 失败
if ( v26 != 0xACu
|| (v15 = v24 ^ v29,//k2^k8
v16 = v25 ^ HIBYTE(v27),//k3^k6
*(_DWORD *)(regdlg + 28) = 2,
v14 = (unsigned __int16)myCal2(v16 + (v15 << 8)),v11 = k3^k6 + ((k2^k8)<<8)
*(_DWORD *)(regdlg + 32) = (unsigned __int16)v14,
!(_WORD)v14)
|| v14 > 0x3E8 )
return 231;
//sub_4FD0B9( (k1^k7 + (k9^k5 + (k6^k10)<<8)<<8), xxx);
//其实就是凑成十六进制数(k6^k10)(k9^k5)(k1^k7)
v17 = sub_4FD0B9(
(v23 ^ v28) + (((v30 ^ (unsigned __int8)v27) + ((HIBYTE(v27) ^ v31) << 8)) << 8),
(char *)loc_5B8C25 + 2);
v33 = v17;
*(_DWORD *)(regdlg + 52) = v17;
v12 = v17;
}
}
//编码name,返回给v18,
v18 = myEncStr(*(const char **)(*(_DWORD *)qstrname + 12), v26 != -4, v12, *(_DWORD *)(regdlg + 32));
//如果v18,如0xABCDEF10分解成0xAB,0xCD, 0xEF10,不等于v29, v28,v27就失败,其实就是
//(k6k5) = 0xEF10, k7 = 0xCD, k8 = 0xAB
if ( v27 != (_WORD)v18
|| v28 != (unsigned __int8)((unsigned int)v18 >> 0x10u)
|| v29 != (unsigned __int8)((unsigned int)v18 >> 0x18u) )
return 231; // 这三个条件很重要啊
//下面就接近成功了,就是上面提到的返回45的结果,成功
if ( v26 == 0x9Cu )
{
//这里就需要regdlg + 28 = myCal1(k1^k7) >= a2,也就是3,传入的a2是3
//然后v20就是0,那么(-v20 & 0x21)=0,最后返回45
v20 = *(_DWORD *)(regdlg + 28) < a2;
return (-v20 & 0x21) + 45; // 成功?
}
if ( v26 != 0xFCu )
{
if ( v26 == 0xACu && v33 )
{
v20 = v33 < a3;
return (-v20 & 0x21) + 45; // 成功?
}
return 231;
}
}
最后总结一下算法,基本可以列出一个方程类似的东西:1
2
3
4
5
6k4 = 0x9C或者0xAC
myCal1(k1^k7) >= 3;//可以任取大于等于3的值,算出k1^k7=?
myCal2(k3^k6 + ((k2^k8)<<8)) > 0;//可以任去大于0
myCal2(k3^k6 + ((k2^k8)<<8)) < 0x3E8;//小于0x3E8的某一个值,算出k3^k6 + ((k2^k8)<<8) = ?
k8k7k6k5 = v18;//0xABCDEF10,可以得到k5=?,k6=?,k7=?,k8=?,由此可以算出上面的k1,k2
sub_4FD0B9((k6^k10)(k9^k5)(k1^k7), xx) = ?//可以算出k9,k10
下面就0x9C的情况写了个注册机
3. 注册机
根据上面的注册算法,写了个针对0x9c的注册机:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78DWORD ckname(char* name, int isnotFC, int islowk1k7, DWORD k3k6k2k8 )
{
int slen = strlen(name);
if(slen > 0 )
{
DWORD v15 = 0, v17 = 0, v16 = 0, chk = 0, v9 = 0, v8=0, v14 = 0, v5 = 0, v6 = 0;
while(v14 < slen)
{
DWORD v7 = toupper(name[v14]);
if(isnotFC)
{
v9 = dword_B21DC4[(v17 + 15 * k3k6k2k8) & 0xFF]
+ dword_B21DC4[(v6 + 17 * islowk1k7) & 0xFF]
+ dword_B21DC4[(v7 + 47) & 0xFF] * ((v5 + dword_B21DC4[v7]) ^ dword_B21DC4[(v7 + 13) & 0xFF]);
v8 = v16;
}
else{
v9 = dword_B21DC4[(v17 + 15 * k3k6k2k8) & 0xFF]
+ dword_B21DC4[(v6 + 17 * islowk1k7) & 0xFF]
+ dword_B21DC4[(v7 + 23) & 0xFF] * ((v5 + dword_B21DC4[v7]) ^ dword_B21DC4[(v7 + 63) & 0xFF]);
v8 = v15;
}
v16 += 19;
v17 += 13;
v15 += 7;
v6 += 9;
v5 = dword_B21DC4[v8] + v9;
v14 = v14 + 1;
}
return v5;
}
}
int main()
{
char name[] = "";
char key[0x20] = {0};
int k4 = 0x9c;//0xac
int islowk1k7 = 3;//>=3
int k1_xor_k7 = ((islowk1k7^0xA7)-61)^0x18;
int k3k6k2k8 = 1;//k3k6k2k8>=1 && k3k6k2k8<0x3E8,其中任意一个值
int k3_xor_k6_k2_xor_k8 = 0xFFFF & (((k3k6k2k8*11)^0x3421)-19760);
k3_xor_k6_k2_xor_k8 = k3_xor_k6_k2_xor_k8^0x7892;
int k3_xor_k6 = k3_xor_k6_k2_xor_k8 & 0xff;//低位
int k2_xor_k8 = k3_xor_k6_k2_xor_k8 >> 8;//高位
int k1=0, k2=0, k3=0, k5=0, k6=0, k7=0, k8=0, k9=0, k10=0;//
printf("****************************************************\n");
printf("************* 010 Editor v3.1.2 keygen *************\n");
printf("************* by anhkgg 2014-11-18 *************\n");
printf("****************************************************\n\n");
printf("name>");
scanf("%s", name);
if(!stricmp(name, "999"))
{
printf("name is not valid!\n");
system("pause");
return 0;
}
DWORD name_chk = ckname(name, k4==0x9C?1:0, islowk1k7<2?islowk1k7:0, k3k6k2k8) ;
k5 = name_chk & 0xFF;
k6 = (name_chk & 0xFFFF)>>8;//
k7 = (name_chk >> 16) & 0xFF;//
k8 = (name_chk >> 24) & 0xFF;//
k1 = k1_xor_k7 ^ k7;//
k2 = k2_xor_k8 ^ k8;
k3 = k3_xor_k6 ^ k6;//
printf("key>%02x%02x-%02x%02x-%02x%02x-%02x%02x\n\n", k1, k2, k3, k4, k5, k6, k7, k8);
system("pause");
return 0;
}
4. 其他
- 本次分析针对的是010 v3.1.2
- 希望各位大牛不要见笑,欢迎交流
- 网站:anhkgg.gitcafe.com, www.devilstep.com
- 转载请注明出处:anhkgg.gitcafe.com/010%E6%B3%A8%E5%86%8C%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90/
- 更新:最新版v5.0.2,分析之后,算法流程基本没有变化,只需要myCal1(k1^k7)条件更新一下就行!