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

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

2023-9-7 07:05
10438

为什么下载附件会被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@(
        int a1@,
        int a2@,
        int a3@,
        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@(int a1@, 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快速跳转到想要的函数,还是不太方便)


[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!

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