首页
社区
课程
招聘
[原创] 看雪 2023 KCTF 年度赛 第三题 秘密计划
2023-9-7 07:05 9416

[原创] 看雪 2023 KCTF 年度赛 第三题 秘密计划

2023-9-7 07:05
9416

为什么下载附件会被chrome认为危险拦截掉?!(目前三道题的附件都这样……)

Windows GUI程序,一个输入框,输入长度是32位。

IDA打开,41347个函数,看一眼字符串至少用了 soui4cryptoppunicorn等库,而且还是C++编写的,各种间接跳转虚函数调用等。虽然没有混淆,但本身程序的复杂性使得如何排除干扰找到真正的校验逻辑变成了一件并不轻松的事,特别是如何避免花费大量时间逆向结果是一个库函数。


这个程序编译C++时保留了RTTI信息,因此IDA把所有的虚表重命名了,方便了后面看伪代码对虚表赋值的地方猜测函数的作用。这些大部分都是CrytoPP的函数。

先学习soui4框架再从WinMain函数正向深入显然不适合短期解题。
注意到随着exe还有一个code.dat文件,程序中一定会打开它,然而直接搜索"code.dat"字符串找不到结果。换一个方向,在IDA中查找CreateFileW的交叉引用,只有十个左右的条目,顺次看下来,在sub_40DA90的伪代码中看到了L"code.dat",确认了这个函数出题人写的而不是库函数。

程序中大量使用了std::string,在IDA中定义出它的结构体(大小是6个DWORD共24字节,后两个DWORD是size和capacity,第一个DWORD是指向实际char *的指针,或者长度小于16时内联到结构体的前16字节),会让F5的伪代码观感提升不少,更重要的是局部变量的交叉引用能更准确。

粗略看一遍sub_40DA90,直观上是读入了code.dat文件,前8字节和后面的字节分别存成两个string,各自做了一些运算(跑了ClassInformer插件后,一些函数点进去后,虚表赋值的地方能看到CryptoPP的类名和函数名,至少有AES、HexDecode、CRC32等)。
合理猜测code.dat的前8字节是hexencode后的crc32,而后面的字节是加密过的不知道什么东西,中间的逻辑可能是解密然后crc校验,通过后又做了一些不知道什么的事。


虽然找到了sub_40DA90作为突破口,但是这里并没有获取输入的地方。
向上找,它的调用者sub_40CF80有很多SendMessageW;再上一级的调用者是sub_40CF60,是通过QueueUserWorkItem在其他线程中调用的sub_40CF80。而sub_40CF60本身在虚表中,通过静态分析很难再找到它的调用者了。

开始动态调试,但调试器附加后程序总是闪退,基本断定程序有反调试。简单试了x32dbg ScallyHide的几个默认选项发现无效,而一时间又很难静态分析从海量函数中找到反调试所在,初次陷入僵局。


在不断尝试中发现如果在sub_40CF60调用QueueUserWorkItem之前下断点,程序不会闪退。在此处查看调用栈,找到了它的调用者是sub_4061D0。这个函数有两处MessageBox分别是L"验证成功"验证失败,还有1146、1147、1148、1149、1150等几个常数的分支判断,所以这很可能是一个消息处理函数,几个常数对应几种不同的消息。

其中1149直接导向“验证失败”,到达“验证成功”则需要1147以及一个额外的判断。
回看sub_40CF80,里面有SendMessage 1147、1149、1150等,所以接下来期望找到1147的条件。

sub_40CF80的逻辑十分复杂,再加上调用了大量CrytoPP的密码学函数,要进一步分析还是依赖动态调试。而且,最难受的是,到现在仍然没有找到获取输入的地方。但无论如何,反调试问题仍然要先解决掉。

注意到程序在sub_40CF60调用QueueUserWorkItem之后才会闪退,所以可以把这里patch为直接调用sub_40CF80函数,不会影响逻辑,且调试器附加后程序不会闪退了。

依靠不断调试和猜,一点点梳理程序的逻辑。


sub_4061D0开始:(重名了一些函数)

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
// sub_4061D0
int __userpurge handle_window_message@<eax>(
        int a1@<ecx>,
        int a2@<ebp>,
        int a3@<edi>,
        int a4,
        _OWORD *input_wchars,
        int a6)
{
  char *p_input_sha256; // edx
  void *s; // ecx
  char *v9; // edx
  char *v10; // edx
  int v11; // eax
  unsigned __int8 v12; // al
  char v13; // al
  int v15; // [esp-54h] [ebp-60h]
  unsigned __int8 v17; // [esp-45h] [ebp-51h]
  struct string input_sha256; // [esp-44h] [ebp-50h] BYREF
  struct string v19; // [esp-2Ch] [ebp-38h] BYREF
  int *v20; // [esp-10h] [ebp-1Ch]
  struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList; // [esp-Ch] [ebp-18h]
  void *v22; // [esp-8h] [ebp-14h]
  int v23; // [esp-4h] [ebp-10h]
  int v24[2]; // [esp+0h] [ebp-Ch] BYREF
  int v25; // [esp+8h] [ebp-4h] BYREF
  int retaddr; // [esp+Ch] [ebp+0h]
 
  v24[0] = a2;
  v24[1] = retaddr;
  v23 = -1;
  v22 = &loc_C0D4D5;
  ExceptionList = NtCurrentTeb()->NtTib.ExceptionList;
  v20 = &v25;
  if ( a4 != 1146 )
  {
    if ( a4 == 1147 )
    {
      if ( *(_BYTE *)(a1 + 652) )
        MessageBoxW(*(HWND *)(a1 + 28), L"验证成功!", L"提示", 0);
      else
        (*(void (__stdcall **)(int, int, _DWORD, _DWORD))(*(_DWORD *)a1 + 176))(a1, 1149, 0, 0);
      return 0;
    }
    if ( a4 == 1149 )
    {
      MessageBoxW(*(HWND *)(a1 + 28), L"验证失败!", L"提示", 0);
      return 0;
    }
    if ( a4 != 1148 )
    {
      if ( a4 == 1150 && input_wchars )
      {
        *(_OWORD *)(a1 + 628) = *input_wchars;
        *(_OWORD *)(a1 + 644) = input_wchars[1];
      }
      return 0;
    }
    v11 = *(_DWORD *)(a1 + 616);
    if ( !v11 )
      return 0;
    v12 = (*(int (__stdcall **)(int, int, int))(*(_DWORD *)(v11 + 4) + 300))(v11 + 4, v15, a3);// sub_A510A7
    v17 = v12;
    if ( v12 < 0x64u )
    {
      if ( v12 <= 0x50u )
      {
        if ( !byte_E43098 )
          byte_E43098 = 1;
        goto LABEL_37;
      }
      v13 = byte_E43098;
    }
    else
    {
      v13 = byte_E43098;
      if ( byte_E43098 == 1 )
      {
        v13 = 0;
        byte_E43098 = 0;
      }
    }
    if ( !v13 )
    {
      (*(void (__cdecl **)(int, _DWORD))(*(_DWORD *)(*(_DWORD *)(a1 + 616) + 4) + 296))(
        *(_DWORD *)(a1 + 616) + 4,
        (unsigned __int8)(v17 - 5));
      byte_E43098 = 0;
      return 0;
    }
LABEL_37:
    (*(void (__cdecl **)(int, _DWORD))(*(_DWORD *)(*(_DWORD *)(a1 + 616) + 4) + 296))(
      *(_DWORD *)(a1 + 616) + 4,
      (unsigned __int8)(v17 + 5));
    byte_E43098 = 1;
    return 0;
  }
  if ( input_wchars )
  {
    if ( !*(_WORD *)input_wchars )              // a5 is input wchat_t *
    {
      j_j__free(input_wchars);
      return 0;
    }
    sha256sum(&input_sha256, input_wchars, (int)v24, a6);// sub_406850 sha256
    v23 = 0;
    p_input_sha256 = (char *)&input_sha256;
    if ( input_sha256.capacity >= 0x10u )
      p_input_sha256 = input_sha256.s;
    sub_406D20(&v19, p_input_sha256);           // v20 may be rsa oaep encrypt result
    LOBYTE(v23) = 1;
    if ( v19.size )
    {
      s = &v19;
      if ( v19.capacity >= 0x10u )
        s = v19.s;
      (*(void (__thiscall **)(int, void *, int, _DWORD))(*(_DWORD *)(a1 + 680) + 8))(
        a1 + 680,
        s,
        v19.size,
        *(_DWORD *)(a1 + 28));                  // call sub_40CF10 simulation_function_8
      (*(void (__thiscall **)(int))(*(_DWORD *)(a1 + 680) + 12))(a1 + 680);// call sub_40CF60 simulation_function_c
    }
    else
    {
      (*(void (__stdcall **)(int, int, _DWORD, _DWORD))(*(_DWORD *)a1 + 176))(a1, 1149, 0, 0);
    }
    if ( v19.capacity >= 0x10u )
    {
      v9 = v19.s;
      if ( (unsigned int)(v19.capacity + 1) >= 0x1000 )
      {
        v9 = (char *)*((_DWORD *)v19.s - 1);
        if ( (unsigned int)(v19.s - v9 - 4) > 0x1F )
          goto LABEL_42;
      }
      free_(v9);
    }
    v19.size = 0;
    v19.capacity = 15;
    LOBYTE(v19.s) = 0;
    if ( input_sha256.capacity < 0x10u )
      return 0;
    v10 = input_sha256.s;
    if ( (unsigned int)(input_sha256.capacity + 1) < 0x1000
      || (v10 = (char *)*((_DWORD *)input_sha256.s - 1), (unsigned int)(input_sha256.s - v10 - 4) <= 0x1F) )
    {
      free_(v10);
      return 0;
    }
LABEL_42:
    _invalid_parameter_noinfo_noreturn();
  }
  return 0;
}

