首页
社区
课程
招聘
8
[原创]Wibu Codemeter 7.3学习笔记——Codemeter API调用及通信协议
发表于: 2022-12-2 22:59 21431

[原创]Wibu Codemeter 7.3学习笔记——Codemeter API调用及通信协议

2022-12-2 22:59
21431

看完了回个贴呗,你们的关注是我更新的动力

求一个Wibu AxProtector的license,或者是一个能用的AxProtector,后续研究需要

0x00前言

Wibu Codemeter 是一个非常优秀的加密壳,论坛上讨论、研究的较少,只有少数帖子能够参考bluefish的wibu证书初探 ,德国威步CodeMeter通信协议中的漏洞分析 我斗胆记录一下我的学习历程,望请抛砖引玉,不吝赐教。

PS:因为wibu codemeter一直没有泄露的源码或者pdb,所以我对函数的命名就很玄学,大家多多包涵

0x01样品

Codemeter SDK(Axprotector)7.3 [下载]CodeMeter5.22 AxProtector 9.12下载 4楼5楼

Ap**s

Gas****

建议使用IDA Pro搭配FindCrypt ScyllaHide,ScyllaHide。ScyllaHide调成下面的选项就能过掉所有的反调试反跟踪手段。

IDA调试前会报一大堆错,直接设置成No Suspend Pass to application静默处理即可,不要试图这时候调试,容易跑飞

EXCEPTION_ILLEGAL_INSTRUCTION No Application Silent

EXCEPTION_ACCESS_VIOLATION No Application Log

EXCEPTION_WX86_BREAKPOINT No Application Log

MS_VC_EXCEPTION No Application Silent



0x02初步分析

Codemeter整体架构类似flexlm,rlm,都为server-client式的授权系统但破解难度上来说前者是后者的十倍甚至九倍。

Server Codemeter.exe(主要负责CmContainer即license的授权管理,为重点研究对象) CodemeterCC.exe CmWebAdmin.exe(为Codemeter.exe的图形化交互界面不重要)


Client wibucm32.dll wibucm64.dll(负责架起未用AxProtector加密的用户软件与Server间的桥梁,能被aheadlib的方式破掉)
          被Axprotector保护的用户软件(直接与Server通信)

对于java,.Net,python则是通过WibuCmNET.dll,CodeMeter.jar等间接调用wibucm32(64).dll,大同小异不再赘述

下面贴一段从Ap**s中扒下来进行授权的代码,Ap**s本体调用wibucm64.dll中的函数来实现授权

从这段代码以及wibu官方提供的指南可知,wibu codemeter checkout时只是去服务器上查找是否有对应授权,但在客户端上却不进行检查,有也是软件服务商的自建算法。因此,我们需要重点照顾的对象便是CmAccess和CmGetInfo这两个函数。

0x03向CmAccess进发

WibuCm32(64).dll没有加壳所以分析还是比较快乐的,导出的CmAccess通过几层皮套Api到了真正做事的地方(Codemeter Api中所有函数,类型,结构体等都能在Codemeter/Devkit/include/CodeMeter.h中找到,可以稍微减轻点工作量)

通过分析,可以一眼丁真的分析可以发现整个cm_access函数中基本上都是参数,版本检查,错误处理的鸡毛蒜皮的小事,而cmacc这个重要的参数最终传进了send_cm_socket_req这个函数。

看一眼send_cm_socket_req的交叉引用,发现这个函数跟基本上所有的Cm API有一腿


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

最后于 2023-1-27 00:35 被ericyudatou编辑 ,原因: 2022年12月4日更新,修正拼写错误 2022年12月7日更新,修正代码,补图
收藏
免费 8
支持
分享
赞赏记录
参与人
雪币
留言
时间
mb_fdzmkugw
为你点赞~
2023-9-23 17:55
CanineTooth
为你点赞~
2023-4-1 12:53
伟叔叔
为你点赞~
2023-3-18 00:20
PLEBFE
为你点赞~
2023-1-10 14:26
Kingh413
为你点赞~
2022-12-29 10:18
jiavjiav
为你点赞~
2022-12-17 10:25
AEVE
为你点赞~
2022-12-8 10:58
tomtory
为你点赞~
2022-12-3 09:56
最新回复 (11)
雪    币: 8959
活跃值: (4901)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
分析的很到位!
2022-12-3 09:05
0
雪    币: 4752
活跃值: (2628)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
期待后续...
2022-12-6 20:10
0
雪    币: 4999
活跃值: (5076)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
期待后续...
2022-12-7 18:39
0
雪    币: 1194
活跃值: (4897)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
5

