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

测试文件:http://static2.ichunqiu.com/icq/resources/fileupload/CTF/echunqiu/qgdxs/numgame_8808BCE6D17A3EF92461A50079264767.zip

 

1.准备

获取信息

  • 32位文件

 

2.IDA打开

int __cdecl main(int argc, const char **argv, const char **envp)
{
  std::string *v4; // [esp-18h] [ebp-1B8h]
  int (*v5)[9]; // [esp-14h] [ebp-1B4h]
  struct _Unwind_Exception *lpuexcpt; // [esp+0h] [ebp-1A0h]
  struct SjLj_Function_Context fctx; // [esp+4h] [ebp-19Ch]
  void *v8; // [esp+28h] [ebp-178h]
  std::string **v9; // [esp+2Ch] [ebp-174h]
  char v10; // [esp+40h] [ebp-160h]
  int v11; // [esp+184h] [ebp-1Ch]
  char v12; // [esp+188h] [ebp-18h]
  int *v13; // [esp+190h] [ebp-10h]

  v13 = &argc;
  fctx.personality = (_Unwind_Personality_Fn)__gxx_personality_sj0;
  fctx.lsda = dword_47CAB8;
  fctx.jbuf[0] = &v12;
  v8 = &loc_4015A5;
  v9 = &v4;
  _Unwind_SjLj_Register(&fctx);
  __main();
  Sudu::Sudu(&v10);                             // v10中填充324个0
  Sudu::set_data((int)&v10, (Sudu *)&_data_start__, v5);// 初始化数据,进入函数知道_data_start__应该是两字为一组,即4字节为一组。
  fctx.call_site = -1;
  std::string::string(&v11);
  fctx.call_site = 1;
  std::operator>><char,std::char_traits<char>,std::allocator<char>>((std::istream::sentry *)&std::cin, &v11);
  if ( (unsigned __int8)set_sudu((Sudu *)&v10, (const std::string *)&v11) ^ 1 )// 需要函数返回1
  {
    std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "fail");
    std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
    lpuexcpt = 0;
  }
  else
  {
    if ( Sudu::check((Sudu *)&v10) )            // 需要函数返回1
    {
      fctx.call_site = 1;
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "success");
    }
    else
    {
      fctx.call_site = 1;
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "fail");
    }
    std::ostream::operator<<(std::endl<char,std::char_traits<char>>);
    lpuexcpt = 0;
  }
  std::string::~string(v4);
  _Unwind_SjLj_Unregister(&fctx);
  return (int)lpuexcpt;
}

 

 

3.代码分析

Sudu::set_data((int)&v10, (Sudu *)&_data_start__, v5);函数

int __userpurge Sudu::set_data@<eax>(int a1@<ecx>, Sudu *this, int (*a3)[9])
{
  int result; // eax
  signed int j; // [esp+Ch] [ebp-Ch]
  signed int i; // [esp+10h] [ebp-8h]

  for ( i = 0; i <= 8; ++i )                    // 9行9列数据
  {
    for ( j = 0; j <= 8; ++j )
    {
      result = j + 9 * i;                       // i+1行j+1列的坐标
      *(_DWORD *)(a1 + 4 * result) = *((_DWORD *)this + 9 * i + j);// 可以计算得到al
    }
  }
  return result;
}

使用脚本计算v10矩阵

