首页
社区
课程
招聘
[原创] 看雪 2023 KCTF 年度赛 第六题 至暗时刻
2023-9-15 03:50 9284

[原创] 看雪 2023 KCTF 年度赛 第六题 至暗时刻

2023-9-15 03:50
9284

main函数通过beginthreadex启动的sub_140001630(重命名为mainfunction)是真正的主逻辑所在。

IDA F5反编译,主要部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
struct string
{
  char *s;
  __declspec(align(16)) __int64 size;
  __int64 capacity;
};
 
// sub_140001630
__int64 mainfunction()
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
 
  std_string_assign(&userinput, byte_140006428);
  std_string_assign(&Format, aV);
  std_string_assign(&lpModuleName, aJzlOm);
  std_string_assign(&lpProcName, aM);
  std_string_assign(&str_kctf, aA);
  std_string_append(&lpProcName, aD_0);
  std_string_assign(&Block, "7PK:");
  p_Format = (char *)&Format;
  if ( Format.capacity >= 0x10ui64 )
    p_Format = Format.s;
  str_xor(p_Format, Format.size, "&x+^x", 5);   // Please enter your key:
  s = (char *)&Format;
  if ( Format.capacity >= 0x10ui64 )
    s = Format.s;
  printf(s);
  v3 = userinput.size;
  if ( userinput.size < 0x1F4ui64 )
  {
    v5 = 500 - userinput.size;
    if ( (unsigned __int64)(500 - userinput.size) > userinput.capacity - userinput.size )
    {
      sub_140002398(&userinput, 500 - userinput.size, v2, 500 - userinput.size);
    }
    else
    {
      userinput.size = 500i64;
      p_userinput = (char *)&userinput;
      if ( userinput.capacity >= 0x10ui64 )
        p_userinput = userinput.s;
      v7 = &p_userinput[v3];
      memset(v7, 0, 500 - v3);
      v7[v5] = 0;
    }
  }
  else
  {
    userinput.size = 500i64;
    v4 = (char *)&userinput;
    if ( userinput.capacity >= 0x10ui64 )
      v4 = userinput.s;
    v4[500] = 0;
  }
  v8 = (char *)&userinput;
  if ( userinput.capacity >= 0x10ui64 )
    v8 = userinput.s;
  scanf("%s", v8);
  v9 = (char *)&userinput;
  if ( userinput.capacity >= 0x10ui64 )
    v9 = userinput.s;
  v10 = &global_string1;
  std_string_append(&global_string1, v9);
  v12 = &global_string2;
  if ( global_string2.capacity >= 0x10ui64 )
    v12 = global_string2.s;
  v13 = global_string2.size;
  v14 = global_string1.size;
  if ( global_string2.size > (unsigned __int64)(global_string1.capacity - global_string1.size) )
  {
    std_string_realloc_append((void **)&global_string1.s, global_string2.size, v11, v12, global_string2.size);
  }
  else
  {
    global_string1.size += global_string2.size;
    v15 = (char *)&global_string1;
    if ( global_string1.capacity >= 0x10ui64 )
      v15 = global_string1.s;
    v16 = &v15[v14];
    memmove(v16, v12, global_string2.size);
    v16[v13] = 0;
  }
  p_strkctf = &str_kctf;
  if ( str_kctf.capacity >= 0x10ui64 )
    p_strkctf = str_kctf.s;
  str_xor(p_strkctf, str_kctf.size, "*s>0?", 5);// kctf
  if ( (unsigned __int64)(0x7FFFFFFFFFFFFFFFi64 - str_kctf.size) < global_string1.size )
    sub_140001284();
  v19 = &str_kctf;
  if ( str_kctf.capacity >= 0x10ui64 )
    v19 = str_kctf.s;
  v20 = &global_string1;
  if ( global_string1.capacity >= 0x10ui64 )
    v20 = global_string1.s;
  std_string_concat((struct string *)Src, str_kctf.size, v18, v19, str_kctf.size, v20, global_string1.size);
  if ( global_string1.capacity >= 0x10ui64 )
  {
    v21 = global_string1.s;
    if ( (unsigned __int64)(global_string1.capacity + 1) >= 0x1000 )
    {
      if ( (unsigned __int64)&global_string1.s[-*((_QWORD *)global_string1.s - 1) - 8] > 0x1F )
        invalid_parameter_noinfo_noreturn();
      v21 = (char *)*((_QWORD *)global_string1.s - 1);
    }
    j_j_free(v21);
  }
  global_string1.size = 0i64;
  global_string1.capacity = 15i64;
  LOBYTE(global_string1.s) = 0;
  memcpy(&global_string1, Src, sizeof(global_string1));
  CurrentProcess = GetCurrentProcess();
  v59 = 5500i64;
  LODWORD(Size) = 0x3000;
  maybe_virtualallocex((__int64)CurrentProcess, (__int64)&Source, 0i64, (__int64)&v59, Size);
  if ( global_string1.capacity >= 0x10ui64 )
    v10 = global_string1.s;
  maybe_writeprocessmemory((__int64)CurrentProcess, (__int64)Source, (__int64)v10, global_string1.size, (__int64)v60);
  p_lpModuleName = (char *)&lpModuleName;
  if ( lpModuleName.capacity >= 0x10ui64 )
    p_lpModuleName = lpModuleName.s;
  str_xor(p_lpModuleName, lpModuleName.size, "!?>d*", 5);// kernel32.dll
  p_lpProcName = (char *)&lpProcName;
  if ( lpProcName.capacity >= 0x10ui64 )
    p_lpProcName = lpProcName.s;
  str_xor(p_lpProcName, lpProcName.size, "?x)da", v24);// RtlFillMemory
  v26 = (char *)&lpProcName;
  if ( lpProcName.capacity >= 0x10ui64 )
    v26 = lpProcName.s;
  v27 = (char *)&lpModuleName;
  if ( lpModuleName.capacity >= 0x10ui64 )
    v27 = lpModuleName.s;
  ModuleHandleA = GetModuleHandleA(v27);
  RtlFillMemory_ = (__int64)GetProcAddress(ModuleHandleA, v26);
  if ( !RtlFillMemory_
    || (v36 = Source, (unsigned int)has_antidebug2_((__int64)CurrentProcess, (__int64)(Source + 0x1F4))) )
  {
    // COLLAPSED destructors
    // ...
  }
  sub_140002E4E((__int64)hThread, (__int64)(v36 + 0x1F4), 0i64, 0i64, 0i64, 0i64, 1, 0i64, 0i64, 0i64, 0i64);
  ResumeThread(hThread);
  sub_140002A8E((__int64)hThread, 0i64, 0i64, v43, Sizea);
  CloseHandle(hThread);
  *(_DWORD *)Str1 = 0;
  *(_DWORD *)Destination = 0;
  strncpy(Destination, Source, 3ui64);
  strncpy(Str1, Source + 67, 3ui64);
  if ( !strcmp(Str1, "110") )
  {
    v44 = &unk_140006590;
  }
  else
  {
    if ( strcmp(Str1, "120") )
      ExitProcess(0);
    v44 = &unk_140006598;
  }
  str_xor(Destination, 3, v44, 3);
  printf("\n%s", Destination);
  // COLLAPSED destructors
  // ...
  return 0i64;
}