0x07 send_cm_socket_Req初探二——参数协议

1
2
3
4
5
6
char __thiscall send_cm_socket_Req(
        LPCRITICAL_SECTION *this,
        _DWORD *buf,              //发送的数据报内容
        unsigned int final_length,//长度
        unsigned int len2,        //长度final_length,len2取最长
        char flag)  //数据发送失败是否尝试重启Codemeter服务

显而易见,需要重点分析buf的结构,从wibucm32.dll中可以通过调用以及编译残留的字符串可以发现各个Cm api调用send_cm_socket_Req的参数。初步分析如下(完整版见附件):

通过归类可知,这些Cm Api分为三大类

    1.超短型:不需要传递太多的参数,如CmGetVersion CmGetInfoExt CmRelease CmGetBoxes等

    2.本地实现型:通过调用其他Cm Api或者直接本地解决,如CmCryptEcies CmCalculateDigest等

    3.超长型:1.传递巨量参数 2.buf[9]=0 3.buf[1]~buf[8]={-1,0,0,0,0,-1,0,0}这个特定的开头 如CmGetContainerInfo CmGetAccountInfo CmWriteSettings        等 (黄色标出),不是研究的重点

    4.一般型:1.传递一定参数 2.buf[9]≠0 3.buf[1]~buf[8]={-1,0,0,0,0,-1,0,0}这个特定的开头 如CmAccess CmAccess2 CmGetInfo等,为研究重点

通过分析我们可以发现一些未公开的Cm Api如CmControl CmCreateLicenseFile等,其中CmControl非常值得我们分析研究(挖坑)

