[160CrackMe]aLoNg3x.2

本文详细介绍了如何通过反编译和动态调试分析软件中隐藏按钮的触发条件,揭示了一个需要输入特定英文字母的codice才能激活后续流程的机制。在分析过程中,使用了多种逆向工程工具如DeDe、IDA和OllyDbg,并通过Python脚本复现了关键算法。最终,揭示了软件中反调试的策略,即通过误导分析者忽略错误分支来隐藏真实逻辑。
摘要由CSDN通过智能技术生成

还是跟前一个版本的一样,都是要藏按钮。

分析工具

  • IDR
  • DelphiDecompiler(DeDe)
  • IDA
  • OllyDbg

本篇博客采用的测试数据如下:

Name:1234567890
Codice:098765432

老样子,DeDe打开,有个AgainClick,难不成隐藏了按钮?
在这里插入图片描述
确实显示这个程序还有一个按钮。
在这里插入图片描述

然后IDR辅助分析
在这里插入图片描述

RegisterzClick

OD中定位,结合IDR给的注释,单步分析,可以看到执行完4429A8之后的返回值决定了是否发生跳转

分析并单步测试可以看出不跳转就会出现一个新的按钮(果然没这么简单),且register按钮会消失,要是发生跳转了就相当于失败,不会有任何变化。
在这里插入图片描述
所以我们着重分析4429A8函数,看看能不能把正确的codice搞出来

先在IDA中静态反编译出伪代码查看一下,下面以及根据一些函数功能做了函数重命名操作。可以看出是两层循环处理

int __fastcall sub_4429A8(int a1, int a2, int a3)
{
  int v4; // ebx
  int v5; // eax
  int v6; // esi
  int v7; // eax
  int v8; // ebx
  unsigned int v10[2]; // [esp-Ch] [ebp-24h] BYREF
  int *v11; // [esp-4h] [ebp-1Ch]
  int v12; // [esp+Ch] [ebp-Ch]
  int v13; // [esp+10h] [ebp-8h] BYREF
  int v14; // [esp+14h] [ebp-4h]
  int savedregs; // [esp+18h] [ebp+0h] BYREF

  v13 = a3;
  v14 = a2;
  LStrAddRef(a3);
  v11 = &savedregs;
  if ( lstrlen(v13) <= 4 )
  {
    v8 = 0;
  }
  else
  {
    v4 = 0;
    v5 = lstrlen(v13);
    if ( v5 > 0 )
    {
      v12 = v5;
      v6 = 1;
      do
      {
        v7 = lstrlen(v13);
        if ( v7 >= 1 )
        {
          do
            v4 += a1 * *(v13 + v7-- - 1) * *(v13 + v6 - 1);
          while ( v7 );
        }
        ++v6;
        --v12;
      }
      while ( v12 );
    }
    v8 = abs32(v4) % 666666;
    v14 = v14 % 80 + v14 / 89 + 1;
    if ( v8 == v14 )
      LOBYTE(v8) = 1;
    else
      v8 = 0;
  }
  LStrClr(&v13);
  return v8;
}

动态调试一下看看具体是如何实现的,

突然发现上面v4 += a1 * *(v13 + v7-- - 1) * *(v13 + v6 - 1);中的a1是作为参数传递进入这个函数的(在运行时是edi寄存器存储这个参数的值),OD动态调试发现这个参数是0,那么这个双层嵌套循环其实到最后结果始终为0,根本不必分析内部逻辑,看来这个循环下面的代码才是主要的。
在这里插入图片描述
切回到IDA看看,既然v4是0,给他取绝对值之后模666666得到的v8也是0,那么最终就是要让v14==0成立这个程序才算结束,这里的v14就是数值型的codice。
在这里插入图片描述
分析v4 += a1 * *(v13 + v7-- - 1) * *(v13 + v6 - 1);对应的汇编代码如下
在这里插入图片描述
目前看来我们需要一个codice满足codice%80+codice/89+1==0,于是认为这个值存在的我就写了个脚本爆破,当然肯定不是直接-10000000000直接一个个带进去算,这样效率太低了。经过分析发现codice的值必须是在 − 81 × 89 ∼ 0 -81\times 89\sim 0 81×890 − 7029 ∼ 0 -7029\sim 0 70290之间,这是因为 c o d i c e % 80 codice\%80 codice%80的值肯定在80以内。

