ctf题库--该题不简单

<题目>
无语了,想给你们制造点悬念都没有了,哎!直接去做题吧。
解题链接: http://ctf5.shiyanbar.com/crack/3/
<解答>
该题目虽然被列为难题,但是如果选对正确的工具和应用正确的方式去操作还是很容易解答的。

※拿到题目后,打开程序,按照要求输入hello,注册码随意,点击按钮出现错误信息,记录下错误信息内容。
错误
※使用PEiD查壳,发现该程序未加壳。
查壳
接下来介绍两种不同的方法来破解此程序,

  1. OllyDbg法:

※将exe文件丢到OD中去,主窗口上右键->中文搜索引擎->智能搜索,然后在其中找到刚刚记录下的错误提示:
智能搜索
※双击“秘钥无效”转到相应位置,向上找发现该命令可以跳过“秘钥正确”,因此这是一个关键跳转,那么jnz上面的call命令就极有可能是一个关键call,让我们在这两个关键点处 摁f2设置断点。
关键
运行程序后,输入用户名和密码,发现程序断在了刚刚设置的call处,因此可判定这是一个决定注册号正确与否的关键call。
关键call
※单步步入(f7)关键call之后再连续单步步过(f8),仔细观察寄存器直到出现了注册码。
FPU
记录下该注册码打开程序并输入,最终提示秘钥正确!
秘钥正确
但是,当我们换一个用户名时发现其注册码相应也随之改变了。
不知道叫啥
因此我们推测注册码可能是根据用户名计算而得来的。

2.IDA法:

※将exe再次丢进IDA中,在Dialogfun中发现其判断标准都来自sub_4011D0。双击call sub_4011D0 进入查看。
IDA
※使用f5将其尝试反汇编成c/c++语言。
IDA反汇编代码如下:

 BOOL sub_4011D0()
{
  unsigned int v1; // esi
  CHAR String[4]; // [esp+8h] [ebp-60h]
  __int16 v3; // [esp+25h] [ebp-43h]
  char v4; // [esp+27h] [ebp-41h]
  CHAR String2; // [esp+28h] [ebp-40h]
  char v6; // [esp+29h] [ebp-3Fh]
  __int16 v7; // [esp+45h] [ebp-23h]
  char v8; // [esp+47h] [ebp-21h]
  CHAR String1; // [esp+48h] [ebp-20h]
  char v10; // [esp+49h] [ebp-1Fh]
  __int16 v11; // [esp+65h] [ebp-3h]
  char v12; // [esp+67h] [ebp-1h]

  String[0] = 0;
  memset(&String[1], 0, 0x1Cu);
  v3 = 0;
  v4 = 0;
  String1 = 0;
  memset(&v10, 0, 0x1Cu);
  v11 = 0;
  v12 = 0;
  String2 = 0;
  memset(&v6, 0, 0x1Cu);
  v7 = 0;
  v8 = 0;
  if ( GetDlgItemTextA(hDlg, 1000, String, 16) < 5 )
    return 1;
  GetDlgItemTextA(hDlg, 1001, &String1, 16);
  v1 = 0;
  if ( strlen(String) != 0 )
  {
    do
    {
      *(&String2 + v1) = (v1 + v1 * String[v1] * String[v1]) % 0x42 + 33;
      ++v1;
    }
    while ( v1 < strlen(String) );
  }
  strcpy(String, "Happy@");
  lstrcatA(String, &String2);
  return lstrcmpA(&String1, String) != 0;
}

※只需关注最后几行代码,将其拷贝到VS2010中去,并稍加修改使得其能够被编译器所编译。

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
    string String="hello";
    string String2;
    int v1 = 0;
    do
    {
        String2.insert(String2.begin()+v1,(v1+v1*String[v1]*String[v1])%0x42+33);
        v1++;
        //cout<<String[v1];
    }
    while (v1<String.length());
    cout<<"Happy@";
    for(int j=0;j<String2.length();j++)
    {
        cout<<String2[j];
    }
    cout<<endl;
    return 0;
}

我们为其添加头文件和能够输出的函数,运行后得到了值“!GA0U”,该值就是IDA中的“&String2”。
IDA最后三行的意思分别可以大致解释为:

 strcpy(String,"Happy@");      //将"Happy@"赋给String;
 lstrcatA(String,&String2);    //将计算得到的String接到"Happy@"后面;
 return lstrcmpA(&String1,String)!=0  //将这个值与用户输入的注册码相对比并返回。

这就解释了为什么不同的用户名对应了不同的注册码。像刚刚编译的带有类似这种算法的程序大致就是我们所常见的算号器的原型。

猜你喜欢

转载自blog.csdn.net/miko2018/article/details/81508919