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}