分享

010editor4.03注册算法分析

 GRTSG休闲阁 2012-12-25
大半年忙东忙西,很少有时间弄技术上的东西了,前几天和一个学长闲聊,他说之前做了一个小东西,就是分析010editor的注册算法,然后就找了一个"最新"的来试试。弄了几天基本算完成了,后面一直偷懒没有写文档,这会儿慢慢补充好吧。

代码:
.text:00550267                 cmp     edi, 0DBh       ; 同时,要来到这里进行判断,有三条路线
.text:0055026D                 jnz     short loc_5502A7 ; 只有这条路线是正确的,此时edi必须要等于0xDB
.text:0055026F                 push    0FFFFFFFFh
.text:00550271                 push    offset aPasswordAccept ; "Password accepted. Thank you for purcha"...
.text:00550276                 call    ebp ; QString::fromAscii_helper(char const *,int) ; QString::fromAscii_helper(char const *,int)
.text:00550278                 mov     [esp+40h+var_18], eax
根据IDA的交叉引用,我们可以看到,达到这一步,必须满足:
代码:
loc_550267:             ; 同时,要来到这里进行判断,有三条路线
cmp     edi, 0DBh
jnz     short loc_5502A7 ; 只有这条路线是正确的,此时edi必须要等于0xDB
我们看到,理论上从在三种可能性,我们逐一来看:
总原则:
Edi必须等于0xDB

代码:
push    3C70h
push    5
call    sub_403963      ; 有三种返回情况:0xE7,0x4E,0x2D
mov     ecx, dword_7D6E24 ; 关键算法点之一
push    3C70h
push    5
mov     ebx, eax
call    sub_40897C      ; 这个函数会影响到edi的值(返回值赋值给edi)
mov     ecx, dword_7D6E24 ; 尽管后面有三条路线,但是不管怎么说edi都必须为0xDB
xor     edx, edx        ; '关键算法点之一'如果返回的是0x2D,那么edi就为0xDB
mov     edi, eax        ; '关键算法点之一’如果返回的是0xE7,那么edi为0x177
mov     eax, [ecx+34h]
test    eax, eax
setz    dl
mov     [esp+38h+var_10], edx
cmp     ebx, 0E7h
jz      loc_550267      ; 如果这一条路想走通,必须满足:ebx为0xE7,edi为0xDB
我们注意到,前面调用了两个函数:sub_403963和sub_40897C。并且,sub_403963的返回值决定了ebx的值,而sub_40897C的返回值决定了edi的值。
如果jz跳转成立,则跳转到了图2处,则此时满足ebx为0x2d,edi为0xDB既可实现注册。

 下面,分析sub_616050函数

代码:
.text:00616081                 jz      loc_6163DF      ; 类中的标志位比较
.text:00616087                 mov     ecx, [esi+8]
.text:0061608A                 cmp     [ecx+8], ebp
.text:0061608D                 jz      loc_6163DF      ; 类中的标志位比较
.text:00616093                 push    edi
.text:00616094                 lea     edx, [esp+30h+var_18] ; edx的值就是存放处理之后的密码串的地址
.text:00616098                 push    edx
.text:00616099                 mov     ecx, esi
.text:0061609B                 call    sub_40889B      ; 这个函数里面对输入的密码进行了操作
.text:006160A0                 mov     edi, offset off_7D58B8
.text:006160A5
.text:006160A5 loc_6160A5:                             ; CODE XREF: sub_616050+71j
.text:006160A5                 mov     eax, [edi]
.text:006160A7                 push    eax
.text:006160A8                 mov     ecx, ebx
.text:006160AA                 call    ds:??8QString@@QBE_NPBD@Z ; QString::operator==(char const *)
.text:006160B0                 test    al, al
.text:006160B2                 jnz     loc_616279
.text:006160B8                 add     edi, 4
.text:006160BB                 cmp     edi, offset unk_7D58BC
.text:006160C1                 jl      short loc_6160A5
首先,判断了两个标志位之后,会进入sub_40889B函数对输入的密码串进行操作,每位字符转化成相应的hex值(目前只考虑0-f的情况,不区分大小写)。
现在,我们假设到这里的时候,我们输入的密码串转换成了一个byte数组,称其为数组a。
比如,输入的用户名:loongzyd,密码:0fb6bcace7870c1fe696

