[TOC]

0x00 010 Editor 简单介绍

010 Editor 是一款非常强大的文本、十六进制编辑器,除了文本、十六进制编辑外,还包括文件解析、计算器、文件比较等功能,但它真正的强大之处还在于文件的解析功能。我们可以使用 010Editor 官方网站提供的解析脚本对 avi、bmp、png、exe 等简单格式的文件进行解析,当然也可以根据需求来自己编写文件解析脚本。但是 010 Editor 是收费的,不过官方给出了 30 天的免费使用期限供用户体验。本文将为大家分析如何去除使用限制和编写注册机。目前最新版本为 v9.0.1,也是本文将分析的版本。010 Editor 官网:http://www.sweetscape.com/010editor/

0x01 暴力破解分析

在软件破解中,暴力破解可谓是一个最基本,也是最常见的一种手段,无非就是改跳转指令(例如 JNZ 改 JZ、JZ 改 JNZ、把跳转指令 NOP 掉等),让它跳的时候不跳,不该跳的时候跳,从而避开检测以获取它的永久使用权限。下面就列下 010 Editor 暴力破解的分析步骤:

  • 找到注册窗口
  • 测试注册窗口的反应
  • 根据反应做出下一步分析的打算(猜测 API、API 下断动态调试、挑出敏感字符串,在程序中搜索)
  • 动态分析,定位关键跳转,修改代码
  • 动态分析,定位关键 CALL,修改代码

在官网下载最新版本并在你的计算机上安装好后,双击让它运行起来,并点击关于,会看到 Free 30-Day Trial 字样,另外,注册窗口在 Tools --> Register ,如下:

点击后弹出注册的窗口,这里需要你填写用户名和序列号,如下:

点击 Check License 按钮,弹出如下提示:

上述意思是你输入了一个无效的用户名或者是密码,也就是说,你点击那个按钮会弹出这个提示窗口,那么我们可以从这个提示窗口入手,比如对创建这个窗口的 API 下断,或者是寻找提示信息的这段字符串。废话不多说,上 OD,附加进程调试,如下:

附加后,我们需要来到主模块,点击 OD 上方的 e 按钮,点击第一个,这里需要重复操作两次才到主模块,完成上述操作后,我们先来看下程序主模块导入了哪些函数,右键 -> 查找 -> 当前模块中的名称(标签),或者按下快捷键 Ctrl + N,如下:

在上面图中可以看出 010 Editor 调用了大量的 Qt 库函数,由此可猜测它是由 Qt 编写的,对于 Qt 中创建窗口函数,个人觉得,不管是何种高级语言,在底层中还是得调用诸如 CreateWindow、MessageBox 以及一些 Dialog 相关的函数,这些函数位于 User32.dll 中,同样点击 OD 上方的小 e 按钮,在里面找到 user32.dll,双击进去,在这里要说一点就是,创建窗口的 API 函数不止一个,而且也不存在 CreateWindow、MessageBox 这类函数,其实它们只是个宏,在深入一点都是调用 Ex 的版本,那么问题又来了,是调用 A 版 还是调用 W 版 的,学过 windows 编程的都知道,A 版 的最终还是会去调用 W 版 的, 所以我们不妨先对 CreateWindowExW 函数下断,按 Ctrl + G,输入 CreateWindowExW,如下:

转到这里后,对该地址下断,回到 010 Editor 注册界面,点击 Check Lincense,之后你会发现程序断下来了,这说明断点下对了,程序停在了 0x74892480 地址处。断下来之后,我们接下来要想的是哪里调用了这个函数,我们可以上方的 k 小按钮来查看,如下:

通过栈回溯分析,可以看出,在地址 0x00EFCD24 上的函数过程有个很显眼的字 show,貌似跟显示窗口有关,我们双击进去看下里面有什么可以利用的信息,结果没发现有价值的东西,不要慌,我们继续点击上面这个地址的下一个地址,双击进行查看,结果也没有,再跟进去下一个地址,也就是 0x00EFCDFC 处,双击进去,会发现如下这种情况:

通过上图可以看出,在这里面出现了大量可疑的字符串,其中 Invalid name or password...... 这个字符串就是我们随意输入用户名和密码时弹出的信息提示,该处为一个 push 指令,我们向上看会发现该处是由 0x1DE8489 处跳转过来的,如下:

不妨在 0x1DE8489 处下断点,把之前的断点禁用或者删除,回到注册界面,点击 Check License,会发现在该处断下来了,F8 一下就跳到无效密码处,在该 JNZ 指令上有三组 CMP 指令,并且第一组 CMP 指令是从地址 0x01DE8336 处跳转过来的,如下:

我们来到第一组 CMP 指令源跳转处,如下:

0x1DE8330 地址处的 CMP 指令,有两条源跳转,分别为 0x01DE82370x01DE8241,如下:

我们不妨在上面处下个断,重新回到注册界面,点击 Check License,会发现它会断在刚下的这个断点处,按 F8 下去,你会发现有两个关键的地方,一个是 EDI 的值,一个是 EBX 的值,前者需要和 0xDB 这个值作比较,后者则需要跟 0xE7 这个值作比较,如下:

这些值都是来自于 EAX 的赋值,而我们知道,EAX 寄存器存放的是函数的返回值,所以我们需要跟进函数里头去看下返回值是如何返回的,但现不急着跟进去,因为现在是暴力破解它,所以我们只需将地址 0x019D8336 处的 JNZ 指令给 NOP 掉即可,如下:

最后保存文件即可,如下:

双击运行 Dump 出来的文件,点击 Check License 会发现弹出如下提示:

点击 OK 就可以进入到主界面了,这样就达到了暴力破解的目的了,但是这种方法对于这个软件来说有一点不好的地方的就是每次运行都会弹出这个注册界面,需再次点击 Check License,这样才能够使用。我们能不能双击直接进入呢?答案肯定能,我们重新理一下整个过程,如下:

如果 EBX 为 0xE7 则跳,跳过之后,再对 EDI 比较,此时 EDI 为 0x177,0x177 不等于 0xDB
又跳走,接着有两次对 EDI 进行比较,值分别为 0xED 和 0x20C,我们知道,EDI 值为 0x177,所以两处的 JE 指令不成立,最后对比 EBX 的值,用它跟 0x93 对比,很明显 0xE7 不等于 0x93,所以 JNZ 指令成立,将跳过密码已被接受过程。

所以为了一开始不跳,EBX 的值要不为 0xE7EDI 的值要为 0xDB,在上面谈到的两个重要的 CALL 里,第二个 CALL 是根据第一个 CALL 的最终 EAX 的来进行对 EDI 进行赋值,所以,为了让 EDI 的值为 0xDBEBX 的值要为 0x2D,也就是说第一个 CALL 的最终 EAX 值要为 0x2D,我们可以跟进第一个 CALL 内,在最开头部分写下:

1
mov eax,0x2D

如下:

修改完后,会发现下一条指令的 OPCODE 下有一根下划线,这说明需要重定位,下次再运行时可能就不是这个地址了,所以我们需要做的就是把软件的重定位标志置为 0 保存即可,如下:

重新运行 OD,将其载入,在最开头部分写下:

1
2
mov eax,0x2D
retn 0x8

如下所示:

最后 Dump 出来,双击运行下看是否成功,如下:

由此可见,我们已成功完美破解了 010 Editor 了,尽情享用吧!

0x02 初步分析算法

在这小节当中,会对 010 Editor 注册算法进行详细分析。回顾上一小节,我们只是修改了一个跳转,但关键点还是在于那两个重要的 CALL,尤其是第一个 CALL,必须让它返回 0x2D 才是正确的,所以本小节将会跟进第一个关键 CALL 内去分析注册算法。在跟进之前,我们先来看下这个 CALL 有哪些参数,如下:

我们知道,ECX 传参为 this 指针,后面两个分别为 0x45960xA,传的参肯定和用户名和密码有关系,我们不妨跟进 ECX 地址所在处数据窗口,ECX 的值为 0330B300,如下:

从上图可知,这个 this 指针应该是指向一个字符串数组,接着我们需进入到地址 0330B304 处,如下:

在这里我们需要转化成 Unicode 16 位 查看,如下:

注意地址的对应,从上图可知,这个字符串就是我们输入的用户名 fanfanblog.cn,我们返回到 this 指针下的第二个地址处跟进到数据窗口看下,也是同样的操作,如下:

同样地,注意地址的对应,在这一段为你输入的密码,也可叫做序列号。知道这些后,对后面分析有一定帮助。我们跟进去第一个重要 CALL 内,以下作了一个简单的分析:

由上面分析知,ECX 存放的是 this 指针,而 this 指针指向的是一个字符串数组,这个数组存放着用户名和密码字符串,我已将说明标注在上图中。所以为什么说之前我们需要跟进 ECX 中的数据窗口去查看下这个地址里存放的是什么东西,那么在上图分析过程中起到一个辅助作用。

在检测用户名和密码是否为空后,紧接着将一个局部变量的值压和栈中,右键栈中的地址数据窗口跟随,会发现这个数据是你输入的密码字符串,而且为 16 进制,所以说 PUSH 指令下的这个 CALL,是将密码字符串转为 16 进制字节的数据。如果你不放心,可以多测试几下,这里把密码换成 1234-5678-9009-8765-4321,如下:

我们接着分析,在接下来,程序会将字符串 999 压入栈中进行一番不知什么操作,如下:

这一段并没有对用户名和密码字符串进行操作,所以不去深究它也无太大影响,在逆向分析中,并不是每条都分析一遍你才能破解它,而是找到关键部分进行分析,这一小段为一个小循环,这个 CALL,根据提示,应该 Qt 中运算符重载,由于对 Qt 不是很熟悉,而且这小段并未对用户名和密码进行操作,所以跳过它往下继续分析。从循环开始,那就是真正的到达算法处,算法并不是很难,但是有一点绕,如下:

在上图中,有几个关键点地方,第一个圈起来的,它会拿你输入的密码的第 4 组数据去和 0x9C、0xAC、0xFC 这三个数进行比较,如果不是这三个数中的其中一个,那么程序会将 0xE7 赋值给 EAX,而在前面一小节当中,EAX 的值不能为 0XE7,否则失败,所以我们令 JNZ 下方这条指令为新的 EIP,否则 JNZ 条件成立跳过去了,就不好分析了,最后我们可以肯定,密码有三个版本,这里只分析下 0x9C 这个版本。第二个关键点是第二个圈起来的地方,跟进这个 CALL 里,如下:

还好这个 CALL 里的代码并不是太复杂,主要处理的是 k[0] 和 k[6],也就是 12 和 87 这两个数。第三个关键点是第三个圈起来的地方,我们跟进这个 CALL 内,如下:

同样,代码也不复杂,主要是判断余数是否为 0,如果为 0,那么就返回商,如果不为 0,那么就返回 0。所以这么一整段下来,在对 k[0]、k[1]、k[2]、k[3]、k[4]、k[5]、k[6]、k[7] 在进行操作,而我们输入的密码有 10 组,OD 中并没看到对 k[8]、k[9] 进行处理,其实这里有个关子,那就是当你密码中第 4 组数据为 0x9C,那么生成的密码并没有 10 组,而是 8 组,如下:

但不管怎么说,在 0x9C 情况下对每组数据进行异或、相加、与等操作,我们不妨先建立下注册机模型,我这里用的是 MFC 进行编写,如下:

Generate 生成按钮添加单击事件,相关代码如下:

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
// 生成密码
void CMy010EditorRegisterDlg::OnBnClickedButtonGenerate()
{
// TODO: 在此添加控件通知处理程序代码
m_edit_password.SetWindowText(L"");
CString str;
srand(time(NULL));
BYTE k[8] = { 0x12,0x34,0x56,0x9C,0x90,0x09,0x87,0x65 };
while (TRUE)
{
BYTE k0 = rand() % 0xFF;
BYTE k6 = rand() % 0xFF;
// AL = (k[0]^k[6]^0x18 + 0x3D)^0xA7
BYTE AL = (k0 ^ k6 ^ 0x18 + 0x3D) ^ 0xA7;
if (AL > 0)
{
k[0] = k0;
k[6] = k6;
break;
}
}

// ESI = (0x100*(k[1]^k[7] & 0xFF) + k[2]^k[5] & 0xFF)&0xFFFF
// EAX = ((ESI^0x7892+0x4D30)^0x3421)&0xFFFF / 0xB
// 判断余数是否为0,为0返回商,不为0返回0
while (TRUE)
{
BYTE k1 = rand() % 0xFF;
BYTE k7 = rand() % 0xFF;
BYTE k2 = rand() % 0xFF;
BYTE k5 = rand() % 0xFF;

DWORD ESI = (0x100 * (k1 ^ k7 & 0xFF) + k2 ^ k5 & 0xFF) & 0xFFFF;
DWORD EAX = (((ESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
if (EAX % 0xB == 0 && EAX / 0xB <= 0x3E8)
{
k[1] = k1;
k[7] = k7;
k[2] = k2;
k[5] = k5;
break;
}
}
str.Format(L"%02x%02x-%02x%02x-%02x%02x-%02x%02x", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]);
m_edit_password.SetWindowText(str.MakeUpper());
}

以上代码只是让你遇到那两个 JE 和一个 JA 跳转指令可以使它不成立,这个雏形生成的密码并没有与用户名进行关联,在下一小节当中会分析密码与用户名的之间的联系。

0x03 深入分析算法

在上一小节当中,通过穷举法来找出符合要求的密码。下面将会为大家分析下用户名和密码之间的关系。

咱继续,接下来这一小段将 ECX0x2 进行对比,其实好像也没什么用,我们看 JMP 跳转后的指令,如下:

这一段主要是将用户名的字符串转为 ASCII 版,在接下来的一个调用 CALL 后,EAX 中为用户名字符串地址,如下:

在上图中的 ds:[edi+0x1C] 处其实是前面处理 k[0] 和 k[6] 那个 CALL 后的返回值(即 AL 的值),所以我们需要将代码中的

1
2
3
4
5
6
if (AL > 0)
{
k[0] = k0;
k[6] = k6;
break;
}

改为如下:

1
2
3
4
5
6
if (AL > 0xA)
{
k[0] = k0;
k[6] = k6;
break;
}

接下来就是要做的就是那个对用户名处理的 CALL,通过分析知道,这个 CALL 的返回值类似于哈希值,我们需要得到这个值,回为这个值需要和 k[4]、k[5]、k[6]、k[7] 发生关系,我们不妨声明这样一个函数,用来处理 ASCII 码版的用户名字符串,这里有个取巧的办法,那就是利用 IDA,将 010 Editor 载入 IDA 中,我们需要先获取处理这个用户名字符串的 CALL 的地址,这个地址可以通过 OD 找到,找到之后,切换到 IDA,按 g 输入地址回车,再按下 F5 进行翻译,如下:

大致浏览一下,并不是太复杂,其中用到了一个数组,也就是上图圈出来的,双击进去,如下:

这里显示的比较乱,我们可把地址 02EDD840 转到 OD 里查看,如下:

利用 OD 的插件将这一段进行数据转换并以 C++ 形式复制出来,并将其命名为 dwEncodeArray,如下:

并将原来的代码做下调整,如下:

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
// 生成密码
void CMy010EditorRegisterDlg::OnBnClickedButtonGenerate()
{
using namespace std;
// 先判断用户名输入是否为空
if(m_edit_name.GetWindowTextLength())
{
m_edit_password.SetWindowText(L"");
CString str;
srand(time(NULL));
int nRet = 0x3E8;
BYTE k[8] = { 0x12,0x34,0x56,0x9C,0x90,0x09,0x87,0x65 };
m_edit_name.GetWindowText(str);

CStringA stra(str.GetBuffer(0));
str.ReleaseBuffer();
string st(stra.GetBuffer(0));
const char* cs = st.c_str();

// 用户名进行加密
DWORD dwKey = EncodeUserName(cs, 1, 0, nRet);
// CMP k[4],RetValue&0xFF
// CMP k[5],RetValue>>8&0xFF
// CMP k[6],RetValue>>16&0xFF
// CMP k[7],RetValue>>24&0xFF
k[4] = dwKey & 0xFF;
k[5] = dwKey >> 8 & 0xFF;
k[6] = dwKey >> 16 & 0xFF;
k[7] = dwKey >> 24 & 0xFF;

while (TRUE)
{
BYTE k0 = rand() % 0xFF;
BYTE k6 = k[6];
// AL = (k[0]^k[6]^0x18 + 0x3D)^0xA7
BYTE AL = (k0 ^ k6 ^ 0x18 + 0x3D) ^ 0xA7;
if (AL > 0xA)
{
k[0] = k0;
k[6] = k6;
break;
}
}

// ESI = (0x100*(k[1]^k[7] & 0xFF) + k[2]^k[5] & 0xFF)&0xFFFF
// EAX = ((ESI^0x7892+0x4D30)^0x3421)&0xFFFF / 0xB
// 判断余数是否为0,为0返回商,不为0返回0
while (TRUE)
{
BYTE k1 = rand() % 0xFF;
BYTE k7 = k[7];
BYTE k2 = rand() % 0xFF;
BYTE k5 = k[5];

DWORD ESI = (0x100 * (k1 ^ k7 & 0xFF) + k2 ^ k5 & 0xFF) & 0xFFFF;
DWORD EAX = (((ESI ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;

if (EAX % 0xB == 0 && EAX / 0xB == 0x3E8)
{
k[1] = k1;
k[7] = k7;
k[2] = k2;
k[5] = k5;
break;
}
}

str.Format(L"%02x%02x-%02x%02x-%02x%02x-%02x%02x", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7]);
m_edit_password.SetWindowText(str.MakeUpper());
}
else
{
MessageBox(L"Please enter name", L"warning", MB_OK);
}
}

EncodeUserName 函数如下:

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
int __cdecl CMy010EditorRegisterDlg::EncodeUserName(const char *a1, int a2, char a3, unsigned __int16 a4)
{
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 + dwEcodeArray[v8];
if (a2)
v10 = dwEcodeArray[v13]
+ dwEcodeArray[v7]
+ dwEcodeArray[v14]
+ dwEcodeArray[(unsigned __int8)(v8 + 47)] * (dwEcodeArray[(unsigned __int8)(v8 + 13)] ^ v9);
else
v10 = dwEcodeArray[v12]
+ dwEcodeArray[v7]
+ dwEcodeArray[v14]
+ dwEcodeArray[(unsigned __int8)(v8 + 23)] * (dwEcodeArray[(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;
}

编译运行后如下:

这样就达到了任意用户名注册!

0x04 去除网络验证

已加去除网络验证这一小节,今天在打开 010 Editor 时,发现它弹出如下界面:

出现这种情况是 010 Editor 会把该密码上传到它的服务器,如果发现不是通过正常渠道获取得的密码,它会把这个密码拉黑,出现上述这种情况你也不要慌,接下来我会为大家分析怎么来去除网络验证。

打开 OD,把 010 Editor 拖进来,F9 运行起来,点击 Check License,会断在如下地方:

因为之前下的断点我并没删除,F8 来到 00E08229 处,如果按照正常来说,在调用完这两个 CALL 后,EAX 中的值要为 0xDB,而此时变成了 0x113,第一个 CALL 调用后,EAX 的值为 0x2D,这个没问题,那么问题可能出现在第二个 CALL 内,这个 CALL 本应该把 EAX 的值修改为 0xDB,而它去却把它修改成了 0x113,我们不妨跟进这个 CALL 内,如下:

F8 运行到此处后,发现此处的 JE 指令跳转不成功,接下来的 mov eax,0x113 代码将被执行并返回,所以需将 JE 指令改为 JMP,改完之后,该 CALL 最后返回 0xDB,而不是 0x113,在后面你会发现即使修改了这处,它还是一样会验证失败,原因如下:

这个 JNS 指令它会跳到失败处,也就是那个字符串提示你的密码已被删除,注意图中的 EAX 的值,它为 -1,同时,在运行 00E0824B 的 CALL 指令会卡一下,如下:

整个注册界面会变灰,这时它在调用这个 CALL 进行网络验证,需要等待一下,等它验证完后,EAX 的值变会变成 -1,从而使得 JNS 指令成立,所以解决的办法是进入到网络验证这个 CALL 内,把 EAX 的值修改成不是负数,在这我把它改成 1,如下:

ret 多少,可以向下滑,看它正常 ret 多少,你这里也 ret 多少,最后 Dump 出来覆盖原文件就可以了,注册机还是不变,只要你从官网下载 32 位版的 010 Editor 并安装在你计算机后,运行注册机后注册即可,以后就不用担心拉黑了。

软件及注册机下载:

链接:https://pan.baidu.com/s/1pYqVMlVX8720uX8hdnMc9Q
提取码:nbow

(本文完)

留言

⬆︎TOP