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

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

2022-12-2 22:59
17922

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

求一个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中的函数来实现授权

if ( a1[8] == 2 )
  {
    memset(&CMACC, 0, sizeof(CMACC));
    CMACC.mflCtrl |= 0x100u;
    setReleaseDate(&CMACC, *a1);
    CMACC.mulFirmCode = a2;
    CMACC.mulProductCode = a1[6] + 9000;
    if ( (unsigned int)sub_1813506F0(v7) )
    {
      CMACC.mulSerialNumber = sub_1813506F0(v8);
      CMACC.musBoxMask = sub_1813506D0(v9);
    }
    strncpy(CMACC.mszServername, "localhost", 0x80ui64);
    hcmse = CmAccess2(0i64, &CMACC);//获取对应license的指针,失败,不存在返回0
    hcmse_1 = hcmse;
    if ( hcmse )//判断一,必修得有这个license
    {
      v35 = (void ***)hcmse;
      len = CmGetInfo(hcmse, 4i64, 0i64);       // CM_GEI_ENTRYDATA 0x0004
      len_1 = len;
      if ( len >= 528 )
      {
        v18 = len / 0x210ui64;
        v19 = (int)v18;
        entrydata = (CMENTRYDATA *)operator new(saturated_mul((int)v18, 0x210ui64));
        if ( (unsigned int)CmGetInfo(hcmse_1, 4i64, entrydata) )//获取license的用户自定义数据
        {
          if ( (int)v18 > 0 )
          {
            secret_data = entrydata->mabData;
            do
            {
              v24 = *((_DWORD *)secret_data - 4);
              if ( (_WORD)v24 == 64 && (v24 & 0xFFFF0000) == 0x10000 )  //判断二:用户自建算法
              {
                v25 = *((_DWORD *)secret_data - 1);
                if ( v25 >= 1 )
                  a1[14] = *secret_data;
                if ( v25 >= 2 )
                  a1[15] = secret_data[1];
                if ( v25 >= 3 )
                  a1[16] = secret_data[2];
                if ( v25 >= 4 )
                  a1[17] = secret_data[3];
                if ( v25 >= 5 )
                  a1[18] = secret_data[4];
              }
              secret_data += 528;
              --v19;
            }
            while ( v19 );
          }
          if ( a1[9] == a1[14] && a1[10] == a1[15] && a1[11] == a1[16] && a1[12] == a1[17] && a1[13] == a1[18] )
          {
            v17 = 0;
          }

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

0x03向CmAccess进发

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

CODEMETER_API HCMSysEntry CMAPIENTRY CmAccess(CMULONG flCtrl, CMACCESS *pcmAcc);

CODEMETER_API HCMSysEntry CMAPIENTRY CmAccess2(CMULONG flCtrl, CMACCESS2 *pcmAcc);
int __thiscall cm_access(int calltbl, int flCtrl, CMACCESS2 *cmacc, int isCMACCESS2)
{
  int calltbl_1; // esi
  UINT_PTR len; // eax
  int hcmse; // eax
  char isCMACCESS2_1; // cl
  unsigned int mulUsedRuntimeVersion; // eax
  char flCtrl_1; // di
  void *v10; // eax
  _DWORD *v11; // eax
  _BYTE *v12; // eax
  __int16 v13; // ax
  char v14; // al
  int hcmse_1; // edi
  CMACCESS2 *cmacc_2; // eax
  int v17; // eax
  char v18; // al
  unsigned int serverversion; // eax
  unsigned int runtimeversion; // [esp+10h] [ebp-188h]
  int v22; // [esp+14h] [ebp-184h]
  CMACCESS2 *cmacc_1; // [esp+18h] [ebp-180h]
  CMACCESS2 *cmacc_3; // [esp+1Ch] [ebp-17Ch]
  int buf_1[56]; // [esp+28h] [ebp-170h] BYREF
  __int128 v26; // [esp+108h] [ebp-90h] BYREF
  unsigned int buf[16]; // [esp+118h] [ebp-80h] BYREF
  _QWORD buf_2[6]; // [esp+158h] [ebp-40h] BYREF
  int v29; // [esp+194h] [ebp-4h]

  calltbl_1 = calltbl;                          // /*
                                                //   flags for mflCtrl in CMACCESS 280000h CM_ACCESS_SUBSYSTEM+
                                                // */
                                                // /* flags for kind of access */
                                                // #define CM_ACCESS_USERLIMIT           0x00000000
                                                // #define CM_ACCESS_NOUSERLIMIT         0x00000100
                                                // #define CM_ACCESS_EXCLUSIVE           0x00000200
                                                // #define CM_ACCESS_STATIONSHARE        0x00000300
                                                // #define CM_ACCESS_CONVENIENT          0x00000400
                                                // /* mask for the access modes */
                                                // #define CM_ACCESS_STRUCTMASK          0x00000700
                                                // 
                                                // /* no validation check of the entry data */
                                                // #define CM_ACCESS_FORCE               0x00010000
                                                // /* constant for searching a fitting FSB entry */
                                                // #define CM_ACCESS_CHECK_FSB           0x00020000
                                                // /* constant for searching a fitting CTSB entry */
                                                // #define CM_ACCESS_CHECK_CTSB          0x00040000
                                                // /* allow normal subsystem access if no CmContainer is found */
                                                // #define CM_ACCESS_SUBSYSTEM           0x00080000
                                                // /* force FI access to prevent access to a FC:PC=x:0 */
                                                // #define CM_ACCESS_FIRMITEM            0x00100000
                                                // 
                                                // /* flag access to borrow license */
                                                // #define CM_ACCESS_BORROW_ACCESS       0x01000000
                                                // /* flag release to borrow license */
                                                // #define CM_ACCESS_BORROW_RELEASE      0x02000000
                                                // /* flag check borrowed license */
                                                // #define CM_ACCESS_BORROW_VALIDATE     0x04000000
                                                // /* flag ignore entry state for release to borrow license */
                                                // #define CM_ACCESS_BORROW_IGNORESTATE  0x08000000
                                                //   
                                                // #define CM_ACCESS_BORROW_MASK         0x0f000000
                                                // /* flag ignore Linger Time of License */
                                                // #define CM_ACCESS_IGNORE_LINGERTIME  0x10000000
  cmacc_3 = 0;
  cmacc_1 = 0;
  len = 0x2C0;
  if ( !(_BYTE)isCMACCESS2 )
    len = 0xB0;
  if ( !sub_385100((void *)calltbl, cmacc, len) )// ->sub_7D17A0 检查输入参数
                                                // 给出的内存地址无效, 错误 113.
    return 0;
  if ( !cmacc )
  {
LABEL_5:
    (*(void (__thiscall **)(int, int))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 105);// 指定了一个无效的参数, 错误 105.
    return 0;
  }
  isCMACCESS2_1 = isCMACCESS2;
  if ( (_BYTE)isCMACCESS2 )
  {
    cmacc_1 = cmacc;
    if ( !cmacc->mulFirmCode && cmacc->mulProductCode == 0x186A0 )
      cmacc->mflCtrl |= 0x10000u;
    if ( cmacc->mulLicenseQuantity > 1 )
    {
      mulUsedRuntimeVersion = cmacc->mulUsedRuntimeVersion;
      if ( mulUsedRuntimeVersion )
      {
        if ( mulUsedRuntimeVersion < 0x614083E )
          goto LABEL_5;
      }
      else
      {
        cmacc->mulUsedRuntimeVersion = 0x614083E;
      }
    }
  }
  else
  {
    cmacc_3 = cmacc;
    if ( !cmacc->mulFirmCode && cmacc->mulProductCode == 100000 )
      cmacc->mflCtrl |= 0x10000u;
  }
  flCtrl_1 = flCtrl;
  if ( flCtrl < 0 )
  {
    v10 = (void *)sub_383BC0();
    if ( !sub_383E90(v10) )
    {
      (*(void (__thiscall **)(int, int))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 101);// 未找到CodeMeter服务器, 错误 101.
      return 0;
    }
    isCMACCESS2_1 = isCMACCESS2;
  }
  v22 = 0;
  while ( 1 )
  {
    if ( isCMACCESS2_1 )
    {
      hcmse_1 = cmaccesss2((_BYTE *)calltbl_1, flCtrl_1, cmacc_1);// cmaccess2重点
      cmacc_2 = cmacc_1;
    }
    else
    {
      memset((__m128i *)buf_1, 0, sizeof(buf_1));// cmaccess1
      buf_1[1] = -1;
      buf_1[2] = 0;
      LOBYTE(buf_1[3]) = 0;
      buf_1[4] = 0;
      buf_1[5] = 0;
      buf_1[6] = -1;
      buf_1[7] = 0;
      LOBYTE(buf_1[8]) = 0;
      buf_1[0] = (int)&YS0061::`vftable';
      LOBYTE(buf_1[9]) = 10;
      memset((__m128i *)&buf_1[10], 0, 0xB8u);
      v29 = 0;
      v11 = (_DWORD *)sub_32CD60();
      cmacc_3->mulReserved1 = getpid(v11);
      v12 = (_BYTE *)sub_383BC0();
      if ( sub_384C80(v12) )
        cmacc_3->mulReserved1 = 0;
      buf_1[10] = flCtrl_1 & 0x13 | 0x10000000;
      qmemcpy(&buf_1[11], cmacc_3, 0xB0u);
      calltbl_1 = calltbl;
      v13 = *(_BYTE *)(calltbl + 500) ? *(_WORD *)(calltbl + 498) : (unsigned __int8)sub_3741C0((_BYTE *)calltbl);
      HIWORD(buf_1[17]) = v13;
      v14 = send_cm_socket_req(calltbl + 24, buf_1, 184, 8u, 1); //重点
      v29 = -1;
      buf_1[0] = (int)&YS0061::`vftable';
      hcmse_1 = v14 ? buf_1[55] : 0;
      sub_39E140(buf_1);
      cmacc_2 = cmacc_3;
    }
    runtimeversion = cmacc_2->mulUsedRuntimeVersion;
    if ( !hcmse_1 )
      break;
    if ( (unsigned __int8)sub_3A2480(hcmse_1) )
    {
      ++*(_DWORD *)(calltbl_1 + 104);
      break;
    }
    memset((__m128i *)buf, 0, sizeof(buf));
    sub_370CC0((int)buf);
    v29 = 1;
    v17 = sub_3A25B0(calltbl_1 + 92, hcmse_1);
    sub_39E430(buf, (unsigned __int16)hcmse_1, 0x200B, 0x10u, v17);
    v18 = send_cm_socket_req(calltbl_1 + 24, buf, 16, 0x1Cu, 0);// cm_get_info
    v26 = 0i64;
    if ( !v18 || !sub_39E2A0(buf, (unsigned int)&v26) )
    {
      (*(void (__thiscall **)(int, _DWORD))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 0);// good
      sub_3A2630((unsigned __int16)hcmse_1);
      if ( (unsigned __int8)sub_3A2480(hcmse_1) )
      {
        ++*(_DWORD *)(calltbl_1 + 104);
        v29 = -1;
        buf[0] = (unsigned int)&YS0065::`vftable';
        if ( buf[15] )
          (**(void (__thiscall ***)(unsigned int, int))buf[15])(buf[15], 1);
        sub_39E140(buf);
        break;
      }
    }
    memset((__m128i *)buf_2, 0, sizeof(buf_2));
    HIDWORD(buf_2[0]) = -1;
    LODWORD(buf_2[1]) = 0;
    BYTE4(buf_2[1]) = 0;
    buf_2[2] = 0i64;
    buf_2[3] = 0xFFFFFFFFi64;
    LOBYTE(buf_2[4]) = 0;
    LODWORD(buf_2[0]) = &YS0060::`vftable';
    BYTE4(buf_2[4]) = 11;
    LOBYTE(v29) = 2;
    buf_2[5] = (unsigned int)hcmse_1;
    send_cm_socket_req(calltbl_1 + 24, buf_2, 8, 8u, 0);
    LODWORD(buf_2[0]) = &YS0060::`vftable';
    sub_39E140(buf_2);
    v29 = -1;
    buf[0] = (unsigned int)&YS0065::`vftable';
    if ( buf[15] )
      (**(void (__thiscall ***)(unsigned int, int))buf[15])(buf[15], 1);
    sub_39E140(buf);
    flCtrl_1 = flCtrl;
    isCMACCESS2_1 = isCMACCESS2;
    if ( ++v22 >= 5 )
    {
      (*(void (__thiscall **)(int, int))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 127);// CodeMeter客户端和服务端的句柄不一致, 错误 127.
      return 0;
    }
  }
  hcmse = (unsigned __int16)hcmse_1;
  if ( (_WORD)hcmse_1 && runtimeversion )
  {
    serverversion = get_server_version((void *)calltbl_1, (unsigned __int16)hcmse_1);
    if ( serverversion && serverversion >= runtimeversion )
    {
      return (unsigned __int16)hcmse_1;
    }
    else
    {
      (*(void (__thiscall **)(int, _DWORD))(*(_DWORD *)calltbl_1 + 16))(calltbl_1, (unsigned __int16)hcmse_1);// sub_7CDB80
      (*(void (__thiscall **)(int, int))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 0x7D);// 所连接的CodeMeter 服务器版本过旧, 错误 125.
      return 0;
    }
  }
  return hcmse;
}
int __thiscall cmaccesss2(_BYTE *this, char flCtrl, CMACCESS2 *CMACC)
{
  _DWORD *v4; // eax
  _BYTE *v5; // eax
  const char *v6; // edx
  void *v7; // ecx
  int hcmse; // esi
  char *v9; // edi
  bool v10; // zf
  char v11; // al
  void *v13[5]; // [esp+10h] [ebp-28h] BYREF
  unsigned int v14; // [esp+24h] [ebp-14h]
  const char *v15; // [esp+28h] [ebp-10h]
  int v16; // [esp+34h] [ebp-4h]
  int buf[188]; // [esp+38h] [ebp+0h] BYREF
  __int64 v18; // [esp+328h] [ebp+2F0h] BYREF
  int v19; // [esp+330h] [ebp+2F8h]

  memset((__m128i *)buf, 0, sizeof(buf));
  buf[1] = -1;
  buf[2] = 0;
  LOBYTE(buf[3]) = 0;
  buf[4] = 0;
  buf[5] = 0;
  buf[6] = -1;
  buf[7] = 0;
  LOBYTE(buf[8]) = 0;
  buf[0] = (int)&YS0062::`vftable';
  LOBYTE(buf[9]) = 100;
  memset((__m128i *)&buf[10], 0, 0x2C8u);
  v16 = 0;
  v4 = (_DWORD *)sub_4FCD60();                  // get proccess
  CMACC->mcmCredential.mulPID = getpid(v4);
  v5 = (_BYTE *)sub_553BC0();
  if ( sub_554C80(v5) )                         // 是否为wine模拟器
    CMACC->mcmCredential.mulPID = 0;
  CMACC->mcmCredential.mbLibType = 1;
  CMACC->mcmCredential.mulLibVersion = 0x71E12D4;
  setusername(CMACC);
  v19 = 0;
  v18 = 0i64;
  sub_549300((int)&v18);
  LOBYTE(v16) = 1;
  v6 = sub_4F9BE0(&v18);
  v13[4] = 0;
  v14 = 15;
  LOBYTE(v13[0]) = 0;
  v15 = v6 + 1;
  allocstring(v13, (unsigned int)v6, strlen(v6));
  LOBYTE(v16) = 2;
  sub_57B260((int)&CMACC->mcmCredential, (int)v13);
  if ( v14 >= 0x10 )
  {
    v7 = v13[0];
    LOBYTE(v16) = 3;
    if ( v14 + 1 >= 0x1000 )
    {
      v7 = (void *)*((_DWORD *)v13[0] - 1);
      if ( (unsigned int)(v13[0] - v7 - 4) > 0x1F )
        sub_50EA4C();
    }
    sub_5D3A97(v7);
  }
  LOBYTE(v16) = 0;
  sub_48FC60((void **)&v18);
  if ( sub_57B9C0((__m128i *)CMACC->mcmCredential.mszUserDefinedText, 0x80u) )
  {
    CMACC->mcmBorrowData.mszClientName[127] = 0;
    if ( !strlen(CMACC->mcmBorrowData.mszClientName) )
      gethostname(CMACC->mcmBorrowData.mszClientName, 127);
    v9 = &CMACC->mcmBorrowData.mszClientName[strlen(CMACC->mcmBorrowData.mszClientName) + 1];
    memset((__m128i *)(v9 - 1), 0, 128 - (v9 - &CMACC->mcmBorrowData.mszClientName[1]));
    v10 = this[500] == 0;
    buf[10] = flCtrl & 0x13 | 0x10000000;
    qmemcpy(&buf[11], CMACC, 0x2C0u);
    if ( v10 )
    {
      buf[128] = (unsigned __int8)sub_5441C0(this);
      HIBYTE(buf[179]) = 0;
    }
    else
    {
      v11 = sub_5441C0(this);
      buf[128] = *((unsigned __int16 *)this + 249);
      HIBYTE(buf[179]) = v11;
    }
    if ( send_cm_socket_req((int)(this + 24), buf, 712, 8u, 1) )
      hcmse = buf[187];
    else
      hcmse = 0;
  }
  else
  {
    (*(void (__thiscall **)(_BYTE *, int))(*(_DWORD *)this + 4))(this, 105);// 指定了一个无效的参数, 错误 105.
    hcmse = 0;
  }
  buf[0] = (int)&YS0062::`vftable';
  sub_56E140(buf);
  return hcmse;
}

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

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

send_cm_socket_Req
cm_get_info_
cm_access_borrow
cm_access
CmCalculateSignature_1
CmControl_1
cm_get_info_1
CmProgramS_1
CmBoxIoControl_0
CmCalculateSignature_0
CmControl_0
CmCreateLicenseFile_0
CmCreateProductItemOption_0
CmCreateSequence_0
CmEnablingGetChallenge_0
CmEnablingSendResponse_0
CmExecuteRemoteUpdate_0
CmGetAccountInfo_0
CmGetBoxDiagnosticData_0
CmGetBoxes_0
CmGetContainerInfo_0
CmGetFileInfo_0
CmCalculatePioCoreKey_0
CmEnablingGetApplicationContext_0
CmEnablingWriteApplicationKey_0
CmBoxIoControl
CmCalculateSignature
CmControl
CmExtendedDiscControl_0
CmCreateLicenseFile
...(212 Lines Total)

所以基本断定,授权是在服务器codemeter.exe上进行,wibucm32(64)就是个给人打工的小喽喽。

0x04初探send_cm_socket_req

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 **)(int, int))(*(_DWORD *)function_table_006F8A58 + 4))(function_table_006F8A58, 101);// 未找到CodeMeter服务器, 错误 101.
        result = 0;
        break;
      case 4:
        v11 = get_function_table_006F8A58();
        (*(void (__thiscall **)(int, int))(*(_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 **)(int, int))(*(_DWORD *)v19 + 4))(v19, 0x65);// 未找到CodeMeter服务器, 错误 101
      break;
    case 4:
      v20 = get_function_table_006F8A58();
      (*(void (__thiscall **)(int, int))(*(_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这个函数

0x05CodeMeter has never been cracked……吗?

对用axprotector保护的axprotector.exe进行分析,尽管原始代码已经变得很抽象但是壳本体倒是没有进行混淆或者是虚拟机保护。抱有侥幸心理用bindiff比对一下,嚯,您猜怎么着,那堆CM API绝大部分都有,连咱心心念念的CmAccess,CmGetInfo都在,并且代码上跟wibucm32中的别无二致。还是通过send_cm_socket_req跟服务器唠嗑,自己就等着服务器给个授不授权的信儿然后解密。

再次我斗胆提出一个思路,望诸多大佬指正:

对于Wibu Axprotector,可以避其锋芒,不直接面对那些反调试,反dump的奇技淫巧,可否对壳与服务器,wibucm32(64).dll与服务器间的协议进行逆向,编写出一个虚假的服务器,让壳以为虚假的服务器进行了可信的授权然后运行呢?

类似的思路已经有人实践,TEAM R2R的codemeter waifu通过伪造wibucm32(64).dll攻击类似Ap**s这种通过调用dll进行授权的软件系统,并且这种攻击方式确实有效。

0x06通信协议(天坑)

诸位先贤(bluefish的wibu证书初探 ,德国威步CodeMeter通信协议中的漏洞分析)已经指出,Codemeter服务器与客户端间通信的私有协议的加密方式:

  1. 生成与时间(getTickCount)相关的random key getTickCount->sha1->randomkey

  2. 原始报文进行crc32并与原报文进行拓展

  3. 对拓展的报文进行AES-128 CBC加密,密钥,iv拆分于randomkey

德国威步CodeMeter通信协议中的漏洞分析这篇文章给出了encrypt_telegram这个函数的实现方式,因此为我们减少了很大的工作量。

int __thiscall getRandomkey(struct_this *this, char flag)
{
  int time; // edx
  int result; // eax
  unsigned int v5; // [esp+4h] [ebp-7Ch] BYREF
  _DWORD sha1[24]; // [esp+8h] [ebp-78h] BYREF
  char sha1res[20]; // [esp+68h] [ebp-18h] BYREF

  if ( flag == 1 )
    time = GetTimeFromFile(0);
  else
    time = GetTickCount() / 1000;
  this->time = time;  //this + 36
  sha1[23] = 0;
  v5 = 1000 * time / 1009u;
  sha1_init(sha1);
  sha1_update(sha1, (unsigned int)&v5, 4u);
  result = sha1_final((char *)sha1, (int *)sha1res);
  this->key = *(_OWORD *)sha1res; //this + 4
  this->iv = *(_OWORD *)&sha1res[4]; //this + 20
  return result;
}
int __cdecl bitnegation(_DWORD *a1)
{
  if ( a1 )
    return ~*a1;
  else
    return -1;
}
__m128i *__cdecl expand_text(int buf, unsigned int *end_pos, char a3)
{
  int v3; // ecx
  unsigned int v4; // eax
  __m128i *result; // eax

  if ( buf )
  {
    if ( end_pos )
    {                                           // ==========================
                                                // |                  |     |
                                                // ==========================
                                                //                 (end_pos)
                                                //                 
                                                // =============================
                                                // |                  ^  -->    |
                                                // =============================
                                                //                  (ep)  (ep+39) 
                                                //                       (new_ep)
                                                // ==============================
                                                // |                            |
                                                // ==============================    
                                                //                       (new_ep)
                                                //                       
                                                // ==============================
                                                // |                         |<=^
                                                // ============================== 
                                                // 
                                                // ===========================
                                                // |                         |
                                                // =========================== 
                                                // 最终使长度变为16的整数倍
      v3 = *end_pos;
      v4 = (*end_pos + 0x27) & 0xFFFFFFF0;
      *end_pos = v4;
      return memset((__m128i *)(v3 + buf), a3, v4 - v3);
    }
  }
  return result;
}
char __thiscall encrypt_telegram(struct_this *this, char *buf, unsigned int *end_pos, char flag)
{
  int nCrc; // edi
  unsigned int len; // esi
  unsigned int len_1; // eax
  struct_this *this_1; // [esp+10h] [ebp-Ch]
  char crc[4]; // [esp+14h] [ebp-8h] BYREF

  this_1 = this;
  init(crc);
  crc32((unsigned int *)crc, buf, *end_pos);
  nCrc = bitnegation(crc);
  getRandomkey(this, flag);
  len = *end_pos;
  expand_text((int)buf, end_pos, 0);
  len_1 = *end_pos;
  *(_DWORD *)&buf[len_1 - 4] = nCrc;            // crc
  *(_DWORD *)&buf[len_1 - 8] = len;             // 原长
  return cm_aes_encrypt_cbc(this_1, buf, end_pos, 0);
}

经过确认,这几个核心函数与德国威步CodeMeter通信协议中的漏洞分析中分析结果逻辑上相同,故可认为7.30a版本及之前通信协议的加密方法是相同的,可以进行复用

但应该指出的德国威步CodeMeter通信协议中的漏洞分析这篇文章只是分析了encrypt_telegram这个函数及其输入参数,并没有细究其中输入的数据与调用的操作之间的联系。我们最后要做到伪造服务端,这种程度是不够的,因此我们应该对send_cm_socket_req send_cm_req_encrypt这两个函数进行分析,并找出其中的内在规律。

(未完待续,不确定再开一贴还是直接往下写)

2022年12月7日更新 4楼

2022年12月24日更新 修改一些代码,增加可读性


[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2023-1-27 00:35 被ericyudatou编辑 ,原因: 2022年12月4日更新,修正拼写错误 2022年12月7日更新,修正代码,补图
收藏
点赞8
打赏
分享
最新回复 (10)
雪    币: 7215
活跃值: (3206)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
十年后 2022-12-3 09:05
2
0
分析的很到位!
雪    币: 1713
活跃值: (1507)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
sunbest 2022-12-6 20:10
3
0
期待后续...
雪    币: 3542
活跃值: (3519)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2022-12-7 18:39
4
0
期待后续...
雪    币: 4221
活跃值: (4067)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 2 2022-12-7 23:29
5
0

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

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

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 **)(int, int))(*(_DWORD *)function_table_006F8A58 + 4))(function_table_006F8A58, 101);// 未找到CodeMeter服务器, 错误 101.
        result = 0;
        break;
      case 4:
        v11 = get_function_table_006F8A58();
        (*(void (__thiscall **)(int, int))(*(_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 **)(int, int))(*(_DWORD *)v19 + 4))(v19, 0x65);// 未找到CodeMeter服务器, 错误 101
      break;
    case 4:
      v20 = get_function_table_006F8A58();
      (*(void (__thiscall **)(int, int))(*(_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编辑 ,原因:
上传的附件:
雪    币: 4221
活跃值: (4067)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 2 2022-12-25 00:10
6
0

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

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 int, char))(*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 int, int))(*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 *, int, int))(*this + 80))(this, a3, a2) )
      send(this[5], Default, 0, 0);
    if ( (*(unsigned __int8 (__thiscall **)(_DWORD *, int, int))(*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 *this, int 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(this, this[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 *, int, int *))(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的魔改,导出这些重要的函数以减少工作量,不用复现那些加解密算法

encrypt_telegram
decrypt_telegram

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

最后于 2022-12-28 15:33 被ericyudatou编辑 ,原因:
雪    币: 4221
活跃值: (4067)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 2 2022-12-28 16:06
7
0

0x11 伪造服务端

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

记于2022年12月28日:

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

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

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

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

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

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

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

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

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

struct PKG
{
	char APICODE;
        FUNCTION_ARGS
};
//举个例子 对于 CODEMETER_API HCMSysEntry CMAPIENTRY CmAccess2(CMULONG flCtrl, CMACCESS2 *pcmAcc);
//请求包的结构就是这样
struct PKG_CMACCESS2
{
	char APICODE;
	CMULONG flCtrl;
	CMACCESS2 pcmAcc;
};

举个例子

[*]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

上传的附件:
雪    币: 1049
活跃值: (559)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Kingh413 2022-12-29 10:26
8
0
学习了,感谢大佬的分享!
雪    币: 4221
活跃值: (4067)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 2 2022-12-31 17:44
9
0
雪    币: 319
活跃值: (2304)
能力值: ( LV12,RANK:980 )
在线值:
发帖
回帖
粉丝
csjwaman 24 2023-1-28 09:27
10
0
超强的耐心和耐力,钦佩楼主
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_wjxoadrg 2023-12-11 09:30
11
0
可以请教一下吗大佬,最近刚好在搞license这个项目,想问一下您有没有使用过WibuCmHIPApi这个库,我用的c++的库貌似出了点问题,谢谢!
游客
登录 | 注册 方可回帖
返回