所以限制了范围之后写了下面脚本。

for i in range(-7209,0,1):
    print(i)
    if i%80+i/89+1==0:
        break

但最后并没有成功得到那个值,这就很匪夷所思。。。

唯一的可能就是传入的那个参数根本不是0,不然不可能没有正确的codice,而之前分析的那个循环也可能是有意义的,而不是作者用来干扰的。
在这里插入图片描述
于是开始寻根溯源先发现这个参数来自于一个地址为445830的内存单元内的值(如下),然后才有了进入到4429A8之后右eax传给了edi进而参与算法处理。
在这里插入图片描述
既然445830出了问题,那么直接下内存访问断点,看是哪里对这个内存单元进行了处理,然后几次调试下来没有找到。。。

参考了别人的wp之后破案了,原来对这个内存单元的访问藏在了我们认为不应该进入的那个分支里,如下图。
在这里插入图片描述
回顾一下这个分支,它是提醒你输入不合规范,即包含了字母,需要重新输入。
在这里插入图片描述
也就是说第一次运行的时候你需要输入一个含字母的codice(尽管你可能知道这是会弹出窗口报错的),让程序提醒你你输入的字符里面必须不能有字母(“You MUST insert a valid Long Integer Value in the Code Editor… Thank you”),程序弹出这个窗口之后做的操作就是给这个445830内存单元中写入值。

细思极恐的是,只有这样,这个程序运行期间,每一次你输入只包含数字的codice,程序才会按照正常的算法(不会乘以0)运算、检验,而在你做这个之前所有输入的codice都不可能成功

所以刚刚本来调试过程中输入的codice都是数字的做法导致程序根本不会进入那个分支,那个内存单元里一直是默认的0,既然没有进入,那么下内存断点程序也不会断在那儿。

这也算是一种反调试了吧,正常人都不会把那个显然错误、需要避免跳入的分支考虑在内,而更多的是去向程序正确的输入靠拢,所以如果破解者只是顾着分析加密算法就会永远蒙在🥁里。

接下来分析那个分支就好,给那个跳转语句下断点

输入username:1234567890codice:ABCDEFG之后,程序断下之后单步执行,发现关键函数442A8C,运行完这个函数之后就把返回值写入445830内存单元。
在这里插入图片描述
这里我没有选择单步步入这个函数,而是直接在IDA中将这个函数反编译得到伪代码,稍作修改如下。

unsigned int __fastcall Libmain::TWindowDesigner::SelectAll(Libmain::TWindowDesigner *this)
{
  int v; // esi
  int j; // ebx
  int i; // ecx
  Libmain::TWindowDesigner *codice; // [esp+Ch] [ebp-4h] BYREF
  //unsigned int v5[2]; // [esp-Ch] [ebp-1Ch] BYREF
  //int *v6; // [esp-4h] [ebp-14h]
  //int savedregs; // [esp+10h] [ebp+0h] BYREF

  codice = this;
  LStrAddRef(this);
  //v6 = &savedregs;
  //v5[1] = &loc_442B21;
  //v5[0] = NtCurrentTeb()->NtTib.ExceptionList;
  //__writefsdword(0, v5);
  if ( lstrlen(codice) <= 5 )
  {
    v = 0;
  }
  else
  {
    v = 891;
    j = lstrlen(codice) - 1;
    if ( j > 0 )
    {
      i = 1;
      do
      {
        v += codice[i - 1] * (codice[i] % 17 + 1);
        ++i;
        --j;
      }
      while ( j );
    }
  }
  //__writefsdword(0, v5[0]);
  //v6 = &loc_442B28;
  LStrClr(&codice);
  return abs32(v % 29000);
}

