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}

posted @ 2020-05-09 17:40  Hk_Mayfly  阅读(958)  评论(2编辑  收藏  举报