定义出std::string的结构体和相关函数,然后排除掉析构函数的干扰,主体逻辑并不复杂(常量字符串大多用sub_1400013B0(str_xor)函数异或加密了,聊胜于无):获取输入(userinput),依次拼接"kctf"、global_string1、userinput、global_string2四部分为一个大字符串(global_string1和global_string2是两个全局string,分别在sub_140001000和sub_140001028初始化。其中global_string1长63字节,包含0-9和A-F,global_string2长120字节,只包含0-9)。

对于拼接后的大字符串,分别经过了sub_140002BBA(maybe_virtualallocex)和sub_140002DA9(maybe_writeprocessmemory)两个函数处理。这两个函数内部都是直接调用syscall指令执行系统调用,且调用号(eax)也是经过复杂的运算得出的。
(题目不支持win11的原因来源如此:windows的系统调用在用户态的接口封装在ntdll.dll中,而内核的系统调用号码和参数结构等并不属于公开的稳定api,随着系统版本更新也会有变化,这一点与linux完全不同)
此时最简单的方法是动态调试看这两个函数的具体行为是什么。在此之前,先留意程序是否存在反调试。sub_140001320和sub140001338是两个tlscallback,里面做了很明显的IsDebuggerPresent和NtQueryInformationProcess反调试检测,且如果检测到调试器,会调用sub_140001298对以"kctf"开头的内存页做修改。此外,在sub_140001450(has_antidebug2_,被sub_140001630(mainfunction)调用)函数也有反调试(CheckRemoteDebuggerPresent)。将这些地方全部patch掉。

