2017年全国大学生信息安全竞赛--填数游戏
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('')
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}