2017年全国大学生信息安全竞赛-填数游戏

2017年全国大学生信息安全竞赛

填数游戏:

题目描述:答案加flag{}

解题方法:题目附件下载下来发现是一个.exe文件,运行一下弹出一个输入框,随便输入几个数字,得到一个fail

这里我们还是一样的把它放进exeinfope.exe去查看一下他的属性信息:

这里我们发现是一个32位的无壳的exe文件,我们将它放进32位的IDA里面去分析一下它的源码:

IDA打开后shift+f12来搜索字符串,找到有用的字符串信息(success),然后索引回去它的源码,然后F5反汇编一下:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  struct SjLj_Function_Context *lpfctxa; // [esp+0h] [ebp-1B8h]
  struct SjLj_Function_Context *lpfctx; // [esp+0h] [ebp-1B8h]
  int (*v6)[9]; // [esp+4h] [ebp-1B4h]
  char v7[324]; // [esp+58h] [ebp-160h] BYREF
  char v8[4]; // [esp+19Ch] [ebp-1Ch] BYREF
  int *p_argc; // [esp+1A8h] [ebp-10h]

  p_argc = &argc;
  __main();
  Sudu::Sudu((Sudu *)v7);   //向v7中填充324个0
  Sudu::set_data((Sudu *)&_data_start__, v6);// 初始化数据,进入函数知道_data_start__应该是两字为一组,即4字节为一组。
  std::string::string((std::string *)lpfctxa);
  std::operator>><char>((std::istream::sentry *)&std::cin, v8);
  if ( (unsigned __int8)set_sudu((Sudu *)v7, (const std::string *)v8) != 1 ) //需要返回值为1
  {
    std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "fail");
    std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
  }
  else
  {
    if ( (unsigned __int8)Sudu::check((Sudu *)lpfctx) )  //需要返回值为1
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "success");
    else
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "fail");
    std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
  }
  std::string::~string((std::string *)v8);
  return 0;
}

这里我们看到了我们所想要的信息,这里我们最终的目的是输出succes,知道了之后我们并开始分析每一条代码

我们看到这里是一个条件选择语句,如果我们想要程序执行到succes的话,我们就需要第一个条件(就是用红框框框起来那里)为假,然后后面的条件为真才能输出我们要的succes

我们分析第一个条件,发现我们需要set_sudu函数的返回值为1,才可以满足条件为假,我们知道后就进去set_sudu函数里面分析一下

set_sudu函数:

int __cdecl set_sudu(Sudu *a1, const std::string *a2)
{
  Sudu *v3; // [esp+0h] [ebp-38h]
  std::string *v4; // [esp+0h] [ebp-38h]
  int v5; // [esp+Ch] [ebp-2Ch]
  int v6; // [esp+1Ch] [ebp-1Ch] BYREF
  int v7; // [esp+20h] [ebp-18h] BYREF
  char v8; // [esp+27h] [ebp-11h]
  const std::string *v9; // [esp+28h] [ebp-10h]
  int v10; // [esp+2Ch] [ebp-Ch]

  v10 = 0;
  v9 = a2;
  v7 = std::string::begin(v3);  //输入字符串的起始字符
  v6 = std::string::end(v4);   //输入字符串的结束符
  while ( (unsigned __int8)__gnu_cxx::operator!=<char const*,std::string>(&v7, &v6) )
  {
    v8 = *(_BYTE *)__gnu_cxx::__normal_iterator<char const*,std::string>::operator*(&v7);
      //v8 = *v7即遍历指针v7指向的值
    if ( (unsigned __int8)Sudu::set_number((Sudu *)(v10 / 9), v10 % 9, v8 - 48, v5) != 1 )       //第一个参数Sudu *a1 为v7的矩阵
        //第二个参数表示v7的行坐标
        //第三个参数表示v7的列坐标
        //第四个参数将输入的字符型数字转换成数字型,48是0的ASCLL码值
        //第五个参数表示?
      return 0;
    ++v10;
    __gnu_cxx::__normal_iterator<char const*,std::string>::operator++(&v7);
  }
  return 1;
}

这里我们看到,我们需要set_sudu函数函数返回1的话,就需要这里的判断条件为假,需要set_number函数的返回值为1,我们就要去set_number函数里面分析一下:

set_number函数:

int __userpurge Sudu::set_number@<eax>(int a1@<ecx>, Sudu *this, unsigned int a3, int a4, int a5)
{
  if ( !a4 )
    return 1;
  if ( (unsigned int)this > 8 
      || a3 > 8     //这里长度要小于等于81
      || *(_DWORD *)(a1 + 4 * (a3 + 9 * (_DWORD)this)) //判断a1[*this][a3]是否为0
      || a4 <= 0 
      || a4 > 9 )   //这里我们需要条件不成立
    return 0;
  *(_DWORD *)(a1 + 4 * (9 * (_DWORD)this + a3)) = a4; //// a1[*this][a3]=a4,将输入的非0数字写入到矩阵中
  return 1;
}

第一个条件分析完,我们可以得到两个信息,一我们输入的数为81个,二我们输入的数要为纯数字

我们去到第二个条件主函数这里:

if ( (unsigned __int8)Sudu::check((Sudu *)lpfctx) )  //需要返回值为1
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "success");

这里调用了check函数,我们查看一下:

BOOL __thiscall Sudu::check(Sudu *ecx0)
{
  Sudu *v2; // [esp+0h] [ebp-4h]
  Sudu *v3; // [esp+0h] [ebp-4h]

  return (unsigned __int8)Sudu::check_block(ecx0)
      && (unsigned __int8)Sudu::check_col(v2)
      && (unsigned __int8)Sudu::check_row(v3);
}