开一台win10虚拟机调试(在win11上运行则程序不会有最后ok!或no!的输出),对于和sub_140002DA9(maybe_writeprocessmemory)函数,观察到第二个参数Source的值通常为0x1F0000,是一块分配出来的内存区域,且调用这个函数之后后会把拼接了常量和用户输入的大字符串写入开头(此时,偏移量为0的地方是"kctf",偏移量为4的地方是global_string1,然后紧跟的是userinput和global_string2)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
__int64 __fastcall has_antidebug2_(__int64 a1, __int64 a2)
{
  HANDLE CurrentProcess; // rax
  __int64 (**v4)(); // rax
  unsigned int v5; // edi
  unsigned __int8 *i; // rsi
  __int64 v7; // r9
  __int64 v9; // [rsp+20h] [rbp-60h]
  unsigned int *ThrdAddr; // [rsp+28h] [rbp-58h]
  int v11; // [rsp+30h] [rbp-50h]
  __int64 v12; // [rsp+38h] [rbp-48h]
  __int64 v13; // [rsp+40h] [rbp-40h]
  __int64 v14; // [rsp+48h] [rbp-38h]
  __int64 v15; // [rsp+50h] [rbp-30h]
  unsigned int v16[4]; // [rsp+60h] [rbp-20h] BYREF
  _Thrd_t v17; // [rsp+70h] [rbp-10h] BYREF
  BOOL pbDebuggerPresent; // [rsp+B8h] [rbp+38h] BYREF
 
  pbDebuggerPresent = 0;
  CurrentProcess = GetCurrentProcess();
  CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
  if ( pbDebuggerPresent )
  {
    v4 = (__int64 (**)())operator new(8ui64);
    *v4 = antidebug1_;
    *(_QWORD *)v16 = beginthreadex(0i64, 0, (_beginthreadex_proc_type)StartAddress, v4, 0, &v16[2]);
    if ( !*(_QWORD *)v16 )
    {
      v16[2] = 0;
      std::_Throw_Cpp_error(6);
LABEL_15:
      TerminateThread(hObject, 0);
      CloseHandle(hObject);
      return 1i64;
    }
    if ( !v16[2] )
    {
      std::_Throw_Cpp_error(1);
      __debugbreak();
    }
    if ( v16[2] == Thrd_id() )
    {
      std::_Throw_Cpp_error(5);
      __debugbreak();
    }
    v17 = *(_Thrd_t *)v16;
    if ( Thrd_join(&v17, 0i64) )
      std::_Throw_Cpp_error(2);
  }
  v15 = 0i64;
  v14 = 0i64;
  v13 = 0i64;
  v12 = 0i64;
  v11 = 1;
  ThrdAddr = 0i64;
  if ( !(unsigned int)sub_140003529() )
  {
    v5 = 0;
    for ( i = (unsigned __int8 *)&unk_140008050;
          !(unsigned int)sub_140002E4E(
                           (__int64)hObject,
                           RtlFillMemory_,
                           a2 + v5,
                           1i64,
                           *i,
                           (__int64)ThrdAddr,
                           v11,
                           v12,
                           v13,
                           v14,
                           v15);
          ++i )
    {
      if ( ++v5 >= 0x92B )
      {
        ResumeThread(hObject);
        sub_140002A8E((__int64)hObject, 0i64, 0i64, v7, v9);
        CloseHandle(hObject);
        return 0i64;
      }
    }
    goto LABEL_15;
  }
  return 1i64;
}

接下来调用了sub_140001450(has_antidebug2_)函数,这里除了反调试,还有一处调用i = (unsigned __int8 *)&unk_140008050; !(unsigned int)sub_140002E4E((__int64)hObject, RtlFillMemory_, a2 + v5, 1i64, *i, (__int64)ThrdAddr, v11, v12, v13, v14, v15); 。其中a2是Source+0x1F4(Source是0x1F0000,即写入了大字符串的内存段)。unk_140008050是.data段的地址,在调用sub_140001450之后观察0x1F0000段的内存,发现unk_140008050处的0x92B个字节被写入了0x1F01F4的位置。
观察发现unk_140008050开始的字节很像x64指令,IDA里按c发现确实如此。随后,mainfunction以0x1F01F4为第二个参数调用了sub_140002E4E,可以猜测sub_140002E4E是启动新的进程,从0x1F01F4开始执行指令。在此之后mainfunction只剩下判断和输出。