数组a:名称:  图片1.jpg
查看次数: 2
文件大小:  3.1 KB
注意到这个函数有三种返回值:0xE7,0x4E,0x2D,而依据总原则:”edi必须等于0xDB”,加上后面的逆向倒推分析可知:该函数的返回值必须为0x2D。

那现在往下分析,怎么样才能让程序流程执行下去使得函数返回0x2D.

代码:
.text:006160C3                 mov     al, [esp+30h+var_15] ; al = a[3]
.text:006160C7                 mov     bl, byte ptr [esp+30h+var_14+1] ; bl = a[5]
.text:006160CB                 cmp     al, 9Ch         ; 判断a[3]是否为0x9c
.text:006160CD                 jnz     short loc_616149 ; 判断7,8位是不是F,C
如果a[3]为0x9c,则有一条分支往下走;

代码:
.text:00616149                 cmp     al, 0FCh        ; 判断7,8位是不是F,C
.text:0061614B                 jnz     short loc_616165
如果a[3]为0xfc,则有一条分支往下走;

代码:
.text:00616165                 cmp     al, 0ACh
.text:00616167                 jnz     loc_616279      ; 判断7,8位是不是A,C
如果a[3]为0xac,则有一条分支往下走;
结论:a[3]必须为0x9c,0xfc,,0xac这三个值之一,否则就会失败。

当a[3]为0x9c时:
代码:
.text:006160CF                 mov     dl, byte ptr [esp+30h+var_14+3] ; 第8位 a[7]
.text:006160D3                 xor     dl, [esp+30h+var_17] ; dl = a[7] ^ a[1] (第二位)
.text:006160D7                 mov     cl, byte ptr [esp+30h+var_14+2] ; 第7位 a[6]
.text:006160DB                 xor     cl, [esp+30h+var_18] ; cl = a[6] ^ a[0] (第一位)
.text:006160DF                 movzx   ax, dl          ; ax = a[7] ^ a[1]
.text:006160E3                 mov     byte ptr [esp+30h+var_1C], cl ; [esp+14] = a[6] ^ a[0]
.text:006160E7                 mov     ecx, 100h
.text:006160EC                 imul    ax, cx          ; ax = (a[7] ^ a[1]) << 8
.text:006160F0                 mov     dl, bl          ; dl = a[5] (第6位)
.text:006160F2                 xor     dl, [esp+30h+var_16] ; dl = a[5] ^ a[2] (第三位)
.text:006160F6                 movzx   cx, dl          ; cx = a[5] ^ a[2]
.text:006160FA                 mov     edx, [esp+30h+var_1C]
.text:006160FE                 add     ax, cx          ; ax = (a[7] ^ a[1]) << 8 + a[5] ^a[2]
.text:00616101                 push    edx             ; edx的值来源于[esp+14],等于a[6] ^ a[0]
.text:00616102                 movzx   edi, ax         ; edi = (a[7] ^ a[1]) << 8 + a[5] ^a[2]
.text:00616105                 call    sub_406D6B      ; eax = ((arg0 ^ 0x18) + 0x3D) ^ 0xA7
.text:0061610A                 movzx   eax, al         ; eax = (([esp+14] ^ 0x18) + 0x3D) ^ 0xA7
.text:0061610D                 push    edi
.text:0061610E                 mov     [esi+1Ch], eax  ; [esi+1c] = (([esp+14] ^ 0x18) + 0x3D) ^ 0xA7
.text:00616111                 call    sub_4077D4      ; eax = (((arg0 ^ 0x7892) + 0x4d30) ^ 0x3421) / 0xB
.text:00616116                 mov     ecx, [esi+1Ch]  ; 上一步中,除以0xB必须要整出,否则eax = 0,失败
.text:00616119                 movzx   eax, ax
.text:0061611C                 add     esp, 8
.text:0061611F                 mov     [esi+20h], eax  ; 假设得到的eax恰好为0x3E8
.text:00616122                 test    ecx, ecx        ; 坚决不能转 [esi+0x1C]的值不能为0
.text:00616124                 jz      loc_616279
要进过上述的计算,为了不是其跳转到eax=0xE7分支上面造成失败,必须满足:
1.((a[6] ^ a[0] ^ 0x18) + 0x3D ) ^ 0xA7 != 0 (令商为dev1)
2.arg0 = (a[7] ^ a[1])  <<  8 + a[5] ^ a[2]
  ((arg0 ^ 0x7892 + 0x4d30 ) ^ 0x3421) &&0x0ffff必须整除0xB,并且商不能大于0x3E8
