010EditorV8.0.1逆向分析

  • 时间:
  • 浏览:
  • 来源:互联网

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的跳转条件

测试代码如下

  1. char szPassWord[8] = {0};  
  2.    szPassWord[3] = 0x9C;  
  3.      
  4.    while (TRUE)  
  5.    {  
  6.        szPassWord[0] = rand() % 0xFF;  
  7.        szPassWord[6] = rand() % 0xFF;  
  8.        DWORD EAX = ((szPassWord[0] ^ szPassWord[6]) ^ 0x18 + 0x30) ^ 0xA7;  
  9.        if (EAX >= 9)  
  10.        {  
  11.            break;  
  12.        }  
  13.    }  
  14.   
  15.    while (TRUE)  
  16.    {  
  17.        szPassWord[1] = rand() % 0xFF;  
  18.        szPassWord[2] = rand() % 0xFF;  
  19.        szPassWord[5] = rand() % 0xFF;  
  20.        szPassWord[7] = rand() % 0xFF;  
  21.        DWORD ECX = DWORD ECX = (((szPassWord[1] ^ szPassWord[7]) & 0xFF) * 0x100 + ((szPassWord[2] ^ szPassWord[5]) & 0xFF));
  22.        DWORD EAX = (((ECX ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;  
  23.        if (EAX % 0xB == 0 && EAX / 0xB <= 0x3E8)  
  24.        {  
  25.            break;  
  26.        }  
  27.    }  
  28.    szPassWord[4] = rand() % 0xFF;  
  29.   
  30.    for (int i = 0; i < 8; i++)  
  31.    {  
  32.        printf("%02X", szPassWord[i] & 0xFF);  
  33.    }  

运行结果: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语言查看

  1. int __cdecl sub_13BD120(const char *a1, int a2, char a3, __int16 a4)  
  2. {  
  3.   const char *v4; // edx  
  4.   signed int v5; // esi  
  5.   signed int v6; // edi  
  6.   unsigned __int8 v7; // bl  
  7.   int v8; // eax  
  8.   int v9; // ecx  
  9.   int v10; // ecx  
  10.   int result; // eax  
  11.   unsigned __int8 v12; // [esp+8h] [ebp-10h]  
  12.   unsigned __int8 v13; // [esp+Ch] [ebp-Ch]  
  13.   unsigned __int8 v14; // [esp+10h] [ebp-8h]  
  14.   int v15; // [esp+14h] [ebp-4h]  
  15.   
  16.   v4 = a1;  
  17.   v15 = 0;  
  18.   v5 = strlen(a1);  
  19.   v6 = 0;  
  20.   if ( v5 <= 0 )  
  21.     return 0;  
  22.   v12 = 0;  
  23.   v13 = 0;  
  24.   v7 = 15 * a4;  
  25.   v14 = 17 * a3;  
  26.   do  
  27.   {  
  28.     v8 = toupper((unsigned __int8)v4[v6]);  
  29.     v9 = v15 + dword_2E64148[v8];  
  30.     if ( a2 )  
  31.       v10 = dword_2E64148[v13]  
  32.           + dword_2E64148[v7]  
  33.           + dword_2E64148[v14]  
  34.           + dword_2E64148[(unsigned __int8)(v8 + 47)] * (dword_2E64148[(unsigned __int8)(v8 + 13)] ^ v9);  
  35.     else  
  36.       v10 = dword_2E64148[v12]  
  37.           + dword_2E64148[v7]  
  38.           + dword_2E64148[v14]  
  39.           + dword_2E64148[(unsigned __int8)(v8 + 23)] * (dword_2E64148[(unsigned __int8)(v8 + 63)] ^ v9);  
  40.     result = v10;  

在OD中使用插件取出数组复制到代码中,并将IDA分析的转换用户名字符串代码EncodeNameStr代码取出直接在自己的代码中使用

2.3.5 注册机2.0

编写MFC程序完成注册机的功能,使用到的主要函数就是对两部分的算法分析,达到完成注册认证的判断跳转,其中调用上面复制出来的对用户名字符串的求值函数

通过值来确定密码数组中下标为4,5,6,7的元素的值,再通过两个穷举得到其他三个元素实现达成条件,因为字符串求值函数最后一个参数为第二个判断函数的返回值,这里需要改变条件,写成固定值,下表为3的元素为固定值,这里写为0x9C,整个密码16位

  1.     //密码数组  
  2.     char szPassWord[8] = { 0 };  
  3.     //标识版本?  
  4.     szPassWord[3] = 0x9C;  
  5.   
  6.     //判断条件值  
  7.     DWORD JudgeResult = 0x3E8;  
  8.   
  9.     UpdateData(TRUE);  
  10.     CStringA str;  
  11.     str = m_NameStr;  
  12.   
  13.     //对用户名ascii码字符串求值  
  14.     DWORD result = EncodeNameStr(str, 1, 0, JudgeResult);  
  15.   
  16.     //根据求出字符串的值对部分字节赋值  
  17.     szPassWord[4] = result & 0xff;  
  18.     szPassWord[5] = result >> 0x8 & 0xff;  
  19.     szPassWord[6] = result >> 0x10 & 0xff;  
  20.     szPassWord[7] = result >> 0x18 & 0xff;  
  21.   
  22.     while (TRUE)  
  23.     {  
  24.         szPassWord[0] = rand() % 0xFF;  
  25.         DWORD EAX = ((szPassWord[0] ^ szPassWord[6]) ^ 0x18 + 0x30) ^ 0xA7;  
  26.         if (EAX >= 9)  

             break;  

  1.     }  
  2.   
  3.     while (TRUE)  
  4.     {  
  5.         szPassWord[1] = rand() % 0xFF;  
  6.         szPassWord[2] = rand() % 0xFF;  
  7.         DWORD ECX = (((szPassWord[1] ^ szPassWord[7]) & 0xFF) * 0x100 + ((szPassWord[2] ^ szPassWord[5]) & 0xFF));  
  8.         DWORD EAX = (((ECX ^ 0x7892) + 0x4D30) ^ 0x3421) & 0xFFFF;  
  9.         if (EAX % 0xB == 0 && EAX / 0xB == JudgeResult)  
  10.             break;  
  11.     }  

  

  1.     m_PasswordStr=" ";  
  2.     for (int i = 0; i < 8; i++)  
  3.     {  
  4.         CString s;  
  5.         s.Format(L"%02X", szPassWord[i] & 0xFF);  
  6.         m_PasswordStr += s;  
  7.     }  
  8.     UpdateData(FALSE);

测试注册机,完成功能

2.4 网络验证分析

2.4.1 跳过网络验证

当一段时间会发现注册码失效,原因是一段时间后会向服务器发送注册码验证,自己生成的验证码并未在服务器保存,导致认证失败,这里可以找到网络验证的地方跳过

通过字符串搜索找到引用提示字符串的地址

找到跳转来自的地方,确认网络验证的函数

将网络验证的函数修改,恒定返回值为1,确保跳转

这里推断过段时间[ECX+0x2C]会被设置为非0值,则会进行网络验证,这个函数会被多次调用验证

将这个跳转修改,让他正常跳转下去

保持返回值的正确

将修改的指令复制到可执行文件,保存文件,验证

网络验证成功,跳转

跟随跳转到判断

3.总结

和前两个程序对函数的调用分析和消息的响应分析不同的是,010Editor逆向分析的主要内容是对数据处理的算法分析,从提示窗口入手,向上找到关键字符串的引用,逆向找到分析数据的地方。
    这是基于QT的程序,用的也是面向对象的思想,所有从ECX中的this指针一样可以找到很多信息,通过变量及参数的查看分析到何时对输入数据的处理,做如何处理,分析算法需要耐心,一步步跟入,边分析边记录,最后形成一个整体的数据处理过程。再自己编写代码还原验证。这次运用了IDA的代码还原功能,自己再加以分析修改导出可以节省不少时间。

总的来说算法分析比较枯燥,足够的耐心和细心必不可少。

本文链接http://www.dzjqx.cn/news/show-617458.html