0x08 send_cm_socket_Req初探三——我们需要再深入一些send_cm_req_encrypt

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
char __thiscall send_cm_socket_req(int this, _DWORD *buf, int final_length, unsigned int len2, char flag)
{
  _DWORD *buf_1; // edi
  int v7; // eax
  int err_4; // eax
  int function_table_006F8A58; // eax
  char result; // al
  int v11; // eax
  int v12; // ebx
  char err; // cl
  int v14; // eax
  int err_2; // eax
  int v16; // eax
  unsigned __int64 v17; // kr10_8
  int v18; // eax
  int v19; // eax
  int v20; // eax
  int v21; // eax
  int err_3; // [esp+10h] [ebp-38h]
  int v23; // [esp+14h] [ebp-34h]
  int v24; // [esp+1Ch] [ebp-2Ch]
  char err_1; // [esp+23h] [ebp-25h]
  int v26[2]; // [esp+24h] [ebp-24h] BYREF
  int time1[2]; // [esp+2Ch] [ebp-1Ch] BYREF
  LPCRITICAL_SECTION *v28[5]; // [esp+34h] [ebp-14h] BYREF
 
  buf_1 = buf;
  v7 = sub_553BC0();                            // 多线程相关
  err_4 = sub_554DF0(v7, 0);                    // should ret 1 Codemeter服务器检测 没启动的话挂起服务
  err_3 = err_4;
  if ( err_4 != 1 )
  {
    switch ( err_4 )
    {
      case 0:
      case 2:
      case 3:
        function_table_006F8A58 = get_function_table_006F8A58();
        (*(void (__thiscall **)(intint))(*(_DWORD *)function_table_006F8A58 + 4))(function_table_006F8A58, 101);// 未找到CodeMeter服务器, 错误 101.
        result = 0;
        break;
      case 4:
        v11 = get_function_table_006F8A58();
        (*(void (__thiscall **)(intint))(*(_DWORD *)v11 + 4))(v11, 308);// 该数据无法写入安全区, 错误 308.
        goto LABEL_5;
      default:
LABEL_5:
        result = 0;
        break;
    }
    return result;
  }
  v28[0] = 0;
  sub_4FE310(v28, (LPCRITICAL_SECTION *)(this + 60));
  v28[4] = 0;
  v12 = 0;
  v24 = 3;
  v23 = 0;
  while ( 1 )
  {
    get_time(time1);
    err = send_cm_req_encrypt((struct_this_2 *)this, buf_1, final_length, len2);// should ret 0 加密数据包并发送
    err_1 = err;
    if ( err && buf_1[2] == 238 )               // CodeMeter 许可服务器启动仍旧在待定中,错误238.
    {
      sleep(1000u);
      v14 = 20;
      v24 = 20;
      goto LABEL_24;                            // try again
    }
    err_2 = *(_DWORD *)(*(_DWORD *)(this + 40) + 12);// getlasterror
    if ( err_2 == 309 )                         // CodeMeter许可服务器超载, 访问请求已超时,该请求已被拒绝, 错误309.
    {
      sleep(1000 * (v23 + 1));
      v12 = v23;
      v24 = 20;
      v14 = 20;
      goto LABEL_24;                            // try again
    }
    if ( err )
    {
LABEL_31:
      v21 = get_function_table_006F8A58();
      (*(void (__thiscall **)(int, _DWORD))(*(_DWORD *)v21 + 4))(v21, *(_DWORD *)(*(_DWORD *)(this + 40) + 12));// cm_set_error
      goto LABEL_32;
    }
    if ( flag && (err_2 == 101 || err_2 == 102) )// 无法将请求发送至其他CodeMeter服务器, 错误 102.
                                                // 未找到CodeMeter服务器, 错误 101.
    {
      v16 = sub_553BC0();
      err_3 = sub_554DF0(v16, 1);
    }
    get_time(v26);
    v17 = v26[1] + 1000000 * (v26[0] - (__int64)time1[0]) - time1[1];
    if ( v17 >= 950
              * (unsigned __int64)(unsigned int)(*(int (__thiscall **)(_DWORD))(**(_DWORD **)(this + 40) + 96))(*(_DWORD *)(this + 40)) )
      break;                                    // 超时检测(网络,反调试)
    v12 = v23;
    if ( v23 != 2 || *(_DWORD *)(this + 56) != 1 )
    {
      buf_1 = buf;
LABEL_23:
      v14 = v24;
      goto LABEL_24;
    }
    buf_1 = buf;
    if ( (*(_BYTE *)(sub_5269C0() + 132) & 2) == 0 )
      goto LABEL_23;
    *(_DWORD *)(this + 40) = *(_DWORD *)(this + 48);
    v14 = v24 + 1;
    *(_DWORD *)(this + 56) = 2;
    ++v24;
LABEL_24:
    v23 = ++v12;
    if ( v12 >= v14 )
      goto LABEL_28;
  }
  v18 = *(_DWORD *)(this + 40);
  if ( !*(_DWORD *)(v18 + 12) )
    *(_DWORD *)(v18 + 12) = 100;
LABEL_28:
  switch ( err_3 )
  {
    case 0:
    case 1:
      goto LABEL_31;
    case 2:
    case 3:
      v19 = get_function_table_006F8A58();
      (*(void (__thiscall **)(intint))(*(_DWORD *)v19 + 4))(v19, 0x65);// 未找到CodeMeter服务器, 错误 101
      break;
    case 4:
      v20 = get_function_table_006F8A58();
      (*(void (__thiscall **)(intint))(*(_DWORD *)v20 + 4))(v20, 0x134);// 该数据无法写入安全区, 错误 308.
      break;
    default:
      break;
  }
LABEL_32:
  sub_4FE380(v28);
  return err_1;
}

分析可知send_cm_socket_Req的主要功能是判断服务器状态以及错误处理,实际上转发给send_cm_req_encrypt

最后于 2022-12-24 23:18 被ericyudatou编辑 ,原因:
上传的附件:
2022-12-7 23:29
0
雪    币: 1194
活跃值: (4897)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6

0x09 通信协议初探

Codemeter在通信上除了使用一个私有的算法进行加密并与系统时间挂钩以外并没有别的门槛,只是一个简单的winsock通信框架,Wireshark能抓包。这里观察一下通信上的行为。

可以发现端口50777的客户端与端口22350的Codemeter服务器间的通信行为:

客户端对服务端的通信行为可以概括如下

  1. 首先发送一个长度为16字节的明文握手包package01结构为如下,length为下一个加密数据包的长度
    struct handshake_package{
         char magic[4] = "samc";
         dword length;
         byte flag;
         char gap[7] = {00,01,00,00,00,00,00,00};
    }

  2. 发送一个长度为length的加密包package02,内容全部为加密后的数据,没有长度等信息
    (更正)

    第二个加密数据包第一个字节是固定的,0xA0,之后才是被加密的数据

  3. 等待服务器回话