消息为1146时参数是wchat_t *的输入字符串,然后调用了sub_406850,调试发现输出是转换为char *后的sha256值。继续调用sub_406D20,它的输出长度高达4096字节,在里面看到了RSA OAEP相关的虚表赋值,猜测这里做了一个RSA OAEP的加密。然后,依次调用了sub_40CF10sub_40CF60函数。

消息为1149时直接提示验证失败
消息为1147时还会额外检查 a1 + 652 不为0才会提示验证成功。但仔细观察,消息为 1150 时输入的offset=24的字节会覆盖这个值。


sub_40CF10 只是一个赋值

sub_40CF60 比较重要,通过 QueueUserWorkItem 调用了 sub_40CF80 函数:

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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// sub_40CF80
// local variable allocation has failed, the output may be wrong!
DWORD __userpurge Function@<eax>(int a1@<ebx>, struct simulation_context *lpThreadParameter)
{
  struct string *begin; // esi
  bool v3; // cf
  _BYTE *s; // eax
  struct string *v5; // edi
  char *v6; // edx
  char *v7; // edx
  char *v8; // edx
  struct simulation_context *v9; // esi
  char *p_load_code_dat_return_string; // ecx
  unsigned __int8 *p_userinput_sha256; // edi
  int v12; // esi
  char *v13; // edx
  _BYTE *v14; // eax
  struct string *v15; // eax
  char *v16; // ecx
  unsigned int v17; // edi
  char *p_inputstr_sha256; // esi
  char *v19; // esi
  char *v20; // edx
  int v21; // esi
  void *v22; // eax
  LRESULT (__stdcall *sendmessagew)(HWND, UINT, WPARAM, LPARAM); // edi
  char *v24; // edx
  HWND hwnd; // eax
  char *v26; // edx
  unsigned __int64 v28; // [esp-4h] [ebp-1F4h]
  int v29; // [esp-4h] [ebp-1F4h]
  unsigned __int64 v30; // [esp+4h] [ebp-1ECh]
  struct string v31; // [esp+Ch] [ebp-1E4h] BYREF
  int v32; // [esp+24h] [ebp-1CCh]
  int v33; // [esp+28h] [ebp-1C8h]
  int v34; // [esp+2Ch] [ebp-1C4h]
  int v35; // [esp+30h] [ebp-1C0h]
  char v36; // [esp+34h] [ebp-1BCh]
  __int128 v37; // [esp+38h] [ebp-1B8h]
  int v38; // [esp+48h] [ebp-1A8h]
  int v39; // [esp+4Ch] [ebp-1A4h]
  char v40; // [esp+50h] [ebp-1A0h]
  int v41[18]; // [esp+54h] [ebp-19Ch] BYREF
  char v42; // [esp+9Ch] [ebp-154h]
  int *v43; // [esp+A8h] [ebp-148h]
  void (__cdecl *v44)(); // [esp+ACh] [ebp-144h]
  int (__cdecl *v45)(int, int); // [esp+B0h] [ebp-140h]
  char *p_load_code_dat_return_string_; // [esp+B4h] [ebp-13Ch]
  int size; // [esp+B8h] [ebp-138h]
  unsigned __int8 *p_userinput_sha256_; // [esp+BCh] [ebp-134h]
  int v49; // [esp+C0h] [ebp-130h]
  int v50; // [esp+C4h] [ebp-12Ch] BYREF
  int v51; // [esp+C8h] [ebp-128h]
  LPVOID lpAddress; // [esp+CCh] [ebp-124h]
  struct simulation_context *v53; // [esp+D0h] [ebp-120h]
  char iftocheck; // [esp+D7h] [ebp-119h] BYREF
  void *Block; // [esp+D8h] [ebp-118h] BYREF
  int v56[39]; // [esp+DCh] [ebp-114h] BYREF
  struct string userinput_sha256; // [esp+178h] [ebp-78h] BYREF
  struct string inputstr_sha256; // [esp+190h] [ebp-60h] BYREF
  struct string load_code_dat_return_string; // [esp+1A8h] [ebp-48h] BYREF
  WPARAM wParam[2]; // [esp+1C0h] [ebp-30h] OVERLAPPED BYREF
  struct string tmp; // [esp+1C8h] [ebp-28h] BYREF
  int v62; // [esp+1ECh] [ebp-4h]
 
  v53 = lpThreadParameter;
  if ( !lpThreadParameter )
    return 0;
  (*(void (__thiscall **)(struct simulation_context *))lpThreadParameter->vtbl)(lpThreadParameter);// sub_40D6A0 simulation_function_0
  memset(&load_code_dat_return_string, 0, 20);
  load_code_dat_return_string.capacity = 15;
  LOBYTE(load_code_dat_return_string.s) = 0;
  v62 = 0;
  begin = lpThreadParameter->uuids.begin;
  lpAddress = v53->uuids.end;
  if ( begin != lpAddress )
  {
    while ( 1 )
    {
      v3 = begin->capacity < 0x10u;
      s = begin;
      iftocheck = 0;
      if ( !v3 )
        s = begin->s;
      v5 = (struct string *)load_code_dat(&tmp, &iftocheck, s);  // sub_40DA90
      if ( &load_code_dat_return_string != v5 )
      {
        if ( load_code_dat_return_string.capacity >= 0x10u )
        {
          v6 = load_code_dat_return_string.s;
          if ( (unsigned int)(load_code_dat_return_string.capacity + 1) >= 0x1000 )
          {
            v6 = (char *)*((_DWORD *)load_code_dat_return_string.s - 1);
            if ( (unsigned int)(load_code_dat_return_string.s - v6 - 4) > 0x1F )
              goto LABEL_76;
          }
          free_(v6);
        }
        load_code_dat_return_string.size = 0;
        load_code_dat_return_string.capacity = 15;
        LOBYTE(load_code_dat_return_string.s) = 0;
        load_code_dat_return_string = *v5;
        v5->size = 0;
        v5->capacity = 15;
        LOBYTE(v5->s) = 0;
      }
      if ( tmp.capacity >= 0x10u )
      {
        v7 = tmp.s;
        if ( (unsigned int)(tmp.capacity + 1) >= 4096 )
        {
          v7 = (char *)*((_DWORD *)tmp.s - 1);
          if ( (unsigned int)(tmp.s - v7 - 4) > 31 )
            goto LABEL_76;
        }
        free_(v7);
      }
      if ( iftocheck )
        break;
      if ( ++begin == lpAddress )
        goto LABEL_17;
    }
    if ( load_code_dat_return_string.size )
    {
      v9 = v53;
      (*(void (__thiscall **)(struct simulation_context *, struct string *))(v53->vtbl + 4))(v53, &userinput_sha256);// sub_40E670 simulation_function_4
                                                // may be rsa decrypt
      LOBYTE(v62) = 1;
      if ( !userinput_sha256.size )             // the output v57 is sha256 of user input
      {
        SendMessageW((HWND)v53->hwnd, 1149u, 0, 0);
LABEL_68:
        if ( userinput_sha256.capacity >= 0x10u )
        {
          v26 = userinput_sha256.s;
          if ( (unsigned int)(userinput_sha256.capacity + 1) >= 0x1000 )
          {
            v26 = (char *)*((_DWORD *)userinput_sha256.s - 1);
            if ( (unsigned int)(userinput_sha256.s - v26 - 4) > 0x1F )
              goto LABEL_75;
          }
          free_(v26);
        }
        userinput_sha256.size = 0;
        userinput_sha256.capacity = 15;
        LOBYTE(userinput_sha256.s) = 0;
        goto LABEL_18;
      }
      p_load_code_dat_return_string = (char *)&load_code_dat_return_string;
      p_userinput_sha256 = (unsigned __int8 *)&userinput_sha256;
      if ( load_code_dat_return_string.capacity >= 0x10u )
        p_load_code_dat_return_string = load_code_dat_return_string.s;
      p_load_code_dat_return_string_ = p_load_code_dat_return_string;
      if ( userinput_sha256.capacity >= 0x10u )
        p_userinput_sha256 = (unsigned __int8 *)userinput_sha256.s;
      p_userinput_sha256_ = p_userinput_sha256;
      size = load_code_dat_return_string.size;
      iftocheck = 0;
      if ( p_userinput_sha256 && p_load_code_dat_return_string && load_code_dat_return_string.size )
      {
        lpAddress = VirtualAlloc(0, (SIZE_T)&loc_A00000, 0x3000u, 0x40u);
        if ( lpAddress )
        {
          Block = 0;
          if ( uc_open(1, 0, &Block) )          // sub_44E640  UC_ARCH_ARM = 1
          {
            sendmessagew = SendMessageW;
          }
          else
          {
            LODWORD(v28) = 17;
            uc_ctl((int *)Block, 0x44000007u, v28, v30);
            memset(&inputstr_sha256, 0, 20);
            inputstr_sha256.capacity = 15;
            LOBYTE(inputstr_sha256.s) = 0;
            v12 = 0;
            v51 = 0;
            while ( 1 )
            {
              memset(v56, 0, sizeof(v56));
              v50 = p_userinput_sha256[v12];
              memset(&v31, 0, sizeof(v31));
              v32 = 0;
              v33 = 0;
              v37 = 0i64;
              v34 = 0;
              v35 = 0;
              v36 = 0;
              v38 = 0;
              v39 = 15;
              LOBYTE(v37) = 0;
              LOBYTE(v62) = 5;
              v40 = -1;
              sub_412030(v41, v29);
              v42 = 0;
              LOBYTE(v62) = 7;
              memset(&tmp, 0, sizeof(tmp));
              std_string_assign__(&tmp, "%02x", 4u);
              LOBYTE(v62) = 8;
              sub_412190(&v31, &tmp);
              LOBYTE(v62) = 7;
              if ( tmp.capacity >= 0x10u )
              {
                v13 = tmp.s;
                if ( (unsigned int)(tmp.capacity + 1) >= 0x1000 )
                {
                  v13 = (char *)*((_DWORD *)tmp.s - 1);
                  if ( (unsigned int)(tmp.s - v13 - 4) > 0x1F )
                    goto LABEL_76;
                }
                free_(v13);
              }
              tmp.size = 0;
              tmp.capacity = 15;
              LOBYTE(tmp.s) = 0;
              LOBYTE(v62) = 9;
              v43 = &v50;
              v44 = nullsub_1;
              v45 = sub_414C90;
              v14 = sub_414130(&v31);
              sub_410F70(v14);
              LOBYTE(v62) = 11;
              sub_40F4A0(&v31);
              v15 = sub_4111C0(v56, &tmp);      // hexencode input_sha256, result is only 2 bytes
              LOBYTE(v62) = 12;
              v16 = (char *)v15;
              if ( v15->capacity >= 0x10u )
                v16 = v15->s;
              v17 = v15->size;
              if ( v17 > inputstr_sha256.capacity - inputstr_sha256.size )
              {
                LOBYTE(v49) = 0;
                sub_40A700((void **)&inputstr_sha256.s, v17, v49, v16, v17);// /
              }
              else
              {
                p_inputstr_sha256 = (char *)&inputstr_sha256;
                if ( inputstr_sha256.capacity >= 0x10u )
                  p_inputstr_sha256 = inputstr_sha256.s;
                v19 = &p_inputstr_sha256[inputstr_sha256.size];
                inputstr_sha256.size += v17;
                memmove(v19, v16, v17);
                v19[v17] = 0;
                v12 = v51;
              }
              LOBYTE(v62) = 11;
              if ( tmp.capacity >= 0x10u )
              {
                v20 = tmp.s;
                if ( (unsigned int)(tmp.capacity + 1) >= 0x1000 )
                {
                  v20 = (char *)*((_DWORD *)tmp.s - 1);
                  if ( (unsigned int)(tmp.s - v20 - 4) > 0x1F )
                    goto LABEL_76;
                }
                free_(v20);
              }
              LOBYTE(v62) = 2;
              sub_40F4A0(v56);
              v51 = ++v12;
              if ( v12 >= 32 )
                break;
              p_userinput_sha256 = p_userinput_sha256_;
            }
            uc_mem_map((int)Block, 0, 0, 0xA00000, 7, (int)lpAddress);
            v21 = size;
            uc_mem_write((int)Block, 0x43000i64, (int)p_load_code_dat_return_string_, size); // sub_44E460
            v22 = &inputstr_sha256;
            if ( inputstr_sha256.capacity >= 0x10u )
              v22 = inputstr_sha256.s;
            uc_mem_write((int)Block, 0x4033i64, (int)v22, inputstr_sha256.size); // sub_44E460
            if ( uc_emu_start(a1, v17, (int)Block, 0x43000, 0, v21 + 0x43000, 0, 0i64, 0) ) // sub_44DA50
            {
              v9 = v53;
              sendmessagew = SendMessageW;
            }
            else
            {
              *(_OWORD *)wParam = 0i64;
              *(_OWORD *)&tmp.field_8 = 0i64;
              uc_mem_read((int)Block, 0x14390u, 0, (int)wParam); // sub_44E1F0
              v9 = v53;
              sendmessagew = SendMessageW;
              iftocheck = 1;
              SendMessageW((HWND)v53->hwnd, 1150u, (WPARAM)wParam, 0);// first send, need offset 24 of wParam is 1
            }
            sub_44E300((int)Block, 0i64, (unsigned int)&loc_A00000);
            sub_44D470((char *)Block);
            if ( inputstr_sha256.capacity >= 0x10u )
            {
              v24 = inputstr_sha256.s;
              if ( (unsigned int)(inputstr_sha256.capacity + 1) >= 0x1000 )
              {
                v24 = (char *)*((_DWORD *)inputstr_sha256.s - 1);
                if ( (unsigned int)(inputstr_sha256.s - v24 - 4) > 0x1F )
LABEL_76:
                  _invalid_parameter_noinfo_noreturn();
              }
              free_(v24);
            }
          }
          VirtualFree(lpAddress, 0, 0x8000u);
        }
        else
        {
          sendmessagew = SendMessageW;
        }
        hwnd = (HWND)v9->hwnd;
        if ( iftocheck )
        {
          sendmessagew(hwnd, 1147u, 0, 0);      // final check
          goto LABEL_68;
        }
      }
      else
      {
        hwnd = (HWND)v53->hwnd;
        sendmessagew = SendMessageW;
      }
      sendmessagew(hwnd, 1149u, 0, 0);
      goto LABEL_68;
    }
  }
LABEL_17:
  SendMessageW((HWND)v53->hwnd, 1149u, 0, 0);
LABEL_18:
  if ( load_code_dat_return_string.capacity >= 0x10u )
  {
    v8 = load_code_dat_return_string.s;
    if ( (unsigned int)(load_code_dat_return_string.capacity + 1) < 0x1000
      || (v8 = (char *)*((_DWORD *)load_code_dat_return_string.s - 1),
          (unsigned int)(load_code_dat_return_string.s - v8 - 4) <= 0x1F) )
    {
      free_(v8);
      return 0;
    }
LABEL_75:
    _invalid_parameter_noinfo_noreturn();
  }
  return 0;
}

