010EDIT破解&注册机

xiaoeryu Lv5

010edit是一款非常好用的二进制文本编辑器,但是这款软件不是免费的,so让我们来分析一下它的算法,写一下注册机。

环境:

win7 32虚拟机,OD,IDA

分析目标

  1. 编写注册机
  2. 去除网络验证

开始分析

0x00 今天我们分析的是9.0版本

0x01 开始分析,我们点击register按钮,弹出来窗口让我们输入用户名&密码

随便先输入了一组用户名和密码,弹出了提示窗口,接下来我们用OD打开010来查找这个字符串

双击找到字符串的位置,往上分析看是从什么地方跳过来的,通过下断点尝试我们找到了这个它做验证的位置

0x02 接下来我们对关键算法函数进行详细分析

在这里我们发现指令对我们输入的密码进行了一系列的操作验证

我们需要把对密码的验证操作逐条分析出来,便于写注册机时使用

通过对这一块的分析,我们得到了密码的验证规则,接下来我们写代码尝试验证看我们分析的是否正确

#include "stdafx.h"
#include <Windows.h>
#include<time.h>

int main()
{
 srand(time(NULL));

 byte k[10] = { 0X11,0X22,0X33,0X9C,0X55,0X66,0X77,0X88,0X99,0X00 };

 while (true)
 {
  byte K0 = rand() % 0xff;
  byte K6 = rand() % 0xff;

  byte al = ((K0 ^ K6) ^ 0x18 + 0x3d) ^ 0xa7;

  if (al > 0)
  {
   k[0] = K0;
   k[6] = K6;
   break;
  }
 }

 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;
  }
 }
 printf("%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], k[8], k[9]);

 getchar();

    return 0;
}

这样我们打开vs执行上面的代码可以得到符合密码验证规则的一个秘钥

但是这样输进去之后,我们依旧是不能成功的,like this

因为这只是第一轮对密码的单独验证,在它后面还有第二轮验证,在第二轮验证中会把用户名也放进来和密码形成一个对应关系,符合了这两次的验证,我们的密码验证才算是通过。

0x03. 接下来我们开始分析第二轮的验证

继续单步F8往下跟,不远处会有一个跳转,跳转之后开始了第二轮的验证

在分析第二轮验证的时候我们要重点分析,用户名和密码之间的对应关系,来编写后面的代码

a. 通过对上面这块代码的分析我们知道了,用户名是和K[4] ~ K[7]有一定的对应关系的

0x04 现在我们梳理一下我们已有的信息

a. 如果K[3] = 9C的话, 密码只需要8个字节就可以了.
b. 我们通过上面的代码已经知道了, K[0] ~ K[7]怎么样才可以通过第一轮的验证.

c. 通过分析我们得知K[4] = sub_1062f86返回值的最后一个字节, K[5] = 倒数第二个字节, K[6], K[7]以此类推.
d. 然后我们发现通过上面的对应关系我们只要求出来了用户名的值, 就已经得到了K[4] ~ K[7]的值.
e. 然后我们需要思考, 怎么才能得到程序中传入用户名求出的值.

0x05 let’s do it

从OD里面找到sub_1062f86按F7进去>ctrl + x复制地址>在IDA里面G搜索这个地址(如果有重定位的话记得去掉重定位: edit>segments>rebase program )

按F5把汇编转换成C代码>会看到里面用到了一个数组>将这个数组以字节的方式拷贝出来

将数组以字节的形式拷贝出来(因为OD没装数据转换插件所以用的x32拷贝)

然后看一下参数是否需要修改(这里面需要修改最后一个参数的类型改为DWORD)(是否需要修改可以看OD里面的参数类型)

然后Ctrl+A全选>复制,将之作为一个函数粘贴到我们之前的代码里面

这样我们就完成了, 我们注册机代码的编写

// decode010.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <windows.h>
#include <time.h>

DWORD g_EcodeArray[] =
{ 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 __cdecl EnCodeUsername(const char *userName, int a2, char a3, unsigned __int16 a4)    // 计算用户名字符串的值
{
 const char *v4; // edx@1
 signed int v5; // esi@1
 signed int v6; // edi@1
 unsigned __int8 v7; // bl@2
 int v8; // eax@3
 int v9; // ecx@3
 int v10; // ecx@4
 int result; // eax@4
 int v12; // ecx@5
 unsigned __int8 v13; // [sp+8h] [bp-10h]@2
 unsigned __int8 v14; // [sp+Ch] [bp-Ch]@2
 unsigned __int8 v15; // [sp+10h] [bp-8h]@2
 int v16; // [sp+14h] [bp-4h]@1

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

int main()
{
 srand(time(NULL));

 // 0. 初始化
 int dwRet = rand() % 0x3E8;
 byte k[10] = { 0x11, 0x22, 0x33, 0x9C, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00 };

 // 用户名,加密求值
 char szName[50] = { 0 };
 printf("please enter user name:");
 scanf_s("%s", szName, 50);
 DWORD dwKey = EnCodeUsername(szName,1,0,dwRet);

 // 给k[4]~k[7]赋值

 // 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];

  byte al = ((k0 ^ k6) ^ 0x18 + 0x3d) ^ 0xa7;

  if (al >= 0xA)
  {
   k[0] = k0;
   k[6] = k6;
   break;
  }
 }

 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 == dwRet)
  {
   k[1] = k1;
   k[7] = k7;
   k[2] = k2;
   k[5] = k5;
   break;
  }
 }
 // 打印key
 printf("%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7], k[8], k[9]);

 getchar();
 getchar();

    return 0;
}

0x06 但是, 之后在使用一段时间后, 可能还会有网络验证, 会提示说key无效的问题

对于网络校验来说, 有两个方法可以过掉

  1. 自己搭建服务器, 分析它发送和接收的的数据包
  2. 修改文件使网络验证失效

在这里我们选用第二种方法, 来绕过它的网络验证
在修改exe文件之前, 我们先用010把它的重定位去除掉, 位置: NtHeader > FileHeader > Characteristics > FILE_RELOCS_STRIPPED字段

然后我们开始修改汇编代码
经过分析我们发现这里有两处验证的函数, 需要分别修改一下

修改过后保存一下

接下来我们继续修改后面一处验证
这个地方尝试了很多次, 发现eax返回1就可以通过这个验证, 像刚才一样我们把这里也修改保存下来

over

这样我们就不用担心他的网络验证问题了

  • 标题: 010EDIT破解&注册机
  • 作者: xiaoeryu
  • 创建于 : 2021-09-08 00:32:31
  • 更新于 : 2023-11-17 19:49:36
  • 链接: https://github.com/xiaoeryu/2021/09/08/010EDIT破解-注册机/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论