(令商为dev2)
名称:  图片2.jpg
查看次数: 1
文件大小:  22.9 KB

edi的值影响后面的计算流程

代码:
.text:006162BA loc_6162BA:                             ; CODE XREF: sub_616050+24Cj
.text:006162BA                 mov     cl, [esp+30h+var_15] ; 取a[3],判断标志位
.text:006162BE                 cmp     cl, 9Ch
.text:006162C1                 jnz     short loc_6162D2 ; 判断a[3]是否为0x9c
代码:
.text:006162C3                 mov     eax, [esp+30h+arg_0] ; 参数固定为5
.text:006162C7                 or      edx, 0FFFFFFFFh
.text:006162CA                 cmp     eax, [esi+1Ch]  ; (pass[6] ^ pass[0] ^ 0x18 + 0x3D ) ^ 0xA7的值必须要大于等于5
.text:006162CD                 jmp     loc_616352
条件3:
((a[6] ^ a[0] ^ 0x18) + 0x3D ) ^ 0xA7 >= 5

代码:
.text:00616220                 mov     eax, [ecx+0Ch]  ; 此时eax的值,为用户名字符串的地址
.text:00616223                 mov     edx, [esi+20h]  ; 上面除以0xB后的商
.text:00616226                 xor     ecx, ecx
.text:00616228                 cmp     [esp+30h+var_15], 0FCh ; 根据a[3]是否为0xFC,来决ecx为0,还是1
.text:0061622D                 push    edx
.text:0061622E                 setnz   cl              ; a[3]等于0xfc,则ecx=0,否则ecx=1
.text:00616231                 push    edi
.text:00616232                 push    ecx
.text:00616233                 push    eax             ; 根据用户名,进行一系列的运算
.text:00616234                 call    sub_40263F      ; 计算出来的'hash'值,决定了a[4],a[5],a[6],a[7]的值
sub_40263F是一个比较关键的函数,它可以根据上面的几个运算结果及用户名,算出一个’hash’,而这个’hash’可以决定a[4],a[5],a[6],a[7]的值
a[4] = hash & 0xff; a[5] = (hash >> 8) &0xff
a[6] = (hash >> 16) &0xff; a[7] = (hash >> 24) &0xff


将hash获取之后,再根据条件1,2,3我们就可以确定所有的密码字符(情况不唯一),具体参看源代码。

当a[3]为0xac时:
名称:  图片3.jpg
查看次数: 1
文件大小:  26.4 KB

这个和前面的条件2是相同的:
arg0 = (a[7] ^ a[1])  <<  8 + a[5] ^ a[2]
((arg0 ^ 0x7892 + 0x4d30 ) ^ 0x3421) &&0x0ffff必须整除0xB,并且商不能大于0x3E8