第一步调用 sub_40D6A0 初始化了很多 uuid 常量。
然后循环遍历,作为参数调用 sub_40DA90 函数。如果成功返回了值,下一步用之前RSA OEAP加密的结果调用 sub_40E670,后者完成了解密,调试看到结果就是程序输入值的sha256。

继续跟踪数据流,如果要进入SendMessageW 1147,至少需要调用 sub_44DA50 的地方返回 0,然后还会根据 sub_44E1F0 的调用结果发送一个 1150 消息。回顾 sub_4061D0,1150 消息的offset=24的字节需要不为0才能在进入 1147 时进入“验证成功”的分支。


所以,决定验证通过的关键是 sub_44DA50sub_44E1F0 两个函数。上面贴的伪代码已经把它们标注出来了,分别是 unicorn 的 uc_emu_start 和 uc_mem_read。
而初始化unicorn内存时,0x4033地址用的是程序输入值的sha256;0x43000地址用的是 sub_40DA90 函数的返回值,动态调试可以发现它来自于解密的 code.dat,且它的 crc32 恰好是code.dat的前8字节hexdecode。

往上看,sub_44E640 uc_open 时用的是 UC_ARCH_ARM,所以 unicorn 0x43000 的值是 arm 指令。


实际上,最初完全没有想到过这些函数竟然属于unicorn,因为里面不像CryptoPP一样有虚表暴露了类名,而且很难找到可读的字符串。

