运行

D:\TRY\holiday\recurrent\2_puzzle\copy>puzzle
Input: aaaa
Wrong!

查壳 && 脱壳

查壳发现是UPX壳, 但是使用脱壳工具会报错, 原因是修改了UPX壳的特征, 比如节区名, 我们需要改回来才能使用工具脱壳, 或者直接手脱也行

image.png

分析

通过跟进可以找到关键函数

void important_function()
{
  __int64 v0; // xmm0_8
  __int64 *_input; // rdx
  __int64 j; // rax
  __int64 index; // rcx
  __int64 i; // rbx
  __int64 v5; // rdi
  __int64 len; // rsi
  unsigned __int8 one_char; // si
  char v8; // [rsp+28h] [rbp-1E8h]
  __int128 v9; // [rsp+28h] [rbp-1E8h]
  __int64 v10; // [rsp+30h] [rbp-1E0h]
  __int64 v11; // [rsp+38h] [rbp-1D8h]
  __int64 v12; // [rsp+50h] [rbp-1C0h]
  char v13[9]; // [rsp+67h] [rbp-1A9h] BYREF
  char v14[9]; // [rsp+70h] [rbp-1A0h] BYREF
  char v15; // [rsp+79h] [rbp-197h] BYREF
  __int64 v16; // [rsp+7Ah] [rbp-196h]
  char v17[9]; // [rsp+82h] [rbp-18Eh] BYREF
  char v18[9]; // [rsp+8Bh] [rbp-185h] BYREF
  char v19[9]; // [rsp+94h] [rbp-17Ch] BYREF
  char v20[9]; // [rsp+9Dh] [rbp-173h] BYREF
  char v21; // [rsp+A6h] [rbp-16Ah] BYREF
  __int64 v22; // [rsp+A7h] [rbp-169h]
  char v23; // [rsp+AFh] [rbp-161h] BYREF
  __int64 v24; // [rsp+B0h] [rbp-160h]
  __int128 v25; // [rsp+B8h] [rbp-158h]
  __int64 v26[2]; // [rsp+C8h] [rbp-148h] BYREF
  __int64 v27[4]; // [rsp+D8h] [rbp-138h] BYREF
  __int64 *inputt; // [rsp+F8h] [rbp-118h]
  __int64 v29[2]; // [rsp+100h] [rbp-110h] BYREF
  __int64 v30[2]; // [rsp+110h] [rbp-100h] BYREF
  __int64 input[2]; // [rsp+120h] [rbp-F0h] BYREF
  __int64 p_puzzle; // [rsp+130h] [rbp-E0h] BYREF
  __int64 box[26]; // [rsp+138h] [rbp-D8h]

  while ( (unsigned __int64)&v18[5] <= *(_QWORD *)(*(_QWORD *)NtCurrentTeb()->NtTib.ArbitraryUserPointer + 16LL) )
    runtime_morestack_noctxt();
  v25 = 0LL;
  inputt = (__int64 *)runtime_newobject((__int64 *)&RTYPE_string);
  p_puzzle = 0LL;
  *(double *)&v0 = ((double (*)(void))loc_5E698F)();
  v23 = 4;
  v24 = 0x800000900000000LL;
  box[0] = 9LL;
  box[1] = 9LL;
  p_puzzle = (__int64)&v23;
  v21 = 0;
  v22 = 0x600000002000100LL;
  box[3] = 9LL;
  box[4] = 9LL;
  box[2] = (__int64)&v21;
  v20[0] = 0;
  *(_QWORD *)&v20[1] = 0x3000000000502LL;
  box[6] = 9LL;
  box[7] = 9LL;
  box[5] = (__int64)v20;
  v19[0] = 0;
  *(_QWORD *)&v19[1] = 0x8000001000000LL;
  box[9] = 9LL;
  box[10] = 9LL;
  box[8] = (__int64)v19;
  v18[0] = 0;
  *(_QWORD *)&v18[1] = 0x10600070005LL;
  box[12] = 9LL;
  box[13] = 9LL;
  box[11] = (__int64)v18;
  v17[0] = 2;
  *(_QWORD *)&v17[1] = 150994944LL;
  box[15] = 9LL;
  box[16] = 9LL;
  box[14] = (__int64)v17;
  v15 = 0;
  v16 = 0x700020008000000LL;
  box[18] = 9LL;
  box[19] = 9LL;
  box[17] = (__int64)&v15;
  v14[0] = 0;
  *(_QWORD *)&v14[1] = 394240LL;
  box[21] = 9LL;
  box[22] = 9LL;
  box[20] = (__int64)v14;
  *(_QWORD *)&v13[1] = 0x80000000000LL;
  *(_WORD *)v13 = 1543;
  box[24] = 9LL;
  box[25] = 9LL;
  box[23] = (__int64)v13;
  fmt_Fprintf((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_708528, (__int64)"Input: ", 7LL, v0, 0LL, 0LL);
  input[0] = (__int64)&RTYPE__ptr_string;
  input[1] = (__int64)inputt;
  fmt_Fscanf(
    (__int64)&go_itab__ptr_os_File_comma__ptr_io_Reader,
    qword_708520,
    (__int64)"%s",
    2LL,
    (__int64)input,
    1LL,
    1LL);
  if ( !v12 )
  {
    _input = inputt;
    j = 0LL;
    index = 0LL;
    while ( j < 9 )                             // 循环9次, 对应有9行
    {
      for ( i = 0LL; i < 9; ++i )               // 循环9次, 对应有9列
      {
        v5 = box[3 * j - 1];                    // 排列的顺序是: <数独一行的指针>, <9>, <9>
                                                //                    -1          0    1
                                                //                     2          3    4
                                                //                     ...
                                                //              所以v5的值为指向数独一行数据的指针
        if ( box[3 * j] <= (unsigned __int64)i )
          ((void (__noreturn *)(void))runtime_panicIndex)();
        if ( !*(_BYTE *)(i + v5) )              // 如果该字节为0的话
        {
          len = _input[1];                      // go字符串格式是input[0]为字符串指针, input[1]为字符串长度
          if ( len <= index )
            goto LABEL_13;
          if ( len <= (unsigned __int64)index )
            ((void (__noreturn *)(void))runtime_panicIndex)();
          one_char = *(_BYTE *)(index + *_input);// 取出输入的一个字符
          if ( one_char < '0' || one_char > '9' )// 检查该字符是否符合要求
            goto LABEL_13;
          *(_BYTE *)(v5 + i) = one_char - '0';  // 将字符转换成对应的BYTE型, 并写入数独中
          ++index;
        }
      }
      ++j;
    }
    if ( _input[1] == index && (check((__int64)&p_puzzle, 9uLL), v8) )
    {
      *(_QWORD *)&v9 = runtime_stringtoslicebyte((__int64)v27, *inputt, inputt[1]);
      *((_QWORD *)&v9 + 1) = crypto_md5_Sum(v9, v10, v11);
      v25 = v9;
      *(_OWORD *)v26 = v9;
      v30[0] = runtime_convT2Enoptr((__int64)&RTYPE__16_uint8, (__int64)v26);
      v30[1] = v9;
      fmt_Fprintf(
        (__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer,
        qword_708528,
        (__int64)"Your flag is flag{%x}async stack too l",
        21LL,
        (__int64)v30,
        1LL,
        1LL);
    }
    else
    {
LABEL_13:
      v29[0] = (__int64)&RTYPE_string;
      v29[1] = (__int64)&off_684828;
      fmt_Fprintln((__int64)&go_itab__ptr_os_File_comma__ptr_io_Writer, qword_708528, (__int64)v29, 1LL, 1LL);
    }
  }
}

在清楚了后面的两个循环检查的逻辑的情况下, 以及题目puzzle, 我们可以判断应该是数独.

而前面的一大块赋值语句就是初始化数独.

在使用工具脱完壳后, 程序不能直接调试, 我们可以使用setIP的方法进行局部的调试(请教了闻铃哥)

我们一直交叉引用到程序的入口点, 并此处打上一个硬件断点

image.png

同时记录数独初始化的起始地址0x638C9C

image.png

现在开始调试, 并修改RIP的值为0x638C9C跳转到数独初始化的位置.

image.png

F8, 运行到初始化完成的位置我们查看当前程序的内存, 得到了我们需要的数据

image.png

这里有点小坑就是最终输入填入的数据顺序, 和我们在内存中dump下来的数据在行上是相反的. 上面的数组是指针数组(中间隔了两个9), 而其中指针指向的地址是从大到小的. 后面进行填入操作的时候, 也是从地址最高的一行开始填入的.

//这是我们从内存中dump下来的样子
7 6 0 0 0 0 8 0 0 
0 0 4 6 0 0 0 0 0 
0 0 0 0 8 0 2 0 7 
2 0 0 0 9 0 0 0 0 
0 5 0 7 0 6 1 0 0 
0 0 0 0 1 0 0 8 0 
0 2 5 0 0 0 0 3 0 
0 0 1 0 2 0 0 0 6 
4 0 0 0 0 9 0 0 8 //但是我们的填入数据是从这一行开始的

使用在线工具解数独, 然后将所有0的答案填入即可.

得到flag

D:\TRY\holiday\recurrent\2_puzzle\copy>puzzle
Input: 76135283549798674164925733849217386455934161872359295314
Your flag is flag{23c3cb3aedbbfdd009d1bf52e530676a}

flag{23c3cb3aedbbfdd009d1bf52e530676a}

关键判断中判断了哪些东西

判断了是否越界

91 ~ 92

image

判断了单个字节的内容是否为0

95 ~ 96

image