我们发现它调用了三个方法check_block,check_col,check_row这里用了“与”运算,所以我们需要三个函数的返回值都要为1,现在来逐个进行分析:

check_block函数:

int __thiscall Sudu::check_block(_DWORD *ecx0)
{
  char v2[10]; // [esp+12h] [ebp-26h]
  int v3; // [esp+1Ch] [ebp-1Ch]
  int v4; // [esp+20h] [ebp-18h]
  int m; // [esp+24h] [ebp-14h]
  int k; // [esp+28h] [ebp-10h]
  int j; // [esp+2Ch] [ebp-Ch]
  int i; // [esp+30h] [ebp-8h]

  for ( i = 0; i <= 8; ++i )
  {
    for ( j = 1; j <= 9; ++j )
      v2[j] = 1;
    for ( k = 0; k <= 8; ++k )  //这里进行初始化
    {
      v4 = 3 * (i / 3) + k / 3;
      v3 = 3 * (i % 3) + k % 3;
                                // 观察一组v4,v3的取值(v4表示矩阵横坐标,v3表示纵坐标)
                                // 0 0 (0,0)
                                // 0 1 (0,1)
                                // 0 2 (0,2)
                                // 0 3 (1,0)
                                // 0 4 (1,1)
                                // 0 5 (1,2)
                                // 0 6 (2,0)
                                // 0 7 (2,1)
                                // 0 8 (2,2)
                                // 这是9x9矩阵中,左上的3x3矩阵
      v2[ecx0[9 * v4 + v3]] = 0; // 将矩阵9个值,分别将v2数组的9个值赋值为0,如果3x3矩阵有重复数字,则必有v2[l]=1
    }
    for ( m = 1; m <= 9; ++m )
    {
      if ( v2[m] )  // 要返回1,因此需要v2[1]=0。则每个3x3的矩阵中分布着1~9的数字,且没有重复。
                    // 这是数独!!!
        return 0;
    }
  }
  return 1;
}

check_col函数:

int __thiscall Sudu::check_col(_DWORD *ecx0)
{
  char v2[10]; // [esp+Ah] [ebp-1Ah]
  int m; // [esp+14h] [ebp-10h]
  int k; // [esp+18h] [ebp-Ch]
  int j; // [esp+1Ch] [ebp-8h]
  int i; // [esp+20h] [ebp-4h]

  for ( i = 0; i <= 8; ++i )
  {
    for ( j = 1; j <= 9; ++j )
      v2[j] = 1;
    for ( k = 0; k <= 8; ++k )
      v2[ecx0[9 * k + i]] = 0;
    for ( m = 1; m <= 9; ++m )
    {
      if ( v2[m] )
        return 0;
    }
  }
  return 1;
}

这里是检查每行是否是不同的9个数字组成的

check_row函数:

int __thiscall Sudu::check_row(_DWORD *ecx0)
{
  char v2[10]; // [esp+Ah] [ebp-1Ah]
  int m; // [esp+14h] [ebp-10h]
  int k; // [esp+18h] [ebp-Ch]
  int j; // [esp+1Ch] [ebp-8h]
  int i; // [esp+20h] [ebp-4h]

  for ( i = 0; i <= 8; ++i )
  {
    for ( j = 1; j <= 9; ++j )
      v2[j] = 1;
    for ( k = 0; k <= 8; ++k )
      v2[ecx0[9 * i + k]] = 0;
    for ( m = 1; m <= 9; ++m )
    {
      if ( v2[m] )
        return 0;
    }
  }
  return 1;
}

检查每列是否是由不同的9个数字组成的

最后我们知道这是一个数独的填数游戏,我们将初始化的数据提取出来,需要去到_data_start__函数里面,将数据提取出来:

因为是四个字节为一组,所以提取出来之后需要用python脚本来处理一下:

data = [0,   0,   0,   0,   0,   0,   0,   0,   7,   0,
    0,   0,   5,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   6,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    2,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   1,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   7,   0,
    0,   0,   9,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   3,   0,
    0,   0,   0,   0,   0,   0,   4,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   2,   0,
    0,   0,   0,   0,   0,   0,   1,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   3,   0,
    0,   0,   0,   0,   0,   0,   1,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   5,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   7,   0,   0,   0,   1,   0,
    0,   0,   0,   0,   0,   0,   4,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   8,   0,   0,   0,
    2,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    5,   0,   0,   0,   9,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    8,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   8,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   1,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    3,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,
    0,   0,   0,   0
]

key = []
for i in range(len(data) // 4):
    key.append(data[i * 4])
print(key)
for i in range(9):
    for j in range(9):
        print(key[j + i * 9], end=' ')
    print('')

最后得到:

0 0 7 5 0 0 0 6 0 
0 2 0 0 1 0 0 0 7 
9 0 0 0 3 0 4 0 0 
2 0 1 0 0 0 0 0 0 
0 3 0 1 0 0 0 0 5 
0 0 0 0 0 0 7 1 0 
4 0 0 0 0 8 2 0 0 
0 0 5 9 0 0 0 8 0 
0 8 0 0 0 1 0 0 3

现在将这个数独补充完整,可以使用在线数独工具来处理一下:

将输入的数字写出来,然后已有的数字用0表示,再代码检测中0将跳过

得到:

340089102508406930016207058060875349709064820854392006093650071170023604602740590

succes

最后得到我们的flag:

flag{340089102508406930016207058060875349709064820854392006093650071170023604602740590}

posted @   张伟文  阅读(26)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示