origin = [
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,
1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0,
9, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0,
7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 3, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0,
9, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 2, 0, 0, 0,
3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0,
7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0,
9, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
4, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0,
6, 0, 0, 0, 7, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0,
5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0,
9, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 6, 0, 0, 0,
7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0,
2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0,
9, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0,
4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0,
8, 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(origin)//4):
        key.append(origin[i*4])
for i in range(9):
    for j in range(9):
        print(key[j+i*9], end=' ')
    print('')
script
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 

 

第一处判断条件

if ( (unsigned __int8)set_sudu((Sudu *)&v10, (const std::string *)&v11) ^ 1 )// 需要函数返回1

打开函数

signed int __cdecl set_sudu(Sudu *a1, const std::string *a2)
{
  std::string *v2; // ST00_4
  std::string *v4; // [esp+0h] [ebp-38h]
  int v5; // [esp+Ch] [ebp-2Ch]
  int v6; // [esp+1Ch] [ebp-1Ch]
  int v7; // [esp+20h] [ebp-18h]
  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(v4);                  // 输入字符串的起始字符
  v6 = std::string::end(v2);                    // 输入字符串的结束符
  while ( __gnu_cxx::operator!=<char const*,std::string>((int)&v7, (int)&v6) )// 遍历整个字符串
  {
    v8 = *(_BYTE *)__gnu_cxx::__normal_iterator<char const*,std::string>::operator*(&v7);// v8=*v7,即遍历指针v7指向的值
    if ( (unsigned __int8)Sudu::set_number((int)a1, (Sudu *)(v10 / 9), v10 % 9, v8 - 48, v5) ^ 1 )// 第一个参数a1为v10矩阵
                                                // 第二个参数表示v10的行坐标
                                                // 第三个参数表示v10的列坐标
                                                // 第四个参数表示将输入的字符数字转为整型数字
                                                // 第五个参数表示?
      return 0;
    ++v10;
    __gnu_cxx::__normal_iterator<char const*,std::string>::operator++(&v7);
  }
  return 1;
}

 

查看(unsigned __int8)Sudu::set_number((int)a1, (Sudu *)(v10 / 9), v10 % 9, v8 - 48, v5)

signed int __userpurge Sudu::set_number@<eax>(int a1@<ecx>, Sudu *this, int a3, int a4, int a5)
{
  if ( !a4 )
    return 1;
  if ( (signed int)this < 0
    || (signed int)this > 8
    || a3 < 0
    || a3 > 8
    || *(_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;
}

 

返回主函数

    if ( Sudu::check((Sudu *)&v10) )            // 需要函数返回1
    {
      fctx.call_site = 1;
      std::operator<<<std::char_traits<char>>((std::ostream::sentry *)&std::cout, "success");
    }

查看Sudu::check((Sudu *)&v10)

bool __fastcall Sudu::check(Sudu *a1)
{
  Sudu *v2; // [esp+0h] [ebp-4h]

  v2 = a1;
  return (unsigned __int8)Sudu::check_block((int)a1)
      && (unsigned __int8)Sudu::check_col((int)v2)
      && (unsigned __int8)Sudu::check_row((int)v2);
}

打开(unsigned __int8)Sudu::check_block((int)a1)

signed int __fastcall Sudu::check_block(int a1)
{
  char v2[10]; // [esp+12h] [ebp-26h]
  int v3; // [esp+1Ch] [ebp-1Ch]
  int v4; // [esp+20h] [ebp-18h]
  int l; // [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[*(_DWORD *)(a1 + 4 * (v3 + 9 * v4))] = 0;// 将矩阵9个值,分别将v2数组的9个值赋值为0,如果3x3矩阵有重复数字,则必有v2[l]=1
    }
    for ( l = 1; l <= 9; ++l )
    {
      if ( v2[l] )                              // 要返回1,因此需要v2[1]=0。则每个3x3的矩阵中分布着1~9的数字,且没有重复。
                                                // 这是数独!!!
        return 0;
    }
  }
  return 1;
}

 

总结:实际上这就是一个解v10矩阵的数独游戏,直接网上在线解9x9矩阵的数独就行。(仔细看一下函数前面带的Sudu::就是数独(虽然正确是Shudu,哈哈哈~~~))

 

将填入的数字写出来,已有的数字用0表示,在代码检测中将跳过0.

 

4.get flag!

flag{340089102508406930016207058060875349709064820854392006093650071170023604602740590}

posted @ 2019-10-23 00:44  Hk_Mayfly  阅读(597)  评论(0编辑  收藏  举报