MoeCTF新生赛逆向A_game
step1: 先探探这个exe的底
拉到die里头。发现是个64位程序。
step2:拉到IDA64里面反编译瞅瞅源码
按F5,反编译的源码如下。
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
size_t v4; // rbx
char Str[96]; // [rsp+20h] [rbp-80h]
int v7; // [rsp+80h] [rbp-20h]
int k; // [rsp+90h] [rbp-10h]
int j; // [rsp+94h] [rbp-Ch]
int i; // [rsp+98h] [rbp-8h]
int v11; // [rsp+9Ch] [rbp-4h]
_main(*(_QWORD *)&argc, argv, envp);
puts("<--- moectf2021 --->");
puts(" [A_game] Welcome to moectf2021.");
puts("Let's play a game!");
puts("Now input your answer, and if you are right, I will give you flag");
printf("input : ");
memset(Str, 0, sizeof(Str));
v7 = 0;
scanf("%s", Str);
if ( strlen(Str) != 49 )
{
puts("It's not enough.");
system("pause");
exit(0);
}
v11 = 0;
for ( i = 0; i <= 8; ++i )
{
for ( j = 0; j <= 8; ++j )
{
if ( !box[9 * i + j] )
{
v3 = v11++;
box[9 * i + j] = Str[v3] - 48;
}
}
}
check1();
check2();
check3();
puts("Congratulations!!!!");
puts("Enjoy the beauty of reverse and sudoku!");
printf("And here is your flag : moectf{");
for ( k = 0; ; ++k )
{
v4 = k;
if ( v4 >= strlen(Str) )
break;
putchar((char)(Str[k] ^ magic[k]));
}
putchar(125);
return 0;
}
阅读完整个程序之后大概明白了str就是和flag直接相关的那个东西。而且他的长度是49.从如下源码可以看出:
if ( strlen(Str) != 49 ) { puts("It's not enough."); system("pause"); exit(0); }
然后需要重点关注下面几个程序:直觉告诉我这三个程序绝对很有用。双击查看他们的具体代码。但是看完之后感觉有点懵。这三个程序到底想干什么?先跳过这步接着往下面看,看看下面有什么有用的信息。
//check1(); __int64 check1() { __int64 result; // rax signed int j; // [rsp+24h] [rbp-Ch] signed int i; // [rsp+28h] [rbp-8h] int k; // [rsp+2Ch] [rbp-4h] for ( i = 0; i <= 8; ++i ) { for ( j = 1; j <= 9; ++j ) { for ( k = 0; ; ++k ) { result = (unsigned int)(char)box[9 * i + k]; if ( j == (_DWORD)result ) break; if ( k == 8 ) { printf("Wrong!!!Try again!!!"); system("pause"); exit(0); } } } } return result; } // check2(); __int64 check2() { __int64 result; // rax signed int j; // [rsp+24h] [rbp-Ch] signed int i; // [rsp+28h] [rbp-8h] int k; // [rsp+2Ch] [rbp-4h] for ( i = 0; i <= 8; ++i ) { for ( j = 1; j <= 9; ++j ) { for ( k = 0; ; ++k ) { result = (unsigned int)(char)box[9 * k + i]; if ( j == (_DWORD)result ) break; if ( k == 8 ) { printf("Wrong!!!Try again!!!"); system("pause"); exit(0); } } } } return result; } // check3(); __int64 check3() { __int64 result; // rax signed int j; // [rsp+2Ch] [rbp-14h] signed int i; // [rsp+30h] [rbp-10h] signed int k; // [rsp+34h] [rbp-Ch] int v4; // [rsp+38h] [rbp-8h] int v5; // [rsp+3Ch] [rbp-4h] for ( i = 0; i <= 8; i += 3 ) { for ( j = 0; j <= 8; j += 3 ) { for ( k = 1; k <= 9; ++k ) { v5 = 0; v4 = 0; while ( 1 ) { result = (unsigned int)(char)box[9 * (i + v5) + j + v4]; if ( k == (_DWORD)result ) break; if ( v5 == 2 && v4 == 2 ) { printf("Wrong!!!Try again!!!"); system("pause"); exit(0); } if ( ++v4 == 3 ) { ++v5; v4 = 0; } } } } } return result; }
关注完重要的函数,接下来看得看看重要的变量,经过筛选发现下面这几个变量有点意思:
box[96],magic[64],str[49].
看下面这段代码我们知道:str[]数组的49个数字每一个都要先减去48然后再嵌入box[]。而且嵌入box的位置好像还有要求。
for ( i = 0; i <= 8; ++i ) { for ( j = 0; j <= 8; ++j ) { if ( !box[9 * i + j] ) { v3 = v11++; box[9 * i + j] = Str[v3] - 48; } } }
接着看magic。这句话说明magic要和str一一进行异或。
putchar((char)(Str[k] ^ magic[k]));
双击magic和box,看看这两个数组到底存着什么数据。看着有点乱,我们复制下来拉到python里面用正则表达式把数字提取出来。
最后就是下面这段程序:看printf输出应该是在输出flag,前面有半段,后面还有个putchar(125),这个在c语言里面就是输出 } 的。这就说明这段程序绝对是通过异或运算得到最终的flag。
而magic这个数组我们有。
printf("And here is your flag : moectf{"); for ( k = 0; ; ++k ) { v4 = k; if ( v4 >= strlen(Str) ) break; putchar((char)(Str[k] ^ magic[k])); } putchar(125);
step3:柳暗花明又一村。
到这里整理整理思路。
按照程序来看:他是将我们输入的str数组嵌入到box里面。然后再用三个check函数去检查box是否符合题意,最后用str和magic异或得到flag的ascll码。
现在magic已知,box也部分已知,看来找到str是关键。那下面我们就去找str,而找str好像只能通过三个check函数来找(这个思路给我埋下了一个小坑,让我在试图反向推测str时耗费了好大的时间,真的花了好长时间,其实就怪自己眼瞎没看到那个最重要的关键词)。
时间过去了好多,那三个check我也没完全搞懂,总感觉缺少条件,正准备跳过这道题做下一道。然后突然看到了这句话:Enjoy the beauty of reverse and sudoku!
sudoku!sudoku!suduku!这不是数独嘛!然后突然想到那个box[96]里面有零,有非0数字,这个box该不会是棋盘吧!然后三个check函数就对应9*9数独的三个条件。再一看box,果然是个棋盘状。
然后瞬间就懂了,str[]中的数组就是这个数独对应0位置的数字+48,因此只需要解出这个数独就可以,压根不需要去看那三个check。拿出纸笔,多年数独功底发挥了作用。
step4: 异或得到flag。
然后写个程序把新填上的数字提取出来+48拼凑成str[]就可以.代码如下。
box = [0, 0, 5, 0, 0, 4, 3, 6, 0,
0, 0, 0, 0, 5, 0, 0, 2, 4,
0, 4, 9, 6, 7, 0, 0, 0, 0,
1, 0, 6, 0, 2, 0, 0, 3, 0,
9, 0, 0, 7, 0, 0, 1, 0, 8,
0, 3, 0, 0, 0, 5, 0, 9, 0,
2, 0, 0, 5, 0, 7, 0, 0, 9,
7, 0, 4, 0, 0, 0, 8, 0, 0,
0, 9, 0, 0, 4, 0, 0, 0, 6,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
aim = [8, 2, 5, 9, 1, 4, 3, 6, 7,
6, 7, 1, 3, 5, 8, 9, 2, 4,
3, 4, 9, 6, 7, 2, 5, 8, 1,
1, 8, 6, 4, 2, 9, 7, 3, 5,
9, 5, 2, 7, 6, 3, 1, 4, 8,
4, 3, 7, 1, 8, 5, 6, 9, 2,
2, 6, 8, 5, 3, 7, 4, 1, 9,
7, 1, 4, 2, 9, 6, 8, 5, 3,
5, 9, 3, 8, 4, 1, 2, 7, 6
]
#提取数字拼凑str
str = []
for i in range(0, 9):
for j in range(0, 9):
if (box[9 * i + j] == 0):
str.append(aim[9 * i + j] + 48)
print('moectf{', end='')
magic = [0x6B, 0x2, 0x66, 0x70, 0x44, 0x69, 0x7E, 0x6E, 0x43, 0x4A, 0x78,
0x4A, 0x6D, 0x60, 0x56, 0, 0x51, 0x59, 0x50, 0x43, 0x50, 0x51,
0x6D, 0x74, 0x2, 0x55, 0x50, 0x52, 0x6E, 0x6F, 0x79, 0x40, 0x5D,
0x4B, 0x1E, 0x19, 0x1C, 0x74, 3, 0x54, 7, 0x4C, 0x52, 0x6A, 0x60,
0x50, 0x58, 0x40, 0x58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
'''异或求flag'''
for k in range(0, 1000):
v4 = k
if v4 >= len(str):
break
print(chr(str[k] ^ magic[k]), end='')
print(chr(125))
大功告成。
sum:不要陷入死思维,尽可能的挖掘有用信息,很可能一个很小的单词就是打开僵局的突破点。
还有就是坚持,不要轻易放弃。