010Editor分析报告

010Editor简介

​ 010Editor是一款文本编辑工具,能够编辑文本文件、XML、Unicode和UTF-8、C/C++源码等。

无限的撤销和强大的编辑和脚本工具。

巨大的文件支持(50GB+)。

010Editor是一款优秀的十六进制/文本编辑工具,其内置的模板功能对于学习文件格式有极大的帮助。010中包含着许多复杂的工具,用于分析和编辑二进制文件。并且支持脚本,便于更好的自定义您的编辑习惯。

当然,010Editor是一款收费软件,本次秉承着学习的想法对其实现进行分析。

逆向初步分析

已知条件:Qt5程序

image-20191227141029701

思路:使用OD动态调试

  1. 程序有随机基址,去掉随机基址拖进OD
  2. 寻找关键函数和字符串并下断点
  3. 自动分析后逆向跳转至关键比较部分
  4. 寻找比较过程中的判断位置以及未知用处的函数调用
  5. 修改关键部分代码(暴力破解)
  6. 逆向分析算法,写出注册机

在未知Qt函数的情况下,通过对User32.dll模块的CreateWindowA/W函数下断点,然后进行栈回溯,找到在主模块中调用的Qt中封装winapi的函数,即可断下程序的弹窗位置

对于未知API的情况,也可以通过字符串查找找到关键调用部分

暴力破解

对CreateWindowA/W下断,找到主模块中调用的call,并对每个call下断,逐个分析

image-20191227142022887

找到关键的函数调用部分

image-20191227142541777

逆向向上查找,最终定位到关键位置

image-20191227143538520

如果想要通过暴力破解的方式完成对软件的破解,只需要让0x0040A826函数始终返回0x2D即可

image-20191227145203585

至此,我们便完成了对010Editor的暴力破解

校验部分算法分析

进行算法分析首先要定位到校验部分

通过逆向跟踪jcc指令定位到相邻的两个call处

单步跟踪进入到第一个call中

image-20191227150631619

分析后发现在0x0040A826函数中存在着大量的异或操作,并且有两个函数调用。跟进第一个函数

image-20191227150752233

函数中同样是异或操作,然后将操作后的结果返回。再此跟进第二个call

image-20191227151325400

同样是对参数进行异或操作,完成异或后,将结果与0xB做除法,余数为零则返回商,不为零则返回零,并将余数保存在EDX寄存器中

跳转到下面比较用户名部分

image-20191227152220728

用户名传入了一个未知函数,其返回值通过移位的方式与序列号进行比较。猜测此函数是计算用户名特征值的函数,call后面的ADD ESP,0x10说明了函数有四个参数,cdecl调用约定。跟进函数发现内部是一个与校验盘运算求出特征值的函数,使用IDA插件还原源代码

image-20191227152933881

至此,我们便完成了对010Editor的验证算法的初步分析

编写注册机

了解了验证算法之后,可以通过分析出来的结果编写注册机

现已知程序现对序列号进行一定的判断,再通过特殊的算法使用用户名算出四字节的校验值,最后用校验值与输入的序列号进行比较,判断是否正确

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
srand(time(0));
BYTE pass[8] = { 0x0,0x0,0x0,0x9C,0x0,0x0,0x0,0x0 };

char username[20] = { 0 };

while (1) {
pass[0] = rand() % 0xFF;
pass[1] = rand() % 0xFF;
pass[2] = rand() % 0xFF;
pass[4] = rand() % 0xFF;
pass[5] = rand() % 0xFF;
pass[6] = rand() % 0xFF;
pass[7] = rand() % 0xFF;

DWORD tmp;
tmp = (pass[1] ^ pass[7]) & 0xFF;
tmp = tmp * 0x100;
tmp = tmp + ((pass[2] ^ pass[5]) & 0xFF);
tmp = tmp ^ 0x7892;
tmp = tmp + 0x4D30;
tmp = tmp ^ 0x3421;
tmp = tmp & 0xFFFF;
if (tmp % 0xB == 0) {
printf("%02x%02x-%02x%02x-%02x%02x-%02x%02x\n", pass[0], pass[1], pass[2], pass[3], pass[4], pass[5], pass[6], pass[7]);
break;
}
}

通过随机数的方式计算出了一个能够运行到验证用户名的部分,将用户名生出的算法抠出来,再dump出校验盘,将函数加入到我们的代码中去,最终的代码为

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <stdio.h>
#include <time.h>
#include <Windows.h>

DWORD array[] = {
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 };

int function(const char *a1, int a2, char a3, __int16 a4 = 0)//最后一个参数是商
{
const char *v4; // edx
signed int v5; // esi
signed int v6; // edi
unsigned __int8 v7; // bl
int v8; // eax
int v9; // ecx
int v10; // ecx
int result; // eax
unsigned __int8 v12; // [esp+8h] [ebp-10h]
unsigned __int8 v13; // [esp+Ch] [ebp-Ch]
unsigned __int8 v14; // [esp+10h] [ebp-8h]
int v15; // [esp+14h] [ebp-4h]

v4 = a1;
v15 = 0;
v5 = strlen(a1);
v6 = 0;
if (v5 <= 0)
return 0;
v12 = 0;
v13 = 0;
v7 = 15 * a4;
v14 = 17 * a3;
do
{
v8 = toupper((unsigned __int8)v4[v6]);
v9 = v15 + array[v8];
if (a2)
v10 = array[v13]
+ array[v7]
+ array[v14]
+ array[(unsigned __int8)(v8 + 47)] * (array[(unsigned __int8)(v8 + 13)] ^ v9);
else
v10 = array[v12]
+ array[v7]
+ array[v14]
+ array[(unsigned __int8)(v8 + 23)] * (array[(unsigned __int8)(v8 + 63)] ^ v9);
result = v10;
v15 = v10;
v13 += 19;
++v6;
v14 += 9;
v7 += 13;
v12 += 7;
v4 = a1;
} while (v6 < v5);
return result;
}

int main()
{
srand(time(0));
BYTE pass[8] = { 0x0,0x0,0x0,0x9C,0x0,0x0,0x0,0x0 };

char username[20] = { 0 };
printf("请输入用户名:");
scanf_s("%s", username, 20);
restart:
DWORD MyNumber = rand() % 0x3E8;

DWORD result = function(username, 1, 0, MyNumber);

pass[3] = 0x9C;
pass[4] = (result & 0xFF);
pass[5] = ((result >> 8) & 0xFF);
pass[6] = (result >> 16);
pass[7] = (result >> 24);

while (1) {
pass[0] = rand() % 0xFF;
pass[1] = rand() % 0xFF;
pass[2] = rand() % 0xFF;

DWORD tmp;
tmp = (pass[1] ^ pass[7]) & 0xFF;
tmp = tmp * 0x100;
tmp = tmp + ((pass[2] ^ pass[5]) & 0xFF);
tmp = tmp ^ 0x7892;
tmp = tmp + 0x4D30;
tmp = tmp ^ 0x3421;
tmp = tmp & 0xFFFF;
if (tmp % 0xB == 0) {
if (tmp / 0xB == MyNumber) {
printf("用户名:\t");
printf("%s\n", username);

printf("序列号为:\t");
printf("%02x%02x-%02x%02x-%02x%02x-%02x%02x\n", pass[0], pass[1], pass[2], pass[3], pass[4], pass[5], pass[6], pass[7]);
break;
}
else {
goto restart;
}
}
}

return 0;
}

至此,010Editor的注册机也就完成了