(当然,从全局字符串可以知道程序用了unicorn,如果对它的api很敏感或许可以猜出这些函数;但是逆到这里早就没有印象了)

不过,我们可以借助其他工具的力量,例如 BinaryAI (之前在论坛也看到了宣传文:链接

文件的分析结果:链接

soui、cryptopp 都识别出来了(虽然版本都是错的),但是关键的 unicorn 却丢失了(IDA直接查字符串表就能看到unicorn,这个号称AI加持的平台却漏掉了)

(去平台首页-自定义比对-组件-比对内容,输入github链接,探测出平台对 unicorn 是有收录的,而soui只收录了 soui2soui3 却唯独没有soui4。之前另一篇宣传文章提到的“数万代码仓库、亿级函数特征”、“准确定位开源项目具体版本”看起来还有很大提升空间)

但是,交互式分析 页面还是带来了一些小惊喜。

例如 sub_44E640sub_44E1F0sub_44E460 等关键函数都被正确识别出来了:uc_openuc_mem_readuc_mem_write,连带它上下附近的函数都是 unicorn 相关的函数。(那么,平台首屏的组件列表为什么没展示呢)

(但如果筛选"匹配效果"为"命中",打上绿色Hit标记的函数反而都是soui2的一些奇奇怪怪的函数)
(而且,交互上不能按照十六进制地址搜索(浏览器地址栏的链接倒是能直接跳转到任意函数,但必须是十进制才行)、也不能按g或ctrl+g快速跳转到想要的函数,还是不太方便)


其他识别库函数的思路:

IDA的F.L.I.R.T函数签名:优点是十分精准,缺点是十分精准导致必须找到原始的.lib或.a等静态库才行。如果作者是从源代码自己编译的库,就完全无法发挥作用。

IDA Lumina:...

自己找到库的源代码编译一个二进制,然后用BinDiffdiaphora之类的工具找出匹配的二进制函数迁移符号:存在的问题题是自己编译库比较麻烦,还要确保库的版本和编译选项尽可能与程序接近才能保证最好的效果;而且,BinDiff和diaphora的识别率和准确率都不是很高。(这里再提一下BinaryAI,之前还针对这个场景发布过一个基于大模型的函数语义匹配功能(链接),官方评测效果非常好,不知道实际应用起来怎么样)


回到题目,确定是unicorn模拟执行code.dat解密出来的arm代码后,将其dump出来拖进IDA逆向分析:(基地址重定位到0x43000)

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
void __noreturn sub_4363C()
{
  int v0; // r1
  int v1; // r0
  const char *v2; // r2
  int v3; // r5
  int v4; // r3
  int v5; // r4
 
  v0 = 0xC;
  while ( 1 )
  {
    v1 = 0x4033;
    v2 = "4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8";
    v3 = 0;
    --v0;
    do
    {
      v4 = *(_DWORD *)v1;
      v5 = *(_DWORD *)v2;
      if ( ++v3 >= 0x10 )
      {
        if ( "4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8" == "6749dae311865d64db83d5ae75bac3c9e36b3"
                                                                                   "aa6f24caba655d9682f7f071023" )
        {
          MEMORY[0x14390] = 1;
          MEMORY[0x143A8] = 1;
        }
LABEL_9:
        JUMPOUT(0x43754);
      }
      v1 += 4;
      v2 += 4;
    }
    while ( v4 == v5 );
    if ( !v0 )
      goto LABEL_9;
  }
}

能看到雏形:MEMORY[0x143A8]的赋值就是后面SendMessageW 1150的参数的offset=24的位置,v1的0x4033地址对应程序输入的flag的sha256,而do-while循环很明显是对比两个字符串。

但是,从伪代码看似乎永远无法到达对MEMORY[0x143A8]赋值的地方,这是IDA的反编译错误。

ROM:00043000             ; Processor       : ARM
ROM:00043000             ; ARM architecture: metaarm
ROM:00043000             ; Target assembler: Generic assembler for ARM
ROM:00043000             ; Byte sex        : Little endian
ROM:00043000
ROM:00043000             ; ===========================================================================
ROM:00043000
ROM:00043000             ; Segment type: Pure code
ROM:00043000                             AREA ROM, CODE, READWRITE, ALIGN=0
ROM:00043000                             ; ORG 0x43000
ROM:00043000                             CODE32
ROM:00043000 8D 01 00 EA                 B               sub_4363C
ROM:00043000             ; ---------------------------------------------------------------------------
ROM:00043004 65 30 62 63+aE0bc614e4fd035 DCB "e0bc614e4fd035a488619799853b075143deea596c477b8dc077e309c0fe42e9"
ROM:00043004 36 31 34 65+                                        ; DATA XREF: sub_4363C+4↓o
ROM:00043004 34 66 64 30+                DCB 0
ROM:00043045 00                          DCB    0
ROM:00043046 36 62 38 36+a6b86b273ff34fc DCB "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b"
ROM:00043046 62 32 37 33+                DCB 0
ROM:00043087 00                          DCB    0
ROM:00043088 64 38 62 64+aD8bdf9a0cb27a1 DCB "d8bdf9a0cb27a193a1127de2924b6e5a9e4c2d3b3fe42e935e160c011f3df1fc"
ROM:00043088 66 39 61 30+                                        ; DATA XREF: sub_4363C+10↓o
ROM:00043088 63 62 32 37+                DCB 0
ROM:000430C9 00                          DCB    0
ROM:000430CA 64 34 37 33+aD4735e3a265e16 DCB "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"
ROM:000430CA 35 65 33 61+                DCB 0
ROM:0004310B 00                          DCB    0
ROM:0004310C 36 37 34 39+a6749dae311865d DCB "6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023"
ROM:0004310C 64 61 65 33+                                        ; DATA XREF: sub_4363C+1C↓o
ROM:0004310C 31 31 38 36+                                        ; sub_4363C:loc_43724↓o
ROM:0004310C 35 64 36 34+                DCB 0
ROM:0004314D 00                          DCB    0
ROM:0004314E 65 61 39 36+aEa96b41c1f9365 DCB "ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd"
ROM:0004314E 62 34 31 63+                DCB 0
ROM:0004318F 00                          DCB    0
ROM:00043190 35 62 36 35+a5b65712d565c15 DCB "5b65712d565c1551340998102d418ceccb35db8dbfb45f9041c4cae483d8717b"
ROM:00043190 37 31 32 64+                                        ; DATA XREF: sub_4363C+28↓o
ROM:00043190 35 36 35 63+                DCB 0
ROM:000431D1 00                          DCB    0
ROM:000431D2 34 65 30 37+a4e07408562bedb DCB "4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce"
ROM:000431D2 34 30 38 35+                DCB 0
ROM:00043213 00                          DCB    0
ROM:00043214 30 33 33 63+a033c339a797554 DCB "033c339a7975542785be7423a5b32fa8047813689726214143cdd7939747709c"
ROM:00043214 33 33 39 61+                                        ; DATA XREF: sub_4363C+34↓o
ROM:00043214 37 39 37 35+                DCB 0
ROM:00043255 00                          DCB    0
ROM:00043256 34 62 32 32+a4b227777d4dd1f DCB "4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a"
ROM:00043256 37 37 37 37+                DCB 0
ROM:00043297 00                          DCB    0
ROM:00043298 63 38 31 64+aC81d40dbeed369 DCB "c81d40dbeed369f1476086cf882dd36bf1c3dc35e07006f0bec588b983055487"
ROM:00043298 34 30 64 62+                                        ; DATA XREF: sub_4363C+40↓o
ROM:00043298 65 65 64 33+                DCB 0
ROM:000432D9 00                          DCB    0
ROM:000432DA 65 66 32 64+aEf2d127de37b94 DCB "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d"
ROM:000432DA 31 32 37 64+                DCB 0
ROM:0004331B 00                          DCB    0
ROM:0004331C 39 65 32 35+a9e259b7f6b4c74 DCB "9e259b7f6b4c741937a96a9617b3e6b84e166ff6e925e414e7b72936f5a2a51f"
ROM:0004331C 39 62 37 66+                                        ; DATA XREF: sub_4363C+4C↓o
ROM:0004331C 36 62 34 63+                DCB 0
ROM:0004335D 00                          DCB    0
ROM:0004335E 65 37 66 36+aE7f6c011776e8d DCB "e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683"
ROM:0004335E 63 30 31 31+                DCB 0
ROM:0004339F 00                          DCB    0
ROM:000433A0 31 30 34 38+a1048f03db5d45f DCB "1048f03db5d45f654b955eae20d84b72673680fb13b318e7da22e8dce58df21c"
ROM:000433A0 66 30 33 64+                                        ; DATA XREF: sub_4363C+58↓o
ROM:000433A0 62 35 64 34+                DCB 0
ROM:000433E1 00                          DCB    0
ROM:000433E2 37 39 30 32+a7902699be42c8a DCB "7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451"
ROM:000433E2 36 39 39 62+                DCB 0
ROM:00043423 00                          DCB    0
ROM:00043424 38 66 30 37+a8f0703d406fdb0 DCB "8f0703d406fdb0ea8011d5de342c3aca62214758a8a2b5b8a4e9f1c8c6c42462"
ROM:00043424 30 33 64 34+                                        ; DATA XREF: sub_4363C+64↓o
ROM:00043424 30 36 66 64+                DCB 0
ROM:00043465 00                          DCB    0
ROM:00043466 32 63 36 32+a2c624232cdd221 DCB "2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3"
ROM:00043466 34 32 33 32+                DCB 0
ROM:000434A7 00                          DCB    0
ROM:000434A8 31 38 32 62+a182b32359558eb DCB "182b32359558eb092511b7166867503ddd83fbe5b42f2545e1903016e721393d"
ROM:000434A8 33 32 33 35+                                        ; DATA XREF: sub_4363C+70↓o
ROM:000434A8 39 35 35 38+                DCB 0
ROM:000434E9 00                          DCB    0
ROM:000434EA 31 39 35 38+a19581e27de7ced DCB "19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7"
ROM:000434EA 31 65 32 37+                DCB 0
ROM:0004352B 00                          DCB    0
ROM:0004352C 66 65 35 66+aFe5f0fc640cbbc DCB "fe5f0fc640cbbc113406f042d08cc60ba784c775f7c3299985665323c5fbcdc4"
ROM:0004352C 30 66 63 36+                                        ; DATA XREF: sub_4363C+7C↓o
ROM:0004352C 34 30 63 62+                DCB 0
ROM:0004356D 00                          DCB    0
ROM:0004356E 34 61 34 34+a4a44dc15364204 DCB "4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5"
ROM:0004356E 64 63 31 35+                DCB 0
ROM:000435AF 00                          DCB    0
ROM:000435B0 34 66 63 38+a4fc82b26aecb47 DCB "4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8"
ROM:000435B0 32 62 32 36+                                        ; DATA XREF: sub_4363C+88↓o
ROM:000435B0 61 65 63 62+                DCB 0
ROM:000435F1 00                          DCB    0
ROM:000435F2 31 62 61 35+a1ba586c0b89202 DCB "1ba586c0b89202f7307b61f1229330978a843afc98589ffc6a62f209225d3528"
ROM:000435F2 38 36 63 30+                DCB 0
ROM:00043633 00                          DCB    0
ROM:00043634 66 6C 61 67+aFlags          DCB "flags:",0          ; DATA XREF: sub_4363C+94↓o
ROM:0004363B 00                          DCB    0
ROM:0004363C
ROM:0004363C             ; =============== S U B R O U T I N E =======================================
ROM:0004363C
ROM:0004363C             ; Attributes: noreturn
ROM:0004363C
ROM:0004363C             ; void __noreturn sub_4363C()
ROM:0004363C             sub_4363C                               ; CODE XREF: ROM:00043000↑j
ROM:0004363C 02 DA A0 E3                 MOV             SP, #0x2000
ROM:00043640 44 00 4F E2+                ADRL            R0, aE0bc614e4fd035 ; "e0bc614e4fd035a488619799853b075143deea5"...
ROM:00043640 06 0C 40 E2
ROM:00043648 04 00 2D E5                 PUSH            {R0}
ROM:0004364C CC 00 4F E2+                ADRL            R0, aD8bdf9a0cb27a1 ; "d8bdf9a0cb27a193a1127de2924b6e5a9e4c2d3"...
ROM:0004364C 05 0C 40 E2
ROM:00043654 04 00 2D E5                 PUSH            {R0}
ROM:00043658 54 00 4F E2+                ADRL            R0, a6749dae311865d ; "6749dae311865d64db83d5ae75bac3c9e36b3aa"...
ROM:00043658 05 0C 40 E2
ROM:00043660 04 00 2D E5                 PUSH            {R0}
ROM:00043664 DC 00 4F E2+                ADRL            R0, a5b65712d565c15 ; "5b65712d565c1551340998102d418ceccb35db8"...
ROM:00043664 04 0C 40 E2
ROM:0004366C 04 00 2D E5                 PUSH            {R0}
ROM:00043670 64 00 4F E2+                ADRL            R0, a033c339a797554 ; "033c339a7975542785be7423a5b32fa80478136"...
ROM:00043670 04 0C 40 E2
ROM:00043678 04 00 2D E5                 PUSH            {R0}
ROM:0004367C FB 0F 4F E2                 ADR             R0, aC81d40dbeed369 ; "c81d40dbeed369f1476086cf882dd36bf1c3dc3"...
ROM:00043680 00 00 A0 E1                 NOP
ROM:00043684 04 00 2D E5                 PUSH            {R0}
ROM:00043688 DD 0F 4F E2                 ADR             R0, a9e259b7f6b4c74 ; "9e259b7f6b4c741937a96a9617b3e6b84e166ff"...
ROM:0004368C 00 00 A0 E1                 NOP
ROM:00043690 04 00 2D E5                 PUSH            {R0}
ROM:00043694 BF 0F 4F E2                 ADR             R0, a1048f03db5d45f ; "1048f03db5d45f654b955eae20d84b72673680f"...
ROM:00043698 00 00 A0 E1                 NOP
ROM:0004369C 04 00 2D E5                 PUSH            {R0}
ROM:000436A0 A1 0F 4F E2                 ADR             R0, a8f0703d406fdb0 ; "8f0703d406fdb0ea8011d5de342c3aca6221475"...
ROM:000436A4 00 00 A0 E1                 NOP
ROM:000436A8 04 00 2D E5                 PUSH            {R0}
ROM:000436AC 83 0F 4F E2                 ADR             R0, a182b32359558eb ; "182b32359558eb092511b7166867503ddd83fbe"...
ROM:000436B0 00 00 A0 E1                 NOP
ROM:000436B4 04 00 2D E5                 PUSH            {R0}
ROM:000436B8 65 0F 4F E2                 ADR             R0, aFe5f0fc640cbbc ; "fe5f0fc640cbbc113406f042d08cc60ba784c77"...
ROM:000436BC 00 00 A0 E1                 NOP
ROM:000436C0 04 00 2D E5                 PUSH            {R0}
ROM:000436C4 47 0F 4F E2                 ADR             R0, a4fc82b26aecb47 ; "4fc82b26aecb47d2868c4efbe3581732a3e7cbc"...
ROM:000436C8 00 00 A0 E1                 NOP
ROM:000436CC 04 00 2D E5                 PUSH            {R0}
ROM:000436D0 A4 10 4F E2                 ADR             R1, aFlags ; "flags:"
ROM:000436D4 00 00 A0 E1                 NOP
ROM:000436D8 0C 10 A0 E3                 MOV             R1, #0xC
ROM:000436DC
ROM:000436DC             loc_436DC                               ; CODE XREF: sub_4363C+E0↓j
ROM:000436DC 01 09 A0 E3+                MOV             R0, #0x4033
ROM:000436DC 33 00 80 E2
ROM:000436E4 04 20 9D E4                 POP             {R2}
ROM:000436E8 02 80 A0 E1                 MOV             R8, R2
ROM:000436EC 00 50 A0 E3                 MOV             R5, #0
ROM:000436F0 01 10 41 E2                 SUB             R1, R1, #1
ROM:000436F4
ROM:000436F4             loc_436F4                               ; CODE XREF: sub_4363C+D8↓j
ROM:000436F4 00 30 90 E5                 LDR             R3, [R0]
ROM:000436F8 00 40 92 E5                 LDR             R4, [R2]
ROM:000436FC 01 50 85 E2                 ADD             R5, R5, #1
ROM:00043700 10 00 55 E3                 CMP             R5, #0x10
ROM:00043704 06 00 00 AA                 BGE             loc_43724
ROM:00043708 04 00 80 E2                 ADD             R0, R0, #4
ROM:0004370C 04 20 82 E2                 ADD             R2, R2, #4
ROM:00043710 04 00 53 E1                 CMP             R3, R4
ROM:00043714 F6 FF FF 0A                 BEQ             loc_436F4
ROM:00043718 00 00 51 E3                 CMP             R1, #0
ROM:0004371C EE FF FF 1A                 BNE             loc_436DC
ROM:00043720 0A 00 00 EA                 B               loc_43750
ROM:00043724             ; ---------------------------------------------------------------------------
ROM:00043724
ROM:00043724             loc_43724                               ; CODE XREF: sub_4363C+C8↑j
ROM:00043724 62 9E 4F E2                 ADR             R9, a6749dae311865d ; "6749dae311865d64db83d5ae75bac3c9e36b3aa"...
ROM:00043728 00 00 A0 E1                 NOP
ROM:0004372C 09 00 58 E1                 CMP             R8, R9
ROM:00043730 06 00 00 1A                 BNE             loc_43750
ROM:00043734 01 18 A0 E3+                MOV             R1, #0x14390
ROM:00043734 01 19 81 E2+
ROM:00043734 03 1C 81 E2+
ROM:00043734 90 10 81 E2
ROM:00043744 01 20 A0 E3                 MOV             R2, #1
ROM:00043748 00 20 81 E5                 STR             R2, [R1]
ROM:0004374C 18 20 81 E5                 STR             R2, [R1,#0x18]
ROM:00043750
ROM:00043750             loc_43750                               ; CODE XREF: sub_4363C+E4↑j
ROM:00043750                                                     ; sub_4363C+F4↑j
ROM:00043750 00 00 A0 E3                 MOV             R0, #0
ROM:00043750             ; End of function sub_4363C
ROM:00043750
ROM:00043750             ; ROM           ends
ROM:00043750
ROM:00043750                             END

从汇编看,v2来自于 0x436E4 的 POP {R2} 指令。随着do-while的每次循环,pop出来的值都会变化。所以,相当于flag的sha256循环与这里硬编码的所有常量比较,如果相等且这个常量是 "6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023",就会成功对MEMORY[0x143A8]赋值。

总结下来,这段代码的作用就是:检查flag的sha256是"6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023"

(这里需要一个双重分割线)



WTF ?? 出题人搞笑吧??逆了大半天,逻辑就一个sha256检查,咋可能逆的出来??难道又是爆哈希(第二题的阴影还未散去)?但这啥约束都没有,长度又是32,怎么可能爆的出来??

回想起放题前交流群说题目有氪金和不氪金的两种解法,难道氪金解法是sha256反查?马上打开cmd5,满怀期待的把sha256输进去,结果是“未查到”??那这氪金也氪不出来啊?难道真的如群友所说,氪金解法是直接向出题人买flag??


秉承对题目质量的信任(CTF逆向题出了个给sha256求原值,只在搞笑段子里见过),会不会是程序其他地方还有对flag的额外约束?

回想前面的逆向过程,可能的坑一个是反调试导致分析出的逻辑是错的,另一个是起始的1146消息是谁发的。

在IDA汇编窗口全局搜索47Ah(1146),果然找到了另一处:sub_406490

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
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
void __thiscall sub_406490(_DWORD *this)
{
  int v1; // ecx
  int v2; // eax
  const unsigned __int16 *v3; // eax
  unsigned __int64 v4; // rax
  int v5; // ebx
  unsigned __int64 v6; // rax
  int v7; // edx
  _DWORD *v8; // ecx
  int v9; // esi
  int v10; // ebx
  int v11; // eax
  void *s; // edi
  int v13; // eax
  int *v14; // eax
  unsigned __int64 v15; // rax
  _WORD *v16; // ebx
  unsigned __int64 v17; // rax
  int v18; // edx
  int v19; // edi
  int v20; // esi
  unsigned __int64 v21; // rax
  int v22; // ecx
  unsigned __int64 v23; // rax
  __int16 v24; // dx
  unsigned __int64 v25; // rax
  int v26; // eax
  unsigned __int64 v27; // rax
  int v28; // eax
  _DWORD *v29; // edi
  int v30; // ebx
  char *v31; // eax
  __int16 *v32; // edx
  int v33[2]; // [esp+10h] [ebp-78h] BYREF
  int v34[2]; // [esp+18h] [ebp-70h] BYREF
  int v35[2]; // [esp+20h] [ebp-68h] BYREF
  _DWORD *v36; // [esp+28h] [ebp-60h]
  int v37; // [esp+2Ch] [ebp-5Ch]
  _DWORD *v38; // [esp+34h] [ebp-54h]
  int v39; // [esp+38h] [ebp-50h]
  int v40; // [esp+3Ch] [ebp-4Ch]
  int v41; // [esp+40h] [ebp-48h]
  int v42; // [esp+44h] [ebp-44h]
  struct wstring v43; // [esp+48h] [ebp-40h] BYREF
  __int64 v44; // [esp+60h] [ebp-28h] BYREF
  int v45; // [esp+68h] [ebp-20h]
  _DWORD v46[2]; // [esp+6Ch] [ebp-1Ch] BYREF
  int v47; // [esp+74h] [ebp-14h]
  int v48; // [esp+84h] [ebp-4h]
 
  v36 = this;
  v1 = this[155];
  if ( !v1 )
    return;
  v45 = 0;
  v44 = 0i64;
  (*(void (__thiscall **)(int, __int64 *, _DWORD))(*(_DWORD *)(v1 + 12) + 560))(v1 + 12, &v44, 0);
  v48 = 0;
  if ( sub_419D40((int)&v44) || soui_wstring_size((int)&v44) != 32 )// v44 is real input
    goto LABEL_43;
  v2 = soui_wstring_size((int)&v44);
  v3 = (const unsigned __int16 *)sub_41AD51((int)&v44, v2);
  sub_4028E0(&v43, v3);
  v4 = __rdtsc();
  srand(v4);
  v5 = rand() % 32;
  v6 = __rdtsc();
  v39 = v5 + 4;
  srand(v6);
  v7 = rand() % (v5 + 4);
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v37 = v7;
  v38 = 0;
  v46[0] = 0;
  v42 = 0;
  v46[1] = 0;
  v41 = 0;
  v47 = 0;
  v11 = 0;
  LOBYTE(v48) = 2;
  v40 = 0;
  if ( v39 <= 0 )
    goto LABEL_30;
  do
  {
    if ( v11 == v7 )
    {
      s = &v43;
      if ( v43.capacity >= 8u )
        s = v43.s;
      v35[0] = (int)s;
      v13 = soui_wstring_size((int)&v44);
      v35[1] = v13;
      if ( v9 != v10 )
      {
        *(_DWORD *)v9 = s;
        *(_DWORD *)(v9 + 4) = v13;
        v9 += 8;
        v46[1] = v9;
        goto LABEL_28;
      }
      v14 = v35;
    }
    else
    {
      v15 = __rdtsc();
      srand(v15);
      if ( (rand() & 1) != 0 )
      {
        v27 = __rdtsc();
        srand(v27);
        v33[0] = 0;
        v28 = rand();
        v33[1] = v28;
        if ( v9 != v10 )
        {
          *(_DWORD *)v9 = 0;
          *(_DWORD *)(v9 + 4) = v28;
          v9 += 8;
          v46[1] = v9;
          goto LABEL_28;
        }
        v14 = v33;
      }
      else
      {
        v16 = (_WORD *)alloc_memory(72);
        v17 = __rdtsc();
        srand(v17);
        v18 = rand() % 36;
        v19 = 1;
        v38 = (_DWORD *)(v18 + 1);
        if ( v18 + 1 > 1 )
        {
          v20 = v18 + 1;
          do
          {
            v21 = __rdtsc();
            srand(v21);
            v22 = rand() % 3;
            v23 = __rdtsc();
            v38 = (_DWORD *)HIDWORD(v23);
            if ( v22 )
            {
              if ( v22 == 1 )
              {
                srand(v23);
                v24 = rand() % 25 + 97;
              }
              else
              {
                srand(v23);
                v24 = rand() % 9 + 48;
              }
            }
            else
            {
              srand(v23);
              v24 = rand() % 25 + 65;
            }
            v16[v19++] = v24;
          }
          while ( v19 < v20 );
          v9 = v42;
        }
        *v16 = 0;
        v25 = __rdtsc();
        srand(v25);
        v34[0] = (int)v16;
        v26 = rand();
        v34[1] = v26;
        if ( v9 != v41 )
        {
          *(_DWORD *)v9 = v16;
          v10 = v41;
          *(_DWORD *)(v9 + 4) = v26;
          v9 += 8;
          v46[1] = v9;
          goto LABEL_28;
        }
        v14 = v34;
      }
    }
    sub_40ADB0((const void **)v46, (_BYTE *)v9, v14);
    v10 = v47;
    v9 = v46[1];
    v41 = v47;
LABEL_28:
    v7 = v37;
    v11 = v40 + 1;
    v42 = v9;
    v40 = v11;
  }
  while ( v11 < v39 );
  v8 = (_DWORD *)v46[0];
  v38 = (_DWORD *)v46[0];
LABEL_30:
  v29 = v8;
  if ( v8 != (_DWORD *)v9 )
  {
    v30 = (int)v36;
    do
    {
      (*(void (__stdcall **)(int, int, _DWORD, _DWORD))(*(_DWORD *)v30 + 176))(v30, 1146, *v29, v29[1]);// 1146, sub_4028C0, sendmessagew__
      v29 += 2;
    }
    while ( v29 != (_DWORD *)v9 );
    v10 = v41;
    v8 = v38;
  }
  if ( v8 )
  {
    v31 = (char *)v8;
    if ( ((v10 - (_DWORD)v8) & 0xFFFFFFF8) < 0x1000
      || (v8 = (_DWORD *)*(v8 - 1), (unsigned int)(v31 - (char *)v8 - 4) <= 0x1F) )
    {
      free_(v8);
      goto LABEL_38;
    }
LABEL_45:
    _invalid_parameter_noinfo_noreturn();
  }
LABEL_38:
  if ( v43.capacity >= 8u )
  {
    v32 = v43.s;
    if ( (unsigned int)(2 * v43.capacity + 2) >= 0x1000 )
    {
      v32 = (__int16 *)*((_DWORD *)v43.s - 1);
      if ( (unsigned int)((char *)v43.s - (char *)v32 - 4) > 0x1F )
        goto LABEL_45;
    }
    free_(v32);
  }
  v43.size = 0;
  v43.capacity = 7;
  LOWORD(v43.s) = 0;
LABEL_43:
  sub_41A562(&v44);
}

动态调试,这个函数开头是真实的获取输入框的内容,包括对长度32的检查;中间通过虚函数间接调用了sendmessage 1146,参数就是输入框的原始内容。

sub_406490的父级是sub_405AF0,已经明确的看到了L"btn_close"和L"check_va",所以发送消息这边应该没有更多逻辑了。

另一处1146的地方在sub_405C50

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
BOOL __thiscall sub_405C50(HWND *this, int a2, int a4, _OWORD *arg8, HWND a5, int *a6, int a7)
{
  HWND v8; // esi
  BOOL v9; // eax
  LRESULT v10; // eax
  int v12; // [esp-10h] [ebp-38h]
  _OWORD *v13; // [esp-Ch] [ebp-34h]
  HWND v14; // [esp-8h] [ebp-30h]
  int v15; // [esp+20h] [ebp-8h] BYREF
  int savedregs; // [esp+28h] [ebp+0h] BYREF
 
  if ( a7 )
    return 0;
  switch ( a4 )
  {
    case 1:
      this[181] = 0;
      break;
    case 272:
      this[181] = (HWND)1;
      *a6 = init_window((int)this); // sub_405F10
      goto LABEL_9;
    case 16:
      this[181] = (HWND)1;
      deinit_windows(this);
      break;
    default:
      goto LABEL_10;
  }
  *a6 = 0;
LABEL_9:
  if ( this[181] )
    return 1;
LABEL_10:
  v8 = this[153];
  v9 = sub_403120(this, a2, a4, (UINT_PTR)arg8, (unsigned int)a5, a6, 0);
  this[153] = v8;
  if ( v9 )
    return 1;
  v15 = 1;
  v10 = sub_A47CEF(this + 1, a4, (WPARAM)arg8, a5, &v15);
  *a6 = v10;
  if ( v10 || (unsigned int)(a4 - 306) > 6 )
  {
    if ( v15 )
      return 1;
  }
  switch ( a4 )
  {
    case 1146:
      v14 = a5;
      v13 = arg8;
      v12 = 1146;
      goto LABEL_22;
    case 1147:
      v14 = a5;
      v13 = arg8;
      v12 = 1147;
      goto LABEL_22;
    case 1149:
      v14 = a5;
      v13 = arg8;
      v12 = 1149;
      goto LABEL_22;
    case 1148:
      v14 = a5;
      v13 = arg8;
      v12 = 1148;
LABEL_22:
      v15 = 1;
      *a6 = handle_window_message((int)this, (int)&savedregs, (int)this, v12, v13, (int)v14); // sub_4061D0
      return a2 != 0;
  }
  if ( a4 != 1150 )
    return 0;
  v15 = 1;
  *a6 = handle_window_message((int)this, (int)&savedregs, (int)this, 1150, arg8, (int)a5); // sub_4061D0
  return a2 != 0;
}

这里看起来是接收消息的主分发器所在(以及,它的上两级父级函数也是直接到了虚表中),前面有一个初始化函数 sub_405F10

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
// sub_405F10
int __thiscall init_window(int this)
{
  int v2; // eax
  int v3; // esi
  int v4; // esi
  int v5; // eax
  int v6; // eax
  int v7; // esi
  int v8; // esi
  int v9; // eax
  bool v10; // zf
  HANDLE TimerQueue; // eax
  char v13[1160]; // [esp+10h] [ebp-920h] BYREF
  char Destination[1164]; // [esp+498h] [ebp-498h] BYREF
  int v15; // [esp+92Ch] [ebp-4h]
  int retaddr; // [esp+934h] [ebp+4h]
 
  *(_DWORD *)(this + 728) = 1;
  v2 = sub_A50E21((void *)L"img_logo", -1);
  v3 = v2;
  if ( v2 && (*(int (__stdcall **)(int, const wchar_t *))(*(_DWORD *)v2 + 12))(v2, L"img") )
  {
    v4 = v3 - 4;
  }
  else
  {
    v5 = sub_A3C15E(Destination, "soui4", 3, (int)&unk_D3C680, (int)"SOUI::SWindow::FindChildByName2", 640, retaddr);
    v15 = 0;
    sub_A3C2AE(v5);
    sub_A3C294("FindChildByName2 Failed, no window of class ");
    sub_A3C29D(L"img");
    sub_A3C294(" with name of ");
    sub_A3C29D(L"img_logo");
    sub_A3C294(" was found within ");
    sub_A3C264(-1);
    sub_A3C294(" levels");
    v15 = -1;
    sub_A3C1B2(Destination);
    v4 = 0;
  }
  *(_DWORD *)(this + 616) = v4;
  v6 = sub_A50E21((void *)L"input_va", -1);
  v7 = v6;
  if ( v6 && (*(int (__stdcall **)(int, const wchar_t *))(*(_DWORD *)v6 + 12))(v6, L"edit") )
  {
    v8 = v7 - 12;
  }
  else
  {
    v9 = sub_A3C15E(v13, "soui4", 3, (int)&unk_D3C680, (int)"SOUI::SWindow::FindChildByName2", 640, retaddr);
    v15 = 1;
    sub_A3C2AE(v9);
    sub_A3C294("FindChildByName2 Failed, no window of class ");
    sub_A3C29D(L"edit");
    sub_A3C294(" with name of ");
    sub_A3C29D(L"input_va");
    sub_A3C294(" was found within ");
    sub_A3C264(-1);
    sub_A3C294(" levels");
    v15 = -1;
    sub_A3C1B2(v13);
    v8 = 0;
  }
  v10 = *(_DWORD *)(this + 616) == 0;
  *(_DWORD *)(this + 620) = v8;
  if ( v10 || !v8 )
    deinit_windows((_DWORD *)this);
  TimerQueue = CreateTimerQueue();
  *(_DWORD *)(this + 660) = TimerQueue;
  if ( TimerQueue )
  {
    CreateTimerQueueTimer(
      (PHANDLE)(this + 664),
      TimerQueue,
      _IsNonwritableInCurrentImage,
      *(PVOID *)(this + 28),
      0x1F4u,
      0x7D0u,
      0);
    CreateTimerQueueTimer((PHANDLE)(this + 668), *(HANDLE *)(this + 660), antidebug1, 0, 0x258u, 0x7D0u, 0);  // sub_407900
    CreateTimerQueueTimer((PHANDLE)(this + 672), *(HANDLE *)(this + 660), antidebug2, 0, 0x2BCu, 0x7D0u, 0);  // sub_4079D0
    CreateTimerQueueTimer((PHANDLE)(this + 676), *(HANDLE *)(this + 660), sub_407A60, 0, 0x320u, 0x7D0u, 0);
  }
  SetForegroundWindow(*(HWND *)(this + 28));
  *(_DWORD *)(this + 624) = SetTimer(*(HWND *)(this + 28), 1u, 0x64u, TimerFunc);
  return 0;
}

CreateTimerQueueTimer初始化的 sub_407900 和 sub_4079D0 分别是 NtQueryInformationThread 和 NtQueryInformationProcess 调试器检测,如果检测到被调试就调用 ExitProcess 或 TerminateProcess 退出。
(但是,尝试patch这两处仍然会在调试时闪退,不知道哪里还有其他检测)


所以,看起来程序其他地方没有隐藏的逻辑了。所以题目真的能解吗?


无奈之下乱搞,把arm代码中的所有sha256都抄出来逐一往cmd5和google搜索塞,有了新的发现:

sha256 plaintext
e0bc614e4fd035a488619799853b075143deea596c477b8dc077e309c0fe42e9 6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b
6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b 1
d8bdf9a0cb27a193a1127de2924b6e5a9e4c2d3b3fe42e935e160c011f3df1fc d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35
d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35 2
6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023
ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd
5b65712d565c1551340998102d418ceccb35db8dbfb45f9041c4cae483d8717b 4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce
4e07408562bedb8b60ce05c1decfe3ad16b72230967de01f640b7e4729b49fce 3
033c339a7975542785be7423a5b32fa8047813689726214143cdd7939747709c 4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a
4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a 4
c81d40dbeed369f1476086cf882dd36bf1c3dc35e07006f0bec588b983055487 ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d
ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d 5
9e259b7f6b4c741937a96a9617b3e6b84e166ff6e925e414e7b72936f5a2a51f e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683
e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683 6
1048f03db5d45f654b955eae20d84b72673680fb13b318e7da22e8dce58df21c 7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451
7902699be42c8a8e46fbbb4501726517e86b22c56a189f7625a6da49081b2451 7
8f0703d406fdb0ea8011d5de342c3aca62214758a8a2b5b8a4e9f1c8c6c42462 2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3
2c624232cdd221771294dfbb310aca000a0df6ac8b66b696d90ef06fdefb64a3 8
182b32359558eb092511b7166867503ddd83fbe5b42f2545e1903016e721393d 19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7
19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7 9
fe5f0fc640cbbc113406f042d08cc60ba784c775f7c3299985665323c5fbcdc4 4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5
4a44dc15364204a80fe80e9039455cc1608281820fe2b24f1e5233ade6af1dd5 10
4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8 11
1ba586c0b89202f7307b61f1229330978a843afc98589ffc6a62f209225d3528 4fc82b26aecb47d2868c4efbe3581732a3e7cbcc6c2efb32062c08170a05eeb8

相邻的两条sha256,有的其中一条是简单整数的sha256,另一条是前面sha256的sha256。
自己尝试正向计算了一下补全表格,只剩两个无法搞出来,其中 6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023 按照之前的分析是程序输入框的正确flag,那么按照上面表格的规律,它应该也是 ea96b41c1f9365c2c9e6342f5faaeab2a44471efe1e65a2356a974646d2588fd 的sha256,但实际计算下来却不对。

然后!随手把它往程序的输入框里粘贴,结果验证成功??再一看是被截断成了32位,所以正确的flag是 ea96b41c1f9365c2c9e6342f5faaeab2,计算了下它的sha256确实是 6749dae311865d64db83d5ae75bac3c9e36b3aa6f24caba655d9682f7f071023。

(writeup完。这里需要第二个双重分割线)



如果这真的是出题人的预设解法(希望不是如此,而是程序还有什么隐藏的校验逻辑我没有找到,等出题人公开解法,如果错怪了,在此先提前道歉然后删除此段),只能说今天长见识了,第一次见到出成这样的题也敢称为Reverse。
最后一步就全靠脑洞吗?程序的唯一关键路径只有一个不可逆的sha256,只靠分析程序运算逻辑无法逆推出flag;当然,或许可以解释为“隐藏线索”,然后这些sha256确实是刻意构造且存在规律的。这种脑洞题虽然不鼓励但也是能出的(当然选手如何评价是另一回事),但惯例上,这类题最多只能被归类为 Misc。那么,按照今年的规则,misc题目禁止参赛,应当如何处理呢?

另外,其实不太明白本次比赛规则为什么特别禁止了misc题目参赛。难道是因为去年秋季赛的无解misc么?
传统的四大分类(Pwn、Reverse、Crypto、Web),虽然没有成文的标准,但基本上是比较死板严格的(以Reverse为例,目标就是逆推出输入,那么程序中给的约束必须是充分且可逆的,而且应该以运算的方式在从输入flag到输出判定结果的数据流的关键路径上;而本题,唯一的线索是所谓的24个sha256找规律,除了实际参与判断的那个,其他都删掉或乱给,程序的校验逻辑仍然能通过,也就是说这些规律并不参与到判断flag正确性的运算中,而只是作为旁路的一个提示,因此完全从运算逻辑无法逆推),misc更多的作为一个补集,收容所有不进入四大类的题目。所以,高质量比赛经常出现一些思路创新但不便于按传统分类的题目放到misc中。
KCTF如果允许其他四类题目参赛但不允许misc,对题目的多样性是不友好的。合理猜测禁止的初衷是保证题目质量,避免脑洞/无解/作者自己不解(例如,随手写个加密算法套层vmp/重量级混淆当逆向题,但是出题人自己不公开自动化去混淆方法的情况,这种题适合在论坛中长期交流或悬赏,放到以技术交流为目的的比赛中是对攻击方的严重不公平)等情况出现,题目质量应该依靠验题来保证。
据了解,一些高质量国际赛的出题规范之一就是作者自己必须在“做题人”的视角下“真的”会解,因为比赛的目的是技术交流,而不是破解悬赏。例如,如果题目加了强混淆的,在公开预期解writeup时必须要给出自己的去混淆方法(自动化或工作量可承受的人工),而不是仅仅依靠自己知道混淆前的原算法,给一份逆推代码了事(这样搞等着被做题人喷吧),因为做题人在对抗混淆之前看不到原算法,所以出题人当站在做题人的视角上验题时,就不能一上来假设自己拥有原算法。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 8
打赏
分享
最新回复 (2)
雪    币: 6013
活跃值: (3815)
能力值: ( LV10,RANK:163 )
在线值:
发帖
回帖
粉丝
yimingqpa 1 2023-9-7 13:41
2
0
不好意思,对不起!  这题的本意是想让大家体验分析过程的乐趣,结果不重要。。。
原本arm代码里面有sha256明文加回去的代码rsa解密等一系列代码,但是怕代码太长太多影响分析时间,所以直接去掉给出了明文。
雪    币: 20199
活跃值: (29735)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-9-8 09:36
3
1
感谢分享
游客
登录 | 注册 方可回帖
返回