代码:
.text:006161BB                 movzx   ecx, [esp+30h+var_F] ; ecx = A[9]
.text:006161C0                 movzx   eax, byte ptr [esp+30h+var_14] ; eax = a[4]
.text:006161C5                 movzx   edx, bl         ; edx = a[5]
.text:006161C8                 xor     ecx, edx        ; ecx = A[9] ^ a[5]
.text:006161CA                 movzx   edx, [esp+30h+var_10] ; edx = A[8]
.text:006161CF                 xor     eax, edx        ; eax = a[4] ^ A[8]
.text:006161D1                 movzx   edx, [esp+30h+var_18] ; edx = a[0]
.text:006161D6                 shl     ecx, 8
.text:006161D9                 add     ecx, eax        ; ecx = (A[9] ^ a[5]) << 8 + a[4] ^ A[8]
.text:006161DB                 movzx   eax, byte ptr [esp+30h+var_14+2] ; eax = a[6]
.text:006161E0                 shl     ecx, 8          ; ecx = ((A[9] ^ a[5]) << 8 + a[4] ^ A[8]) << 8
.text:006161E3                 xor     eax, edx        ; eax = a[6] ^ a[0]
.text:006161E5                 add     ecx, eax        ; ecx = ((A[9] ^ a[5]) << 8 + a[4] ^ A[8]) << 8 + a[6] ^ a[0]
.text:006161E7                 push    (offset loc_5B8C26+1)
.text:006161EC                 push    ecx
.text:006161ED                 call    sub_403882
.text:006161F2                 mov     ebp, eax        ; 不为0,且要大于等于0x3c70
代码:
.text:0061634B                 or      edx, 0FFFFFFFFh ; sub_403882的返回值必须大于0x3c70
.text:0061634E                 cmp     [esp+30h+arg_4], ebp ; 参数固定为0x3c70
sub_403882是个关键的函数,参数的值ecx涉及了多个密码数组元素,它必须满足返回值不为0,同时要大于等于0x3c70

代码:
.text:00616220                 mov     eax, [ecx+0Ch]  ; 此时eax的值,为用户名字符串的地址
.text:00616223                 mov     edx, [esi+20h]  ; 上面除以0xB后的商
.text:00616226                 xor     ecx, ecx
.text:00616228                 cmp     [esp+30h+var_15], 0FCh ; 根据a[3]是否为0xFC,来决ecx为0,还是1
.text:0061622D                 push    edx
.text:0061622E                 setnz   cl              ; a[3]等于0xfc,则ecx=0,否则ecx=1
.text:00616231                 push    edi
.text:00616232                 push    ecx
.text:00616233                 push    eax             ; 根据用户名,进行一系列的运算
.text:00616234                 call    sub_40263F      ; 计算出来的'hash'值,决定了a[4],a[5],a[6],a[7]的值
同样,也是根据用户名计算出一个hash,值得注意的是,当a[3]为0xac的时候,sub_40263f的一个参数是之前sub_403882函数的返回值。

大致的逻辑就可以确定了,详细见源代码。

当a[3]为0xfc时,这种情况不能满足条件。


下面关注一下获取hash的函数sub_614560,总体来看是根据用户名字符串依次进行循环操作,因为设计很多的移位,特别是imul指令,不太好用C语言表示,故直接用内联汇编来编写:
hash获取函数
代码:
int get_hash(char *name, int arg4, int arg8, int argc)
{
  int name_length;
  int argc_tmp;
  int arg8_tmp;
  int eax_tmp;
  int ecx_tmp;
  int edx_tmp;
  int index_i;
  int index_j;
  int index;
  int hash;

  name_length = strlen(name);
  argc_tmp = argc << 4;
  argc_tmp -= argc;   //edi
  arg8_tmp = arg8 << 4;
  arg8_tmp += arg8;   //esi
  index_i = 0;
  index_j = 0;
  hash = 0;

  if (arg4 != 0)
  {
    for(index = 0; index < name_length; index++)
    {
      if (name[index] >= 'a' && name[index] <= 'z')
      {
        eax_tmp = name[index] - 0x20;
      }
      else
      {
        eax_tmp = name[index];
      }
      _asm
      {
        mov eax, eax_tmp
        mov ecx, hash_table[eax*4]
        lea edx, [eax+0xd]
        and edx, 0xff
        add ecx, hash
        xor ecx, hash_table[edx*4]
        add eax, 0x2f
        and eax, 0xff
        imul ecx, hash_table[eax*4]
        mov edx, arg8_tmp
        and edx, 0xff
        add ecx, hash_table[edx*4]
        mov edx, index_j
        mov eax, argc_tmp
        and eax, 0xff
        add ecx, hash_table[eax*4]
        and edx, 0xff
        add ecx, hash_table[edx*4]
        mov hash, ecx
      }
      index_j += 0x13;
      argc_tmp += 0xd;
      arg8_tmp += 9;
    }
  }
  else
  {
    
    for(index = 0; index < name_length; index++)
    {
      if (name[index] >= 'a' && name[index] <= 'z')
      {
        eax_tmp = name[index] - 0x20;
      }
      else
      {
        eax_tmp = name[index];
      }
      _asm
      {
        mov eax, eax_tmp
        mov ecx, hash_table[eax*4]
        lea edx, [eax+0x3f]
        add edx, hash
        add eax, 0x17
        and ecx, 0xff
        xor ecx, hash_table[ecx*4]
        and eax, 0xff
        imul ecx, hash_table[eax*4]
        mov eax, arg8_tmp
        and eax, 0xff
        add edx, hash_table[eax*4]
        mov eax, index_i
        mov ecx, argc_tmp
        and ecx, 0xff
        add edx, hash_table[ecx*4]
        and eax, 0xff
        add edx, hash_table[eax*4]
        mov hash, edx
      }
      index_i += 0x7;
      arg8_tmp += 0x9;
      argc_tmp += 0xd;
    }
  }
  return hash;
}