但服务端对客户端的通信行为却有点不同

服务器对客户端倒像一个高冷御姐,没有了单独的握手包,但是却把握手包与数据杂合到一块。

服务器返回的数据包前16字节是跟客户端handshake_package的结构一样,后面就是长度为length的加密后的数据。

0x10 send_cm_req_encrypt,func_10_send_message, func_6_recv_telegram

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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
char __thiscall send_cm_req_encrypt(struct_this_2 *this, _DWORD *buf, int final_length, unsigned int definelength)
{
  unsigned int len2; // edx
  _DWORD *buf_1; // edi
  SIZE_T len1; // eax
  unsigned int lenbuf_2; // ecx
  __m128i *buf_2; // eax
  __int8 *v10; // ecx
  struct_name_3 *arr; // eax
  char err; // al
  int dword28; // edx
  int final_length_2; // ecx
  _DWORD *buf_4; // eax
  unsigned int len_1; // ecx
  _DWORD *calltb; // edx
  _DWORD *buf_3; // edi
  char v19; // al
  int v20; // ecx
  char *v21; // esi
  int v22; // eax
  char flag; // [esp+Ch] [ebp-58h]
  unsigned int len1_; // [esp+10h] [ebp-54h]
  int *plaintext; // [esp+10h] [ebp-54h]
  char err_1; // [esp+1Bh] [ebp-49h]
  unsigned int v28; // [esp+1Ch] [ebp-48h] BYREF
  int flag_1; // [esp+20h] [ebp-44h] BYREF
  struct_reallocateMem lpMem; // [esp+24h] [ebp-40h] OVERLAPPED BYREF
  char *v31; // [esp+3Ch] [ebp-28h] BYREF
  int v32; // [esp+40h] [ebp-24h]
  int v33; // [esp+44h] [ebp-20h]
  int len; // [esp+48h] [ebp-1Ch] BYREF
  int final_length_1; // [esp+4Ch] [ebp-18h] BYREF
  char gapshould1; // [esp+53h] [ebp-11h] BYREF
  int issuccess; // [esp+60h] [ebp-4h]
 
  len2 = 0x1000;                                // 准备:确定长度
  buf_1 = buf;                                  // buf + 0 ??_7YS0061@@6B@
  if ( definelength > 0x1000 )
    len2 = definelength;
  len1 = final_length + 56;
  v33 = 0;
  *(_OWORD *)&lpMem.dword0 = 0i64;
  if ( len2 > final_length + 56 )
    len1 = len2;
  lenbuf_2 = 0;                                 // len1取最大长度
  final_length_1 = 0;
  *(_DWORD *)&lpMem.isReallocated = 1;
  len = len2;
  LOBYTE(flag_1) = 0;
  gapshould1 = 0;
  v28 = 0;
  len1_ = len1;
  lpMem.dword0 = &YS0073::YS0080<unsigned char>::`vftable';
  memset(&lpMem.pBuf, 0, 12);
  *(_OWORD *)&lpMem.doInit0 = 0i64;
  issuccess = 0;
  if ( len1 )                                   // 准备:分配内存
  {
    buf_2 = (__m128i *)allocmem(len1);
    lenbuf_2 = len1_;
    lpMem.pBuf = buf_2->m128i_i32;              // buf
    lpMem.size = len1_;                         // len
    lpMem.used_size = len1_;                    // len
    if ( lpMem.doInit0 == 1 )
    {
      memset(buf_2, 0, len1_);
      lenbuf_2 = lpMem.used_size;
      buf_2 = (__m128i *)lpMem.pBuf;
    }
  }
  else
  {
    buf_2 = 0;
  }
  lpMem.pBuf = buf_2->m128i_i32;
  issuccess = 1;
  if ( lenbuf_2 )
  {
    v10 = &buf_2->m128i_i8[lenbuf_2];           // wtf?
  }
  else
  {
    v10 = 0;
    buf_2 = 0;
  }
  memset(buf_2, 0, v10 - (__int8 *)buf_2);      // 准备发送
  err_1 = prepareNetwork((int)this);            // 网络连接ret 1
  if ( err_1 )
  {
    final_length_1 = final_length;
    arr = 0;
    if ( lpMem.used_size )
      arr = (struct_name_3 *)lpMem.pBuf;
    plaintext = &arr->buf_new;
    err_1 = (*(int (__thiscall **)(_DWORD *, int *, int *))(*buf + 4))(buf, &arr->buf_new, &final_length_1);// expand_plaintext 将buf中的原文进行操作转换成v11+16数据包
    if ( err_1 && final_length_1 == final_length )
    {
      flag = (*(int (__thiscall **)(_DWORD *))(*buf + 24))(buf);// ret 0 NTI
      if ( flag )
      {
        final_length_2 = final_length_1;
      }
      else
      {
        err = (*(int (__thiscall **)(struct_this_2 *, int *, int *, _DWORD))(this->dword0 + 12))(// encrypt_telegram
                this,
                plaintext,
                &final_length_1,
                this->unsigned___int840);
        dword28 = this->dword28;
        err_1 = err;
        if ( !err )
        {
          *(_DWORD *)(dword28 + 12) = 301;      // 对CodeMeter Runtime Server的访问操作无法被加密, 错误 301.
          goto LABEL_45;
        }
        final_length_2 = final_length_1;
        if ( (unsigned int)final_length_1 > *(_DWORD *)(dword28 + 16) )
        {
          *(_DWORD *)(dword28 + 12) = 112;      // 传递给CodeMeter驱动程序的数据段太小, 错误 112.
          goto LABEL_45;
        }
      }
      buf_4 = 0;
      if ( lpMem.used_size )
        buf_4 = lpMem.pBuf;
      len_1 = final_length_2 + 1;
      *((_BYTE *)buf_4 + 15) = 0xA0;
      calltb = (_DWORD *)this->dword28;
      if ( len_1 < calltb[4] )
      {
        err_1 = (*(int (__thiscall **)(_DWORD *, int, unsigned intchar))(*calltb + 24))(
                  calltb,
                  (int)buf_4 + 15,
                  len_1,
                  flag);                        // func_10_send_message 网络:发送数据
        if ( err_1 )
        {
          sub_56E3D0(buf);                      // buf[6]=-1
          while ( 1 )
          {
            err_1 = (*(int (__thiscall **)(_DWORD, struct_reallocateMem *, int *, int *, char *, _DWORD))(*(_DWORD *)this->dword28 + 32))(// char __thiscall func_5_recv_message(struct_this_1 *this, struct_reallocateMem *buf, int pLen, _BYTE *flag, int gapshould1, int a6)
                                                // 网络,接受服务器返回数据
                      this->dword28,
                      &lpMem,
                      &len,
                      &flag_1,
                      &gapshould1,
                      0);
            if ( !err_1 )
              break;
            (*(void (__thiscall **)(_DWORD *, int))(*buf_1 + 28))(buf_1, flag_1);
            buf_3 = 0;
            if ( lpMem.used_size )
              buf_3 = lpMem.pBuf;
            if ( !flag )
            {
              if ( len == 1 )
                goto LABEL_40;
              err_1 = (*(int (__thiscall **)(struct_this_2 *, _DWORD *, int *, _DWORD))(this->dword0 + 16))(
                        this,
                        buf_3,
                        &len,
                        this->unsigned___int840);// decrypt_telegram
              if ( !err_1 )
              {
                *(_DWORD *)(this->dword28 + 12) = 302;// 通讯加解密出错, 错误 302.
                break;
              }
            }
            v28 = 0;
            if ( !sub_56F180(buf, buf_3, len, &v28) )
            {
              if ( sub_56E240(buf_3, len) )
              {
                *(_DWORD *)(this->dword28 + 12) = 309;// CodeMeter许可服务器超载, 访问请求已超时,该请求已被拒绝, 错误309.
LABEL_40:
                err_1 = 0;
                break;
              }
              v19 = (*(int (__thiscall **)(_DWORD *, _DWORD *, int))(*buf + 8))(buf, buf_3, len);
              v20 = this->dword28;
              err_1 = v19;
              if ( v19 )
                *(_DWORD *)(v20 + 12) = buf[2];
              else
                *(_DWORD *)(v20 + 12) = 100;    // 发生网络错误, 错误 100.
              break;
            }
            buf_1 = buf;
          }
        }
      }
      else
      {
        calltb[3] = 112;
        err_1 = 0;
      }
    }
    else
    {
      *(_DWORD *)(this->dword28 + 12) = 100;    // 发生网络错误, 错误 100.
    }
  }
LABEL_45:
  v21 = v31;
  v22 = v32;
  issuccess = 2;
  for ( lpMem.dword0 = &YS0073::YS0080<unsigned char>::`vftable'; v21 != (char *)v22; v21 += 4 )
  {
    if ( *(_DWORD *)v21 )
    {
      (*(void (__thiscall **)(_DWORD, _DWORD))(**(_DWORD **)v21 + 4))(*(_DWORD *)v21, 0);// good
      v22 = v32;
    }
  }
  if ( lpMem.isReallocated )
  {
    if ( lpMem.pBuf )
    {
      if ( lpMem.doInit0 == 1 )
        memset((__m128i *)lpMem.pBuf, 0, lpMem.used_size);
      free(lpMem.pBuf);                         // realease
    }
    memset(&lpMem.pBuf, 0, 12);
    lpMem.isReallocated = 1;
  }
  release(&v31);                                // release
  return err_1;
}
char __userpurge func_10_send_message@<al>(
        _DWORD *this@<ecx>,
        int a2@<ebx>,
        int a3@<edi>,
        char *buf,
        unsigned int len,
        int a6)
{
  unsigned int i; // edi
  int len_1; // eax
  int v12; // [esp-8h] [ebp-10h]
  int v14; // [esp-4h] [ebp-Ch]
 
  if ( (this[2] & 2) == 0 )
  {
    this[3] = 100;                              // 发生网络错误, 错误 100.
    return 0;
  }
  if ( (*(unsigned __int8 (__thiscall **)(_DWORD *, unsigned intint))(*this + 112))(this, len, a6) )// func_9_send_handshake
                                                // 握手,发送密文的长度
  {
    i = 0;
    if ( !len )
      return 1;
    while ( 1 )
    {
      len_1 = send(this[5], buf, len - i, 0);   // 发送加密报文
      if ( len_1 < 0 )
        break;
      i += len_1;
      buf += len_1;
      if ( i >= len )
        return 1;
    }
    if ( (*(unsigned __int8 (__thiscall **)(_DWORD *, intint))(*this + 80))(this, a3, a2) )
      send(this[5], Default, 0, 0);
    if ( (*(unsigned __int8 (__thiscall **)(_DWORD *, intint))(*this + 80))(this, v12, v14) )
    {
      shutdown(this[5], 2);
      if ( closesocket(this[5]) < 0 )
        this[3] = 100;                          // 发生网络错误, 错误 100.
      this[2] &= ~2u;
      this[5] = -1;
    }
    sub_4FE2C0();
    this[3] = 102;                              // 无法将请求发送至其他CodeMeter服务器, 错误 102.
  }
  return 0;
}
bool __thiscall func_9_send_handshake(_DWORD *thisint len, char a3)
{
  char buf[16]; // [esp+4h] [ebp-14h] BYREF
 
  constractHandshake((int)buf, len, a3);        // 73 61 6D 63 D1 00 00 00  41 00 01 00 00 00 00 00
                                                // 
                                                // 73 61 6D 63 len 00 00 00  a3|1 00 01 00 00 00 00 00
  return send_handshake(thisthis[5], buf, 16) == 16;// this[5] socket
}
handshake_package *__cdecl constractHandshake(handshake_package *buf, int len, char a3)
{
  *buf = 0i64;
  buf->len = len;
  *(_DWORD *)buf->magic = 'cmas';
  buf->gap[1] = 1;
  if ( a3 )
    buf->flag = a3 | 1;
  else
    buf->flag = 65;
  return buf;
}
char __thiscall func_5_recv_message(
        struct_this_1 *this,
        struct_reallocateMem *buf,
        int pLen,
        _BYTE *flag,
        int gapshould1,
        int a6)
{
  SOCKET fd; // edi
 
  *flag = 0;
  fd = this->fd;
  if ( (this->dword8 & 2) != 0 )
  {
    if ( (*(unsigned __int8 (__thiscall **)(struct_this_1 *, SOCKET, struct_reallocateMem *, int, _BYTE *, int))(this->dword0 + 36))(
           this,
           this->fd,
           buf,
           pLen,
           flag,
           gapshould1) )                        // bool __thiscall func_6_recv_telegram(_DWORD *this, SOCKET fd, struct_reallocateMem *buf_2, int *plen, char *flag_1, _BYTE *gapshould1)
    {
      return 1;
    }
    else
    {
      if ( fd == this->fd )
      {
        this->dword8 &= ~2u;
        this->fd = -1;
      }
      return 0;
    }
  }
  else
  {
    this->errcode = 100;
    return 0;
  }
}
bool __thiscall func_6_recv_telegram(
        struct_this_1 *this,
        SOCKET fd,
        struct_reallocateMem *buf_2,
        int *plen,
        char *flag_1,
        _BYTE *gapshould1)
{
  int v6; // esi
  int len; // ebx
  char flag; // bl
  int len_1; // ecx
  struct_this_1 *v10; // edx
  unsigned int used_size; // eax
  SIZE_T newsize; // eax
  int v13; // eax
  __m128i *buf; // edi
  int v15; // eax
  unsigned int v17; // eax
  __m128i *pBuf; // edi
  int recvlen; // eax
  handshake_package buf_1; // [esp+1Ch] [ebp-14h] BYREF
  *flag_1 = 0;
  v6 = 0;
  *gapshould1 = 0;
  if ( !*plen )
    return 0;
  buf_1 = 0i64;
  len = recv_message((int)this, fd, buf_1.magic, 16);
  if ( !(*(unsigned __int8 (__thiscall **)(struct_this_1 *, intint *))(this->dword0 + 108))(this, len, plen) )
    return 0;
  if ( len == 16 )
  {
    if ( *(_DWORD *)buf_1.magic == 'cmas' )     // handshake len and magic check
    {
      flag = buf_1.flag;                        // a3 | 1
      len_1 = buf_1.len;                        // len
      if ( (buf_1.flag & 1) == 0 && buf_1.len >= 0x20000u )// 握手包参数检查
        return 0;
      v10 = this;
      if ( buf_1.len >= *(_DWORD *)this->gap10 )
        return 0;
      used_size = buf_2->used_size;
      if ( used_size < buf_1.len )
      {
        newsize = 0x1000;
        if ( buf_1.len > 0x1000u )
          newsize = buf_1.len;
        reallocateMem(buf_2, newsize);
        v13 = buf_2->used_size;
        if ( !v13 )
          return 0;
        flag = buf_1.flag;
        len_1 = buf_1.len;
        v10 = this;
        *plen = v13;
        used_size = buf_2->used_size;
      }
      if ( used_size )
        buf = buf_2->pBuf;
      else
        buf = 0;
      *flag_1 = flag;
      *gapshould1 = buf_1.gap[1];
      if ( len_1 > 0 )
      {
        while ( 1 )
        {
          v15 = recv_message((int)v10, fd, buf->m128i_i8, len_1 - v6);
          if ( v15 < 0 )
            break;
          if ( !v15 )
            goto LABEL_34;
          len_1 = buf_1.len;
          v6 += v15;
          v10 = this;
          buf = (__m128i *)((char *)buf + v15);
          if ( v6 >= buf_1.len )
          {
            *plen = v6;
            return v6 != 0;
          }
        }
        if ( v15 == -2 )
          v6 = -1;
        goto LABEL_23;
      }
      goto LABEL_34;
    }
  }
  else if ( len < 16 )
  {
    goto LABEL_34;
  }
  v17 = buf_2->used_size;
  if ( v17 < 0x1000 )
  {
    reallocateMem(buf_2, 0x1000u);
    v17 = buf_2->used_size;
  }
  if ( v17 )
    pBuf = buf_2->pBuf;
  else
    pBuf = 0;
  memmove((unsigned int)pBuf, (unsigned int)&buf_1, len);
  v6 = len;
  recvlen = recv_message((int)this, fd, &pBuf->m128i_i8[len], *plen - len);
  if ( recvlen < 0 )
  {
LABEL_23:
    *plen = v6;
    return 0;
  }
  if ( recvlen )
    v6 = recvlen + len;
LABEL_34:
  *plen = v6;
  return v6 != 0;
}

