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}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步