2017年全國大學生資訊保安競賽-填數遊戲

张伟文發表於2024-03-27

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}

相關文章