直接分析,发现这些指令开头是对后面的自修改,所以继续动态调试到这部分执行完毕(到达0x1F0217)再dump出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
__int64 sub_1F0217()
{
  __int64 (__fastcall *v0)(__int64, _QWORD); // rbx
  __int64 (__fastcall *v1)(__int64, int *); // r13
  void (__fastcall *v2)(__int64); // r15
  __int64 (*v3)(void); // r12
  __int64 v4; // rdi
  bool v5; // si
  __int64 v6; // rax
  __int64 v7; // r14
  int v9; // eax
  __int64 (__fastcall *v10)(__int64, int *); // rbx
  __int64 (__fastcall *v11)(__int64, _QWORD); // r13
  char *v12; // rbx
  int v13; // eax
  char *v14; // rdx
  char *v15; // rbx
  int *v16; // [rsp+48h] [rbp-2B0h] BYREF
  int v17; // [rsp+58h] [rbp-2A0h]
  __int64 v18; // [rsp+60h] [rbp-298h]
  int v19; // [rsp+68h] [rbp-290h]
  int v20; // [rsp+80h] [rbp-278h] BYREF
  int v21; // [rsp+88h] [rbp-270h]
  __int64 v22; // [rsp+300h] [rbp+8h]
  __int64 v23; // [rsp+308h] [rbp+10h]
  __int64 (__fastcall *v24)(__int64, char *, int **, _QWORD); // [rsp+310h] [rbp+18h]
 
  v0 = (__int64 (__fastcall *)(__int64, _QWORD))sub_1F0563(-124919994);
  v23 = sub_1F0563(-49588825);
  v24 = (__int64 (__fastcall *)(__int64, char *, int **, _QWORD))sub_1F0563(37938943);
  v1 = (__int64 (__fastcall *)(__int64, int *))sub_1F0563(1060402837);
  v22 = sub_1F0563(-1813961927);
  v2 = (void (__fastcall *)(__int64))sub_1F0563(480663025);
  v3 = (__int64 (*)(void))sub_1F0563(55981281);
  v4 = 0i64;
  v20 = 568;
  v5 = 0;
  v6 = v0(2i64, 0i64);
  v7 = v6;
  if ( v6 == -1 )
    return 0xFFFFFFFFi64;
  v9 = v1(v6, &v20);
  v10 = (__int64 (__fastcall *)(__int64, int *))v22;
  v11 = (__int64 (__fastcall *)(__int64, _QWORD))v23;
  while ( v9 )
  {
    if ( v21 == (unsigned int)v3() )            // getcurrentprocessid
    {
      v4 = v11(0x2000000i64, 0i64);             // openprocess
      if ( v4 )
      {
        v12 = 0i64;
        while ( 1 )
        {
          do
          {
            if ( !v24(v4, v12, &v16, '0') )
            {
              v10 = (__int64 (__fastcall *)(__int64, int *))v22;
              v11 = (__int64 (__fastcall *)(__int64, _QWORD))v23;
              goto LABEL_20;
            }
            v12 = (char *)v16 + v18;
          }
          while ( v19 != 4096 || v17 != 64 );
          v13 = v3();
          v14 = (char *)v16;
          if ( v21 == v13 )
            v5 = sub_1F062F(*v16);
          if ( v5 )
            break;
          strcpy(v14, "mj)");
          qmemcpy(v14 + 67, "120", 3);
        }
        v15 = v14 + 4;
        if ( sub_1F0AA3(v14 + 4) )
        {
          strcpy(v15 - 4, "io ");
          memset(v15 + 63, '1', 2);
        }
        else
        {
          strcpy(v15 - 4, "mj)");
          qmemcpy(v15 + 63, "12", 2);
        }
        v15[65] = '0';
        break;
      }
    }
LABEL_20:
    v9 = v10(v7, &v20);
  }
  v2(v7);
  return ((__int64 (__fastcall *)(__int64))v2)(v4);
}

