1.样本概况
1.1 应用程序信息
----------------------
应用程序名称:010Editor V8.0.1 32位
MD5值:9288F75678EB40C94398DD9F86E1C378
SHA1值:9B5891A124EF051A1175CB5249633AB956474A39
简单功能介绍:
1. 010 Editor是一个专业的文本编辑器和十六进制编辑器
2. 同时也是强大的二进制编辑工具,能查看和编辑硬盘驱动器上的任何二进制文件(文件大小无限制)和文本文件,包括Unicode文件,C / C ++,XML,PHP等。包括查找,替换,在文件中查找,替换文件,二进制比较,校验和/散列算法,直方图等
3. 独特的二进制模板技术使您可以了解任何二进制文件格式,支持下载共享的二进制模板和脚本,已多种不同格式导入导出二进制文件
4. 可以在不同的进制之前相互转换
1.2 分析环境及工具
系统环境:win7 Professional 32位(15pb实验环境)
工具:
1. 010Editor(十六进制编辑器)
2. DIE(PE文件分析)
3. VS2019(编写测试程序)
4. OllyDbg(动态调试分析)
5. Hash(计算MD5,SHA1)
6. IDA(静态分析)
1.3 分析目标
需要实现的功能:
1. 暴力破解,通过注册
2. 分析算法,完成注册机
3. 分析网络验证
2.具体分析过程
2.1 获取样本信息
2.1.1 DIE查壳
由VS2013编写的使用Qt库开发的32位程序
2.2 分析注册响应
2.2.1 进行注册
在帮助中找到关于点击注册
随意输入注册信息查看响应,弹出了一个对话框,提示无效的用户名和密码,并且该对话框阻塞了父窗口的点击,应该为模态对话框
2.2.2 OD中分析
由于使用Qt编写的程序,对Qt的库函数并不熟悉,不过最终调用的还是Windows生成窗口的API,使用OD附加程序,找到user32.dll模块在常见的创建窗口API上下断点(e窗口找到模块进入Ctrl+N查看所有名称,搜索API下断)
点击注册,程序在调用CreateWindowExW断下
在k窗口查看堆栈的调用
主要找到主模块中的调用,依次点击查找是否使用了一些敏感字符串
在地址0x14B5B29的位置找到关键字符串与弹出的窗口提示相对应
找到跳转到此处的关键的判断条件位置
下断点重新运行跟踪,断下最终跳转到输出提示无效密码的字符串的位置,找到的为跳转判断位置
2.2.3 暴力破解
对判断逻辑做基础的分析,找到关键的判断位置,直接修改掉
成功验证跳转到0x01D55920
上一步验证成功之后跳转的位置,理想条件为EDI=0xDB
进入函数0x01389C9B分析,需要的成功返回条件
通过验证显示的提示字符串
将关键跳转处的判断修改,暴力破解,保存新文件
选中修改的指令,右键复制到所执行文件,选择,跳转后再右键保存文件
打开保存的补丁文件,即可通过验证
但是这样修改的每次都会弹出注册页面需要点击按钮才会进入程序
找到关键的判断条件函数,修改,提示包含重定位
使用010Editor修改,关闭随机基址的符号
再次修改函数跳转
修改为
打开保存的文件直接跳过注册进入程序
2.2.4 字符串搜索
还可以通过查找提示窗口的字符串来定位函数调用的地方,对于唯一或只有少数的调用这个方法比较有效,快速定位到关键点,这里的检测许可提示就是唯一引用,跳转可以直接找到关键调用加以分析
2.3 分析算法
2.3.1 分析第一部分过程
找到最开始的返回关键判断条件值的函数开始分析
找到需要分析的关键函数
对代码的初步分析
判断输入是否为空
第一个函数0x00467644,对 p[0]^p[6] 的操作处理,返回结果保存在ECX
第二个函数 0x04083C8,对 ESI = (p[1] ^ p[7]) & 0xFF) * 0x100 + ((p[2] ^ p[5]) & 0xFF) 全零扩展 的处理,返回值在EAX
2.3.2 注册机1.0
实现主要函数中前半部分的跳转,编写初步功能的注册机测试,实现到0x13BDD8B的跳转条件
测试代码如下
- char szPassWord[8] = {0};
- szPassWord[3] = 0x9C;
- while (TRUE)
- {
- szPassWord[0] = rand() % 0xFF;
- szPassWord[6] = rand() % 0xFF;
- DWORD EAX = ((szPassWord[0] ^ szPassWord[6]) ^ 0x18 + 0x30) ^ 0xA7;
- if (EAX >= 9)
- {
- break;
- }
- }
- while (TRUE)
- {
- szPassWord[1] = rand() % 0xFF;
- szPassWord[2] = rand() % 0xFF;
- szPassWord[5] = rand() % 0xFF;
- szPassWord[7] = rand() % 0xFF;
- DWORD ECX = DWORD ECX = (((szPassWord[1] ^ szPassWord[7]) & 0xFF) * 0x100 + ((szPassWord[2] ^ szPassWord[5]) & 0xFF));
- DWORD EAX = (((ECX ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
- if (EAX % 0xB == 0 && EAX / 0xB <= 0x3E8)
- {
- break;
- }
- }
- szPassWord[4] = rand() % 0xFF;
- for (int i = 0; i < 8; i++)
- {
- printf("%02X", szPassWord[i] & 0xFF);
- }
运行结果:29E2-409C-F4F6-6B4D,输入单步查看判断跳转情况
2.3.3 分析第二部分过程
对上半部分实现成功跳转后的算法分析,完成最后的判断条件的达成,通过验证,主要操作是对用户名字符串格式转换为ascii,然后对其求值,依次与密码数组下标为4,5,6,7的元素进行比较,相等达成条件,最后将前半部分中第一个函数0x00467644的返回值与主函数的第一个参数9进行比较,最后返回值为0x2D
这里的关键函数就是对处理过的字符串进行求值,返回值为一个DWORD类型的值
对这个函数进行简单分析
2.3.4 使用IDA分析
使用IDA跳转到该地址分析0x0013BDDB6
使用IDA跳转到该地址分析0x0013BDDB6
找到该DWORD类型的数组,确实是对一个DWORD数组元素操作
F5转换为C语言查看
- int __cdecl sub_13BD120(const char *a1, int a2, char a3, __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 + dword_2E64148[v8];
- if ( a2 )
- v10 = dword_2E64148[v13]
- + dword_2E64148[v7]
- + dword_2E64148[v14]
- + dword_2E64148[(unsigned __int8)(v8 + 47)] * (dword_2E64148[(unsigned __int8)(v8 + 13)] ^ v9);
- else
- v10 = dword_2E64148[v12]
- + dword_2E64148[v7]
- + dword_2E64148[v14]
- + dword_2E64148[(unsigned __int8)(v8 + 23)] * (dword_2E64148[(unsigned __int8)(v8 + 63)] ^ v9);
- result = v10;
在OD中使用插件取出数组复制到代码中,并将IDA分析的转换用户名字符串代码EncodeNameStr代码取出直接在自己的代码中使用
2.3.5 注册机2.0
编写MFC程序完成注册机的功能,使用到的主要函数就是对两部分的算法分析,达到完成注册认证的判断跳转,其中调用上面复制出来的对用户名字符串的求值函数
通过值来确定密码数组中下标为4,5,6,7的元素的值,再通过两个穷举得到其他三个元素实现达成条件,因为字符串求值函数最后一个参数为第二个判断函数的返回值,这里需要改变条件,写成固定值,下表为3的元素为固定值,这里写为0x9C,整个密码16位
- //密码数组
- char szPassWord[8] = { 0 };
- //标识版本?
- szPassWord[3] = 0x9C;
- //判断条件值
- DWORD JudgeResult = 0x3E8;
- UpdateData(TRUE);
- CStringA str;
- str = m_NameStr;
- //对用户名ascii码字符串求值
- DWORD result = EncodeNameStr(str, 1, 0, JudgeResult);
- //根据求出字符串的值对部分字节赋值
- szPassWord[4] = result & 0xff;
- szPassWord[5] = result >> 0x8 & 0xff;
- szPassWord[6] = result >> 0x10 & 0xff;
- szPassWord[7] = result >> 0x18 & 0xff;
- while (TRUE)
- {
- szPassWord[0] = rand() % 0xFF;
- DWORD EAX = ((szPassWord[0] ^ szPassWord[6]) ^ 0x18 + 0x30) ^ 0xA7;
- if (EAX >= 9)
break;
- }
- while (TRUE)
- {
- szPassWord[1] = rand() % 0xFF;
- szPassWord[2] = rand() % 0xFF;
- DWORD ECX = (((szPassWord[1] ^ szPassWord[7]) & 0xFF) * 0x100 + ((szPassWord[2] ^ szPassWord[5]) & 0xFF));
- DWORD EAX = (((ECX ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;
- if (EAX % 0xB == 0 && EAX / 0xB == JudgeResult)
- break;
- }
- m_PasswordStr=" ";
- for (int i = 0; i < 8; i++)
- {
- CString s;
- s.Format(L"%02X", szPassWord[i] & 0xFF);
- m_PasswordStr += s;
- }
- UpdateData(FALSE);
测试注册机,完成功能
2.4 网络验证分析
2.4.1 跳过网络验证
当一段时间会发现注册码失效,原因是一段时间后会向服务器发送注册码验证,自己生成的验证码并未在服务器保存,导致认证失败,这里可以找到网络验证的地方跳过
通过字符串搜索找到引用提示字符串的地址
找到跳转来自的地方,确认网络验证的函数
将网络验证的函数修改,恒定返回值为1,确保跳转
这里推断过段时间[ECX+0x2C]会被设置为非0值,则会进行网络验证,这个函数会被多次调用验证
将这个跳转修改,让他正常跳转下去
保持返回值的正确
将修改的指令复制到可执行文件,保存文件,验证
网络验证成功,跳转
跟随跳转到判断
3.总结
和前两个程序对函数的调用分析和消息的响应分析不同的是,010Editor逆向分析的主要内容是对数据处理的算法分析,从提示窗口入手,向上找到关键字符串的引用,逆向找到分析数据的地方。
这是基于QT的程序,用的也是面向对象的思想,所有从ECX中的this指针一样可以找到很多信息,通过变量及参数的查看分析到何时对输入数据的处理,做如何处理,分析算法需要耐心,一步步跟入,边分析边记录,最后形成一个整体的数据处理过程。再自己编写代码还原验证。这次运用了IDA的代码还原功能,自己再加以分析修改导出可以节省不少时间。
总的来说算法分析比较枯燥,足够的耐心和细心必不可少。