用python复现一下

def get_mem_445830(codice):
    v=891
    j=len(codice)-1
    if j>0:
        i=1
        while True:
            v+=ord(codice[i-1])*(ord(codice[i])%17+1)
            i+=1
            j-=1
            if j==0:
                break
    return v%29000

if __name__=='__main__':
    codice=input("Codice:")
    print(get_mem_445830(codice))

由于我用的codice‘ABCDEFG’,用脚本运行一下看看结果如下。

也就是说以后内存单元中的值就是3743,换算成16进制就是0xe9f
在这里插入图片描述
接下来直接OD中按F9,这下修改codice输入框,改成原来的数据0987654321,程序断在了函数RegisterzClick。然后点击F8,看一下效果,这次终于不再是0了,也变成了值0xe9f
在这里插入图片描述
重新分析4429A8,这次直接用IDA得到伪C代码,整理之后如下

int __fastcall sub_4429A8(int mem, int a2, int a3)
{
  int temp; // ebx
  int v5; // eax
  int i; // esi
  int k; // eax
  int v; // ebx
  int j; // [esp+Ch] [ebp-Ch]
  int Name; // [esp+10h] [ebp-8h] BYREF
  int codice; // [esp+14h] [ebp-4h]
  
  Name = a3;
  codice = a2;
  if ( lstrlen(Name) <= 4 )
    v = 0;
  else
  {
    temp = 0;
    length = lstrlen(Name);
    if ( length > 0 )
    {
      j = length;
      i = 1;
      do
      {
        k = lstrlen(Name);
        if ( k >= 1 )
        {
          do
            temp += mem * Name[k-1] * Name[i - 1],k--;
          while ( k );
        }
        ++i;
        --j;
      }
      while ( j );
    }
    v = abs32(temp) % 666666;
    codice = codice % 80 + codice / 89 + 1;
    if ( v == codice )
      LOBYTE(v) = 1;
    else
      v = 0;
  }
  LStrClr(&Name);
  return v;
}

根据这个可以写注册机如下

def get_mem_445830(codice):
    v=891
    j=len(codice)-1
    if j>0:
        i=1
        while True:
            v+=ord(codice[i-1])*(ord(codice[i])%17+1)
            i+=1
            j-=1
            if j==0:
                break
    return v%29000

def register(Name,mem):
    if len(Name) <= 4:
        v = 0
    else:
        temp = 0
        length = len(Name)
        if length > 0:
            j = length
            i = 1
            while True:
                k = len(Name)
                if k >= 1:
                    while True:
                        temp += mem*ord(Name[k-1])*ord(Name[i-1])
                        k-=1
                        if k==0:
                            break
                    #print('0x%x'%temp)
                i+=1
                j-=1
                if j==0:
                    break
    #print('final:0x%x'%temp)
    v = temp % 666666
    #print(v)  
    truecodice=(80-((v-1)*89%80))+(v-1)*89
    return truecodice

if __name__=='__main__':
    name=input("Name:")
    codice=input("Codice:")
    mem=get_mem_445830(codice)
    print(register(name,mem))

在这里插入图片描述
这个时候出现了第二个按钮,其实用的是一样的套路,同样的操作再做一遍,效果如下:

这里不再用测试数据,而是换成自己的网名🤞
在这里插入图片描述

其实最主要是输入的那个英文字母的codice,最后的最后再总结一下流程

  1. 先输入含英文字母的codice
  2. 点击register按钮
  3. 输入正确的codice
  4. 点击register按钮
  5. register按钮消失,出现Again按钮
  6. 先输入含英文字母的codice(与第1步一样)
  7. 点击again按钮
  8. 输入正确的codice(与第3步一样)
  9. 点击again按钮
  10. again按钮消失
  11. 标题改成了我们的名字

参考:https://bbs.pediy.com/thread-249429.htm

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Em0s_Er1t

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值