看到了mainfunction中检测的"120"常量字符串和分支。动态调试发现v14为0x1F0000,所以这个函数前半部分只是找到此块内存区域,sub_1F0AA3是真正的输入校验逻辑。(其参数v14+4,恰好是global_string1+userinput+global_string2三部分的拼接)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
char __fastcall sub_1F0AA3(char *a1)
{
  int v2; // ebx
  int v3; // ebx
  int v4; // edi
 
  v2 = 0;
  while ( sub_1F093B(a1, v2) && sub_1F09A7(a1, v2) )
  {
    if ( ++v2 >= 9 )
    {
      v3 = 0;
LABEL_6:
      v4 = 0;
      while ( sub_1F0A13((__int64)a1, v3, v4) )
      {
        v4 += 3;
        if ( v4 >= 9 )
        {
          v3 += 3;
          if ( v3 < 9 )
            goto LABEL_6;
          return 1;
        }
      }
      return 0;
    }
  }
  return 0;
}

sub_1F0AA3里,先不管调用的三个函数,这里外层的while循环是0-9的遍历,内层的while循环还有3*3的分块,看起来很像数独。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
char __fastcall sub_1F093B(char *a1, int a2)
{
  int v2; // ebx
  unsigned int v5; // eax
  int v7[8]; // [rsp+20h] [rbp-38h] BYREF
  int v8; // [rsp+40h] [rbp-18h]
 
  memset(v7, 0, sizeof(v7));
  v8 = 0;
  v2 = 0;
  while ( 1 )
  {
    v5 = sub_1F063B(a1, a2, v2) - 1;
    if ( v5 > 8 || v7[v5] )
      break;
    ++v2;
    v7[v5] = 1;
    if ( v2 >= 9 )
      return 1;
  }
  return 0;
}
 
char __fastcall sub_1F09A7(char *a1, int a2)
{
  int v2; // ebx
  unsigned int v5; // eax
  int v7[8]; // [rsp+20h] [rbp-38h] BYREF
  int v8; // [rsp+40h] [rbp-18h]
 
  memset(v7, 0, sizeof(v7));
  v8 = 0;
  v2 = 0;
  while ( 1 )
  {
    v5 = sub_1F063B(a1, v2, a2) - 1;
    if ( v5 > 8 || v7[v5] )
      break;
    ++v2;
    v7[v5] = 1;
    if ( v2 >= 9 )
      return 1;
  }
  return 0;
}
 
char __fastcall sub_1F0A13(char *a1, int a2, int a3)
{
  int v3; // edi
  int i; // ebx
  signed int v8; // eax
  __int128 v10[2]; // [rsp+20h] [rbp-38h] BYREF
  int v11; // [rsp+40h] [rbp-18h]
 
  memset(v10, 0, sizeof(v10));
  v11 = 0;
  v3 = 0;
  while ( 2 )
  {
    for ( i = 0; i < 3; ++i )
    {
      v8 = sub_1F063B(a1, v3 + a2, i + a3) - 1;
      if ( (unsigned int)v8 > 8 || *((_DWORD *)v10 + v8) )
        return 0;
      *((_DWORD *)v10 + v8) = 1;
    }
    if ( ++v3 < 3 )
      continue;
    break;
  }
  return 1;
}

调用的三个函数,逻辑高度类似,都是循环检查sub_1F063B的返回值范围在1-9且无重复,所以它们依次是数独的行检测、列检测、九宫检测。

