測試文件: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}