其中,要涉及一个数组表,这里采用IDA中的IDC脚本语言来获取(具体参加代码),运行之后在C盘根目录中会出现一个1.txt文件,里面就是需要的数组。
IDC脚本:
代码:
auto address;
auto num;
auto str;
auto index;
auto file_handle;

address = 0x7D53E8;
index = 0;

file_handle = fopen("c:\\1.txt", "w");
writestr(file_handle, "hash_table[] = {");
while(1)
{
   num = Dword(address);
   if (num != 0)
   {
      str = ltoa(num, 16);
      str = "0x" + str + ", ";
      Message("%s\n", str);
      writestr(file_handle, str);
      address = address + 4;
      index = index + 1;
      if (index % 4 == 0)
      {
         writestr(file_handle, "\n");
      }
   }
   else
   {
      break;
   }
}
writestr(file_handle, "};");
fclose(file_handle);
Message("over\n", num);
比较纠结的还有一个,sub_614880,逆向推理之后进行了偷懒,简化成:
(不是考虑了所有的情况)

代码:
for (i = 0x40370; i < 0xffffff; i += 0x11)
  {
    _asm
    {
      mov eax, 0x0f0f0f0f1
      mov ecx, i
      mul ecx
      shr edx, 4
      mov t, edx
    }
    if (t < 0x3c70)
    {
      continue;
    }
    if (t * 0x11 == i)
  {
  符合条件
  }
   }
通过上面的分析,算法部分就完成了,注册机里面的密码,绝大部分都是符合算法本身的;
不过因为软件有联网的检测,注册一段时间之后不能重新注册,应该是和MAC地址绑定了,在虚拟机注册之后回滚了,然后几天之后发现同样的用户名和密码就不能用了。
有两处是需要注意的:
代码:
.text:00617B53                 cmp     dword ptr [esi+2Ch], 0 ; 如果[esi+0x2C]不为0,则返回的是0x113,应该就失败了
代码:
.text:005500D8                 cmp     dword ptr [ecx+2Ch], 0 ; 这里ecx应该是某个对象的指针 要搞清楚[ecx+0x2C]的含义
这两处中ecx和esi的值是一样的,所代表的含义也是一样的。在第一次进行注册之前,[this+0x2c]为0,用户名和密码符合算法要求后即可正确注册,虚拟机回滚几天之后,再次注册的时候,[this+0x2c]的值变成了1,意味着即使有相同的用户名和密码也不能注册了。估计是通过网络验证限制了一台机器的注册次数,当我们验证的时候需要注意到这点,[this+0x2c]的值和我们输入的用户名和密码是无关的,手动修改即可。


简易注册机:
010editor.txt
注册机算法里面,简略了很多数学的操作,从严谨上来说,忽略了很多情况,只是能找到符合算法的用户名和密码。从使用角度上来说,爆破即可。

赶着末日重生第一天,有些措辞或者介绍的不详细,慢慢修复,敬请谅解

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约