共同调用到的函数是sub_1F063B,从调用方式看,它的后两个参数应该是坐标,返回值是坐标位置的值。所以下一步需要搞清楚拼接后的大字符串的编码逻辑,以及如何映射到数独的格子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
__int64 __fastcall sub_1F063B(char *a1, int a2, int a3)
{
  char *a1_; // rbx
  int a3_; // r15d
  __int64 (__fastcall *v5)(char *); // rax
  int should_be_363; // eax
  int v7; // r11d
  int should_be_243; // ebp
  __int64 v9; // rsi
  int should_be_81; // r10d
  __int64 v11; // rdi
  char *p; // r14
  char *tmpbuf; // rdx
  char v14; // cl
  int n; // r9d
  int v16; // r12d
  int base; // r8d
  int c; // ecx
  char v19; // cl
  int nn; // r12d
  int v21; // r13d
  int col; // r12d
  unsigned int value; // r8d
  int row; // r13d
  int v25; // esi
  char *v26; // r15
  int v27; // edi
  char *v28; // r11
  char v29; // dl
  char *v30; // rcx
  char v31; // al
  int another_n; // ebx
  int v33; // edx
  char v34; // dl
  char v36[4]; // [rsp+20h] [rbp-78h] BYREF
  int v37; // [rsp+24h] [rbp-74h]
  int should_be_81_; // [rsp+2Ch] [rbp-6Ch]
  __int64 should_be_243_; // [rsp+30h] [rbp-68h]
  __int64 v40; // [rsp+38h] [rbp-60h]
  __int64 v41; // [rsp+40h] [rbp-58h]
  char v45[4]; // [rsp+B8h] [rbp+20h] BYREF
 
  a1_ = a1;
  a3_ = a3;
  v5 = (__int64 (__fastcall *)(char *))sub_1F0563(0xD3A22F6A);
  should_be_363 = v5(a1_);                      // ntdll.strlen
  v7 = 0;
  v37 = 0;
  should_be_243 = should_be_363 - 120;
  v9 = should_be_363 - 120;
  should_be_243_ = (unsigned int)(should_be_363 - 120);
  should_be_81 = (should_be_363 - 120) / 3;
  v41 = v9;
  should_be_81_ = should_be_81;
  if ( should_be_363 - 120 <= 0 )
    return 0i64;
  v36[3] = 0;
  v11 = -2i64 - (_QWORD)a1_;
  p = a1_ + 2;
  v40 = -2i64 - (_QWORD)a1_;
  while ( 1 )
  {
    tmpbuf = v36;
    v14 = *(p - 2);
    v36[1] = *(p - 1);
    n = 0;
    v36[2] = *p;
    v16 = 1;
    v36[0] = v14;
    base = 16;
    while ( v14 == ' ' || (unsigned __int8)(v14 - 9) <= 4u )
      v14 = *++tmpbuf;
    if ( ((v14 - '+') & 0xFD) == 0 )
    {
      if ( v14 == '-' )
        v16 = -1;
      goto LABEL_19;
    }
    while ( 1 )
    {
      v19 = *tmpbuf;
      if ( !*tmpbuf )
        break;
      if ( (unsigned __int8)(v19 - '0') > 9u )
      {
        if ( (unsigned __int8)(v19 - 'A') > 25u )
        {
          if ( (unsigned __int8)(v19 - 'a') > 25u )
            break;
          c = v19 - 0x57;
          base = 0;
        }
        else
        {
          c = v19 - '7';
        }
      }
      else
      {
        c = v19 - '0';
      }
      if ( c >= base )
        break;
      n = c + base * n;
LABEL_19:
      ++tmpbuf;
    }
    nn = n * v16;
    v21 = nn / 10;
    col = nn % 10;
    value = v21 / 10;
    row = v21 % 10;
    if ( v7 > 20 )
    {
      v25 = 0;
      v26 = a1_ + 0xF3;
      v27 = 1;
      v45[2] = 0;
      v28 = a1_ + 0xF3;
      do
      {
        v29 = *v28;
        v30 = v45;
        v31 = v28[1];
        another_n = 0;
        v45[0] = *v28;
        v45[1] = v31;
        while ( v29 == ' ' || (unsigned __int8)(v29 - 9) <= 4u )
          v29 = *++v30;
        if ( ((v29 - '+') & 0xFD) == 0 )
        {
          v27 = 1;
          if ( v29 == '-' )
            v27 = -1;
          ++v30;
        }
        while ( 1 )
        {
          v34 = *v30;
          if ( !*v30 )
            break;
          if ( (unsigned __int8)(v34 - 48) > 9u )
          {
            if ( (unsigned __int8)(v34 - 65) > 0x19u )
            {
              if ( (unsigned __int8)(v34 - 97) > 0x19u )
                break;
              v33 = v34 - 87;
            }
            else
            {
              v33 = v34 - 55;
            }
          }
          else
          {
            v33 = v34 - 48;
          }
          if ( v33 >= 10 )
            break;
          another_n = v33 + 10 * another_n;
          ++v30;
        }
        if ( row + col + 8 * row == another_n * v27 )
          break;
        v28 += 2;
        ++v25;
      }
      while ( v28 - v26 < 120 );
      v7 = v37;
      should_be_81 = should_be_81_;
      should_be_243 = should_be_243_;
      if ( v37 - 21 != v25 )
        return value + 9;
      a1_ = a1;
      v11 = v40;
      v9 = v41;
      a3_ = a3;
    }
    if ( should_be_81 > 81 )
      return value + 10;
    if ( should_be_81 < 81 )
      return value + 12;
    if ( should_be_243 != 3 * should_be_81 )
      return value + 11;
    if ( a2 == row && a3_ == col )
      return value;
    p += 3;
    v37 = ++v7;
    if ( (__int64)&p[v11] >= v9 )
      return 0i64;
  }
}

