BUUCTF--[GUET-CTF2019]number_game
测试文件:https://lanzous.com/icfcxtg
代码分析
unsigned __int64 __fastcall main(__int64 a1, char **a2, char **a3) { _QWORD *v3; // ST08_8 __int64 v5; // [rsp+10h] [rbp-30h] __int16 v6; // [rsp+18h] [rbp-28h] __int64 v7; // [rsp+20h] [rbp-20h] __int16 v8; // [rsp+28h] [rbp-18h] char v9; // [rsp+2Ah] [rbp-16h] unsigned __int64 v10; // [rsp+38h] [rbp-8h] v10 = __readfsqword(0x28u); v5 = 0LL; v6 = 0; v7 = 0LL; v8 = 0; v9 = 0; __isoc99_scanf("%s", &v5, a3); if ( (unsigned int)sub_4006D6((const char *)&v5) ) { v3 = sub_400758((__int64)&v5, 0, 10); sub_400807((__int64)v3, (__int64)&v7); v9 = 0; sub_400881((char *)&v7); if ( (unsigned int)sub_400917() ) { puts("your are cxk!!"); } else { puts("TQL!"); printf("flag{", &v7); printf("%s", &v5); puts("}"); } } return __readfsqword(0x28u) ^ v10; }
sub_4006D6函数很好理解,用来判断输入字符数组长度是否为10,且每个字符是否为'0'~'4'
这道题最简单的方法应该是,直接爆破就行,反正10位数,0~4444444444,直接就出结果了,另一种就是老老实实分析了。
二叉树遍历
接着sub_400807和sub_400881函数,实际就是一个二叉树的先序遍历和中序遍历,对字符数组中的下标进行排序。
先看看sub_400807,我们来构建出数组下标的二叉树
_QWORD *__fastcall sub_400758(__int64 a1, int a2, int a3) { _QWORD *v4; // rax _QWORD *v5; // ST28_8 int v6; // [rsp+0h] [rbp-30h] char v7; // [rsp+1Fh] [rbp-11h] v6 = a3; v7 = *(_BYTE *)(a2 + a1); if ( v7 == 0x20 || v7 == 0xA || a2 >= a3 ) return 0LL; v4 = malloc(0x18uLL); v5 = v4; *(_BYTE *)v4 = v7; v4[1] = sub_400758(a1, 2 * a2 + 1, v6); v5[2] = sub_400758(a1, 2 * (a2 + 1), v6); return v5; }
写成可执行的C语言程序,我们可以看到下标的先序遍历顺序(注意:大于等于10的值实际就是NULL,上面代码也可以看到return 0)
#include <iostream> using namespace std; void func1(int a2, int a3) { cout << a2 << endl; if (a2 >= a3) return; func1(2 * a2 + 1, a3); func1(2 * (a2 + 1), a3); } int main() { func1(0,10); system("PAUSE"); return 0; }
先序遍历的结果即为:0137849256,构建出二叉树
因此中序遍历的结果为:7,3,8,1,9,4,0,5,2,6
实际上还有一个更简单的方式,得到中序遍历结果。我们直接输入0~9,它的值即代表下标。修改sub_4006D6判断结果,跳过函数,最后在sub_400881中下断点,我们一样能够得到期望的结果。
sub_400881函数实际就是,一个按照中序遍历顺序给byte_601062按顺序赋值。(byte_601062是不完整的,我们主要是给'#'处赋值,你排列出来就可以看出是一个5x5的数独)
解数独
sub_400917函数
__int64 sub_400917() { unsigned int v1; // [rsp+0h] [rbp-10h] signed int i; // [rsp+4h] [rbp-Ch] signed int j; // [rsp+8h] [rbp-8h] int k; // [rsp+Ch] [rbp-4h] v1 = 1; for ( i = 0; i <= 4; ++i ) { for ( j = 0; j <= 4; ++j ) { for ( k = j + 1; k <= 4; ++k ) { if ( *((_BYTE *)&unk_601060 + 5 * i + j) == *((_BYTE *)&unk_601060 + 5 * i + k) ) v1 = 0; if ( *((_BYTE *)&unk_601060 + 5 * j + i) == *((_BYTE *)&unk_601060 + 5 * k + i) ) v1 = 0; } } } return v1; }
这就是个检测横纵是否有相同元素的函数(数独的规则),我直接爆破出结果。
#include <iostream> #include <Windows.h> using namespace std; #define N 52 int func(int* s) { bool v1 = TRUE; for (int i = 0; i <= 4; ++i) { for (int j = 0; j <= 4; ++j) { for (int k = j + 1; k <= 4; ++k) { if (s[5 * i + j] == s[5 * i + k]) { v1 = FALSE; return v1; } if (s[5 * j + i] == s[5 * k + i]) { v1 = FALSE; return v1; } } } } return v1; } int main() { int s[] = { 0x31,0x34,0x23,0x32,0x33,0x33,0x30,0x23,0x31,0x23,0x30,0x23,0x32,0x33,0x23,0x23,0x33,0x23,0x23,0x30,0x34,0x32,0x23,0x23,0x31}; for (int i = 48; i <= N; ++i) { for (int j = 48; j <= N; ++j) { for (int k = 48; k <= N; ++k) { for (int a = 48; a <= N; ++a) { for (int b = 48; b <= N; ++b) { for (int c = 48; c <= N; ++c) { for (int d = 48; d <= N; ++d) { for (int e = 48; e <= N; ++e) { for (int f = 48; f <= N; ++f) { for (int g = 48; g <= N; ++g) { s[2] = i; s[7] = j; s[9] = k; s[11] = a; s[14] = b; s[15] = c; s[17] = d; s[18] = e; s[22] = f; s[23] = g; if (func(s)) { cout << s[2] << " " << s[7] << " " << s[9] << " " << s[11] << " " << s[14] << " " << s[15] << " " << s[17] << " " << s[18] << " " << s[22] << " " << s[23]; system("PAUSE"); return 0; } } } } } } } } } } } system("PAUSE"); return 0; }
得到了byte_601062的值,我们又知道赋值给它的顺序,因此只需要反向根据下标赋值回去就行。
脚本
# -*- coding:utf-8 -*- model = [7, 3, 8, 1, 9, 4, 0, 5, 2, 6] s = [48, 52, 50, 49, 52, 50, 49, 52, 51, 48] flag = [0] * 10 for i in range(10): flag[model[i]] = s[i] print ('flag{' + ''.join([chr(x) for x in flag]) + '}')
get flag!
flag{1134240024}