xiaoyu2032 发表于 2022-4-30 11:42

练习笔记之160Crackme-007

本帖最后由 xiaoyu2032 于 2022-4-30 11:50 编辑

# 160CM-007

## 1.爆破

  这道题和上一道类似,要求逆算法的,但不管他,先爆破一下试试,爽爽再说。
  从爆破的角度来看,这道题其实难度和上一道一样,Delphi编程(可以IDR反编译)、字符串未加密(可以字符串搜索)。先拖进IDR中看一下,可以发现图片实际有两个按钮控件,一个是register,一个是again,分别对应一个OnClick的事件,双击事件函数可以直接查看到函数对应的代码。

  IDR可以直接识别一些delphi的函数,但是OD识别不了,因为跟踪调试还是需要OD,因此需要在OD中对调用的函数设置一下标签,这样方便阅读理解代码。register对应的函数代码如下:

``` asm
00442F28 >/.55            push ebp                                 ;reg_click
00442F29|.8BEC          mov ebp,esp
00442F2B|.83C4 F8       add esp,-0x8
00442F2E|.53            push ebx
00442F2F|.56            push esi
00442F30|.33C9          xor ecx,ecx
00442F32|.894D F8       mov ,ecx
00442F35|.8BD8          mov ebx,eax
00442F37|.33C0          xor eax,eax
00442F39|.55            push ebp
00442F3A|.68 22304400   push aLoNg3x_.00443022
00442F3F|.64:FF30       push dword ptr fs:
00442F42|.64:8920       mov dword ptr fs:,esp
00442F45|.8D55 F8       lea edx,
00442F48|.8B83 DC020000 mov eax,dword ptr ds:
00442F4E|.E8 ED02FEFF   call <aLoNg3x_.GetText>
00442F53|.8B45 F8       mov eax,                        ;comctl32.5D176109
00442F56|.8D55 FC       lea edx,
00442F59|.E8 FAF9FBFF   call <aLoNg3x_.Str2Long>
00442F5E|.8BF0          mov esi,eax
00442F60|.837D FC 00    cmp ,0x0
00442F64|.74 37         je short aLoNg3x_.00442F9D               ;判断是否非数字
00442F66|.B8 38304400   mov eax,aLoNg3x_.00443038                ;ASCII 59,"ou MUST insert a valid Long Integer Value in the Code Editor... Thank you :)"
00442F6B|.E8 00F6FFFF   call <aLoNg3x_.Showmessage>
00442F70|.8D55 F8       lea edx,
00442F73|.8B83 DC020000 mov eax,dword ptr ds:
00442F79|.E8 C202FEFF   call <aLoNg3x_.GetText>
00442F7E|.8B45 F8       mov eax,                        ;comctl32.5D176109
00442F81|.E8 06FBFFFF   call <aLoNg3x_.suanfa1>                  ;算法函数1 计算edi
00442F86|.A3 30584400   mov dword ptr ds:,eax
00442F8B|.BA 90304400   mov edx,aLoNg3x_.00443090                ;UNICODE "0"
00442F90|.8B83 DC020000 mov eax,dword ptr ds:
00442F96|.E8 D502FEFF   call <aLoNg3x_.SetText>
00442F9B|.EB 6F         jmp short aLoNg3x_.0044300C
00442F9D|>85F6          test esi,esi
00442F9F|.7E 5A         jle short aLoNg3x_.00442FFB
00442FA1|.8D55 F8       lea edx,
00442FA4|.8B83 D8020000 mov eax,dword ptr ds:
00442FAA|.E8 9102FEFF   call <aLoNg3x_.GetText>
00442FAF|.8B4D F8       mov ecx,                        ;comctl32.5D176109
00442FB2|.8BD6          mov edx,esi
00442FB4|.A1 30584400   mov eax,dword ptr ds:
00442FB9|.E8 EAF9FFFF   call <aLoNg3x_.suanfa2>                  ;算法函数2
00442FBE|.84C0          test al,al
00442FC0|.74 30         je short <aLoNg3x_.Error2>               ;判断是否正确
00442FC2|.33D2          xor edx,edx
00442FC4|.8B83 CC020000 mov eax,dword ptr ds:
00442FCA|.E8 6101FEFF   call <aLoNg3x_.SetVisable>            ;设置可见属性
00442FCF|.B2 01         mov dl,0x1
00442FD1|.8B83 E8020000 mov eax,dword ptr ds:
00442FD7|.E8 5401FEFF   call <aLoNg3x_.SetVisable>            ;设置可见属性
00442FDC|.33D2          xor edx,edx
00442FDE|.8B83 D8020000 mov eax,dword ptr ds:
00442FE4|.8B08          mov ecx,dword ptr ds:
00442FE6|.FF51 60       call dword ptr ds:             ;SetEnable
```

  从上述代码可以看到有两处调用的SetVisable函数,用于设置控件的可见属性。我们可以看一下这段代码:

``` asm
00442FC2|.33D2          xor edx,edx
00442FC4|.8B83 CC020000 mov eax,dword ptr ds:
00442FCA|.E8 6101FEFF   call <aLoNg3x_.SetVisable>
```

  代码第一行将edx清零,第二行将一个内存地址送如eax,第三行调用子函数。从这个地方可以很清楚的看出,第一行设置的是可见属性(0,隐藏;1,显示),第二行设置是是控件对应的ID。这就是一个典型的函数调用过程,call之前的代码都是为了将参数传递给子函数。
  因此爆破的话就是要控制程序执行流程进入到00442FC2地址,因此直接将je改成jmp跳转。

``` asm
00442F64   /74 37         je short aLoNg3x_.00442F9D
改成:
00442F64   /EB 5C         jmp short aLoNg3x_.00442FC2
```

  改完以后运行,会发现register按钮隐藏了,另一个again按钮显示出来了。同样的办法进入到again Onclick函数里面,可以发现程序过程和register Onclick函数基本相同,因此同样做一下修改:

``` asm
004430F6   /74 3A         je short aLoNg3x_.00443132
改成:
004430F6   /EB 63         jmp short aLoNg3x_.0044315B
```

  复制修改、保存,然后运行,依次点击register按钮和again按钮,按钮全部隐藏完毕。


## 2.算法分析

  通过前面的爆破分析,其实对于这个程序的结构已经比较了解了,在OD中将IDR识别的函数都进行标签标识后可以发现,仅剩两个函数没有识别出来(红框部分),应该对应为验证算法函数。

  运行程序,输入name和Code,F8跟踪,可以发现程序直接跳过00442F81,执行到00442FB9代码段,因此先分析一下函数4429A8。函数4429A8的主要算法代码如下图:

  在IDA中F5得到对应的伪代码如下:

  这个代码通常看起来有点一头雾水,因为我们不知道其中变量v4~v15到底代表什么内容,这时就需要F8单步跟踪一下程序运行过程,将变量根据实际含义修改一下名字,最终能得到如下图的代码:

  将代码复制到vs中,将其中不符合C语言语法的部分修改整理一下,就能得到下面的代码:

``` Cpp
    i=0;
    if ( Length > 0 )
    {
      k = Length;
      j = 1;
      do
      {
      m =Length;
      if ( m >= 1 )
      {
          do
    {
   i=i+n*str1*str1;
    }
          while ( m );
      }
      ++j;
      --k;
      }
      while ( k );
    }
    a = abs(i) % 666666;
    code = code % 80 + code/ 89 + 1;
    if ( a == code )
      result = 1;
    else
      result = 0;
```

  其中有个参数n,对应汇编代码中edi地址中的参数,在F8跟踪时发现这个数值一直是0,将n=0代入计算算法中,发现无论输入什么字符串,计算出来的a值都是零,不可能等于code,无法通过算法验证。

``` asm
00442A16|.0FAFD7      ||imul edx,edi
```

  因此,我们还需要找一下,程序哪里对这个n进行了赋值。我们往前查看一下代码,发现算法函数中,只有4429B7位置将eax赋值给了edi。

  再往前查,发现在进入算法调用前,在442FB4位置对eax进行了赋值,将地址445830的数值赋给了eax。

  那就先查一下什么地方对地址445830进行,在地址445830处[右键]-[查找参考]-[地址常量],可以查找到程序中所有涉及到这个地址常量的位置。

  如下图,一共找出不少位置,依次排查,我们可以发现442F86就在算法2稍微前面一点的位置,在算法1执行完成后就将eax赋值给445830。其他位置除again Onclick中也有一处外,都是将445830进行清零的操作。

  稍微往前查看一下程序代码就可以发现,前面有一处判断输入的code是否是数字,如果不是数字,就弹窗提示对话框告诉你code必须输入数字,然后执行算法1,跳过算法2;是数字的话,就跳过算法1,执行算法2。呃,原来是这里放了个烟雾弹迷惑人的,想要正确完成注册的话需要先在code其余输入非数字字符串,通过算法1的验证后,再输入数字字符串,通过算法2的验证。
  同样的方法,通过IDA获得算法的伪代码后,稍加改造,就能得到对应的C代码如下:

``` Cpp
int Length2 = strlen(str2);
n=0;
i = 891;
    m =Length2 - 1;
    if ( m > 0 )
    {
      j = 1;
      do
      {
      i = i + str2*(str2 % 17 + 1);
      ++j;
      --m;
      }
      while ( m );
    }
printf("i=%d\n",i);
n= abs(i % 29000);
printf("n=%d\n",n);
```

  这样就能通过输入的注册码字符串计算得到n的大小。
  由于算法2中对注册码的校验中的计算过程是code = code % 80 + code/ 89 + 1,这里面用了除余,没办法直接反向求解,只能用穷举法了算一下。

``` Cpp
i=1;
do
{
i++;
code=i%80+i/89+1;
}
while (code!=a);
```

  最终完成的注册码计算算法如下:

``` Cpp
#include "stdafx.h"
#include <Windows.h>
#include<stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
char str1,str2;
int i,j,k,m,n,a,result,code=0;
printf("请输入用户名(长度5~10):");
    scanf_s("%s",str1,30);
printf("请输入注册码字符(长度6以上):");
    scanf_s("%s",str2,30);
    int Length = strlen(str1);
int Length2 = strlen(str2);
n=0;
i = 891;
    m =Length2 - 1;
    if ( m > 0 )
    {
      j = 1;
      do
      {
      i = i + str2*(str2 % 17 + 1);
      ++j;
      --m;
      }
      while ( m );
    }
printf("i=%d\n",i);
n= abs(i % 29000);
printf("n=%d\n",n);

i=0;
    if ( Length > 0 )
    {
      k = Length;
      j = 1;
      do
      {
      m =Length;
      if ( m >= 1 )
      {
          do
    {
   i=i+n*str1*str1;
    }
          while ( m );
      }
      ++j;
      --k;
      }
      while ( k );
    }
    a = abs(i) % 666666;
    code = code % 80 + code/ 89 + 1;
printf("a=%d\n",a);
printf("code=%d\n",code);
/*
    if ( a == code )
result = 1;
    else
      result = 0;
   */
i=1;
do
{
i++;
code=i%80+i/89+1;
}
while (code!=a);

printf("用户名为:%s\n",str1);
printf("注册码为:%d \n",i);
system("pause");
return 0;
}
```

  运行注册码程序,输入用户名和注册码字符,结果如下:

  运行aLoNg3x.2.exe程序,输入用户名abcde,注册码abcdef,注册。

  然后再输入注册码58674159,注册,注册按钮隐藏了。

  注册按钮隐藏了,但是又出来一个again按钮,同样分析again Onclick函数,可以发现程序流程和register Onclick函数一样,算法也一样,因此输入注册码abcdef,点击again按钮。

  再输入注册码58674159,点击again按钮,完成。


## 3.总结

  到目前为止,已经成功完成了几个算法题,想想之前都只敢四处改跳转爆破,一看算法就头大,还是很有成就感的。
  如果大家想试试算法,从这几个入手,感觉还是不错的,不会直接打击到没有信心。不理解的汇编命令,百度一下,不理解的delphi函数,百度一下,一回生,二回熟,渐渐的就不怕他们了。

附:百度一下“Delphi反汇编内部字符串处理函数/过程不完全列表”,基本上反汇编后常见的delphi函数的功能都能查到。

hackerbob 发表于 2022-4-30 13:28

不辍,很详细,连算法也逆出来了

fjsxzhz 发表于 2022-5-1 12:57

感动!谢谢分享。

MC淡定 发表于 2022-5-1 17:27

不辍,很详细,连算法也逆出来了

JZSun 发表于 2022-5-1 21:35

赞赞赞赞

deepgo 发表于 2022-5-2 00:58

存下以后备用

Kow丶安慕希 发表于 2022-5-2 07:29

感动!谢谢分享。

wildbloom 发表于 2022-5-2 18:20

学习了逆向算法的思路

白鱼啊 发表于 2022-6-17 19:08

xiaoyu2032 发表于 2022-6-18 00:38

白鱼啊 发表于 2022-6-17 19:08
 IDR这个工具在哪下载啊 没找到 方便分享出来吗

论坛爱盘里有的
页: [1]
查看完整版本: 练习笔记之160Crackme-007