从调用情况看,sub_1F063B不应返回大于9的值。而sub_1F063B最开始就先获取了长度,但长度的检测却是在循环中完成的(should_be_81和should_be_243)。
这里的参数a1是0x1F0004,指向的内容是global_string1+userinput+global_string2三部分的拼接。
回顾一下,global_string1的长度是63,包含数字和字母,疑似hexencoded,global_string2的长度是120,只包含数字。此处检查strlen(a1)等于363,减去两个常量字符串的63和120,合计用户输入的userinput长度应为180。此时global_string2位于a1的63+180=243=0xF3的偏移处。

大循环的上半部分从a1偏移量0处开始遍历,每3个字符按十六进制解析为3位十进制整数,从百位到个位分别是数值和横纵坐标。另外,当循环次数达到21后,会开启一个新的循环,从a1偏移量0xF3的位置开始,每2个字符按十进制解析为整数,检查是否为横纵坐标所指示的位置。
结合global_string1长度63=21*3,userinput长度180=60*3,且21+60=81=9*9,所以global_string1是数独的21个初始值,userinput是60个待填值。a1的0xF3偏移开始是120个字符的global_string2,而120=60*2,所以global_string2指示的是60个待填值的排列顺序。


提取数独,找一个求解器解出答案,反向编码,即可得到正确的输入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
buf = [[0]*9 for _ in range(9)]
 
def parse1(s):
    r = []
    assert len(s) % 3 == 0
    for i in range(0, len(s), 3):
        tmp = int(s[i:i+3], 16)
        col = tmp % 10
        row = tmp // 10 % 10
        value = tmp // 100
        r.append((row, col, value))
    return r
 
 
def parse2(s):
    r = []
    assert len(s) % 2 == 0
    for i in range(0, len(s), 2):
        tmp = int(s[i:i+2], 10)
        # col = tmp % 10
        # row = tmp // 10    # 最开始这里写错导致多花了两个小时单步调试找bug。。。
        col = tmp % 9
        row = tmp // 9
        r.append((row, col))
    return r
 
 
seq1 = parse1("3201382652D139C0E22132DF1BC2212EA0991650A229B36436823D0B13D51E6")
seq2 = parse2("677116575313142309154604431859253431473963507533496829080645035455771774602058076430276921790210013736267644383505517280")
 
print(len(seq1), len(seq2))    # 21 60
 
for c in seq1:
    print(c)
    row, col, value = c
    buf[row][col] = value
 
 
s = ""
for line in buf:
    print(line)
    s += "".join(chr(c+48) for c in line)
    s += "\n"
 
print(s)
 
'''
800000000
003600000
070090200
050007000
000045700
000100030
001000068
008500010
090000400
'''
 
 
solution = '''
812753649
943682175
675491283
154237896
369845721
287169534
521974368
438526917
796318452
'''
 
ss = solution.replace("\n", "")
assert len(ss) == 81
 
 
ans = ""
for row, col in seq2:
    vv = ss[row*9+col]
    v = ord(vv)-48
    tmp = v*100+row*10+col
    t = f"{tmp:03X}"
    print(tmp, t)
    ans += t
 
print(len(ans))    # 180
print(ans)    # 11230A2CD3C31CA32E0D707D38E0743531F80F726C1D133B3A914E2F034B1D63BB17F34428E2A31B038C25E0FA2BF2301053752062AA16E20A2FC1971730E90823D01A724B0CA19B0652811541480B80943AE27E13122C30C120

最终的正确答案:

1
11230A2CD3C31CA32E0D707D38E0743531F80F726C1D133B3A914E2F034B1D63BB17F34428E2A31B038C25E0FA2BF2301053752062AA16E20A2FC1971730E90823D01A724B0CA19B0652811541480B80943AE27E13122C30C120

(p.s. 看到有人发现了多解,按照上面的分析似乎只有大小写可能导致多解(毕竟,限制了输入长度,其实就已经排除了大部分意料之外的多解)?但是尝试把答案全部小写却不能通过程序检查。所以可能还有其他的问题(数独多解?)?不深究了)
(p.s. 赛程过半,今年的KCTF可比往年卷多了。。。)


[竞赛]2024 KCTF 大赛征题截止日期08月10日!

最后于 2023-9-15 03:55 被mb_mgodlfyn编辑 ,原因:
收藏
免费 3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回