对Codemeter的逆向的感受有别于flexlm,最突出也是最蛋疼的就是codemeter运用了大量的虚函数,虚表。使得有很多程序逻辑不能直接了当的呈现出来,而是必须通过动态调试才能知悉。

因为这个send_cm_req_encrypt很长所以直接呈上分析结果,验证后发现func_10_send_message,func_6_recv_telegram的逻辑与wireshark抓包的结果相符,可以认为分析是正确的

0x11 伪造服务端

通过调试可知encrypt_telegram,decrypt_telegram,expand_plaintext等加解密函数在服务端也被复用,因此我们可以通过对wibucm32.dll的魔改,导出这些重要的函数以减少工作量,不用复现那些加解密算法

1
2
encrypt_telegram
decrypt_telegram

通过实践发现只需要这两个函数笑ψ(`∇´)ψ

最后于 2022-12-28 15:33 被ericyudatou编辑 ,原因:
2022-12-25 00:10
0
雪    币: 1194
活跃值: (4897)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
7

0x11 伪造服务端

帖子被加精了,新人第一回,有点受宠若惊(❁´◡`❁)

记于2022年12月28日:

经过几天摸爬滚打,Fake server已经初见雏形。已经能够解密客户端发送的加密数据包了

简单的记录一下过程中遇到的问题和发现

1.让人摸不着头脑的错误

一开始通过GetProcAddress获得函数的地址进行访问,但总是离奇出错,出错原因各不相同。后来排查发现

1
typedef char(* Type_encrypt_telegram)(struct_communication*, char* buf, int* length, char flag);

这样编译出来的和这么编译出来的

1
typedef char(__thiscall* Type_encrypt_telegram)(struct_communication*, char* buf, int* length, char flag);

生成出的汇编代码不同,前一种报错而后一种则正常运行。也算是涨经验了。

2.客户端对服务端的请求格式

1
2
3
4
5
6
7
8
9
10
11
12
13
struct PKG
{
    char APICODE;
        FUNCTION_ARGS
};
//举个例子 对于 CODEMETER_API HCMSysEntry CMAPIENTRY CmAccess2(CMULONG flCtrl, CMACCESS2 *pcmAcc);
//请求包的结构就是这样
struct PKG_CMACCESS2
{
    char APICODE;
    CMULONG flCtrl;
    CMACCESS2 pcmAcc;
};

举个例子

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
[*]Client 127.0.0.1:13794 login,time=63479375
[*]Valid Handeshake
[*]Recieved handshake package,length=737,magic=samc?
[*]Encrypted package decrypt:SUCCESS,len:712
[*]Function Request:CmAccess2
[*][CmAccess2] flctrl=0x10000000,mflCtrl=0x00000100,firmcode=1000,productcode=114514,featurecode=1919810
[*]Printing Decrypted Request:
[*]-------------------------------------------PRINT HEX---------------------------------------------------
[*]00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
[*]-------------------------------------------------------------------------------------------------------
[*]64 00 00 00 00 00 00 10 00 01 00 00 ffffffe8 03 00 00
[*]52 ffffffbf 01 00 42 4b 1d 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 65 64 61 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 fffffff8 45 00 00 3d ffffffc0 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 73 68 65 6e 77 65 6e 68
[*]75 61 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00
[*]00 00 00 00 45 44 41 00 00 00 00 00 00 00 00 00
[*]00 00 00 00 ffffffd4 12 1e 07
[*]----------------------------------------------END------------------------------------------------------
[*]Client 127.0.0.1:13794 out

之后需要做的就是

  1. 完成服务器对客户端的回复这一行为的函数

  2. 优先实现cmaccess cmaccess2 cmrelease等函数的模拟

  3. 实现其他函数

  4. 做个GUI

附件为更新过的各个API所对应的API Code

上传的附件:
2022-12-28 16:06
0
雪    币: 1049
活跃值: (589)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
学习了,感谢大佬的分享!
2022-12-29 10:26
0
雪    币: 1194
活跃值: (4897)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
9
2022-12-31 17:44
0
雪    币: 319
活跃值: (2559)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
10
超强的耐心和耐力,钦佩楼主
2023-1-28 09:27
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
可以请教一下吗大佬,最近刚好在搞license这个项目,想问一下您有没有使用过WibuCmHIPApi这个库,我用的c++的库貌似出了点问题,谢谢!
2023-12-11 09:30
0
雪    币: 1590
活跃值: (1433)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
谢谢大佬!Ap**s和Gas****是指什么呀?
2024-5-9 17:07
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册