← PC Reverse Engineering

CodeMeter 7.3 Study Notes: The CodeMeter API and Communication Protocol

About the author. I’m Colin Xiao — an independent reverse engineer, mostly working solo on contract jobs. I dig into compiled code of every kind, including but not limited to: Android and iOS apps, x86/x64/ARM native binaries, packers and obfuscators, DRM and license checks, network protocols and APIs, crypto and signature algorithms, malware, game binaries, and embedded/IoT firmware. I also handle large-scale data collection and vulnerability research. If you have any project in mind, I’d be glad to take it on — whatever it is, as long as it’s within my wheelhouse. Just reach out. You can reach me on X at @colinxiao_lab or on Telegram at https://t.me/ColinXiao666.

This research is published for educational and defensive purposes. The target sample is anonymized throughout.


CodeMeter 7.3 Study Notes: The CodeMeter API and Communication Protocol

0x00 Foreword

Wibu CodeMeter is an excellent software-protection/licensing system, but it’s under-discussed compared to its difficulty. These are my study notes; corrections are welcome. A caveat up front: because there’s never been a leak of the CodeMeter source or PDBs, my function names are guesswork — bear with me.

0x01 The samples

CodeMeter SDK (AxProtector) 7.3. The two protected targets I worked from are an app I’ll call Ap**s and another called Gas*****.

For tooling, IDA Pro with FindCrypt and ScyllaHide. Configure ScyllaHide as below and it defeats all the anti-debug / anti-trace tricks:

ScyllaHide options

IDA throws a bunch of exceptions before debugging starts — set them to “No Suspend / Pass to application” and let them pass silently. Don’t try to debug at this stage or you’ll fly off the rails:

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 Initial analysis

Architecture overview

CodeMeter’s overall architecture is a server–client licensing system, similar to FlexLM and RLM — but cracking-wise it’s an order of magnitude harder.

  • Server: Codemeter.exe (the core — it manages CmContainer, i.e. license authorization; this is the main target of study), CodemeterCC.exe, and CmWebAdmin.exe (a GUI, not important).
  • Client: wibucm32.dll / wibucm64.dll — the bridge between non-AxProtector-protected user software and the server; these can be defeated via aheadlib-style attacks. AxProtector-protected software talks to the server directly.

For Java/.NET/Python, calls go indirectly through WibuCmNET.dll, CodeMeter.jar, etc., into wibucm32/64.dll — same idea, not worth belaboring.

Here’s an authorization snippet pulled from Ap**s; the binary calls functions in wibucm64.dll to perform authorization:

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);// get the pointer to the license; 0 on failure/not found
    hcmse_1 = hcmse;
    if ( hcmse )// check 1: this license must exist
    {
      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) )// get the license's user-defined data
        {
          if ( (int)v18 > 0 )
          {
            secret_data = entrydata->mabData;
            do
            {
              v24 = *((_DWORD *)secret_data - 4);
              if ( (_WORD)v24 == 64 && (v24 & 0xFFFF0000) == 0x10000 )  // check 2: vendor's custom algorithm
              {
                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;
          }

From this snippet and Wibu’s official docs, the key insight: at checkout time CodeMeter only asks the server whether a matching license exists — the client does no validation on its own; anything extra is the vendor’s custom algorithm. So the functions to focus on are CmAccess and CmGetInfo.

0x03 Marching toward CmAccess

WibuCm32/64.dll is unpacked, which makes analysis pleasant. The exported CmAccess passes through a few wrapper layers to the real work. (All CodeMeter API functions, types, and structs can be found in CodeMeter/Devkit/include/CodeMeter.h, which lightens the load.)

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 validate the input params
                                                // invalid memory address given, error 113.
    return 0;
  if ( !cmacc )
  {
LABEL_5:
    (*(void (__thiscall **)(int, int))(*(_DWORD *)calltbl_1 + 4))(calltbl_1, 105);// invalid parameter, error 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 server not found, error 101.
      return 0;
    }
    isCMACCESS2_1 = isCMACCESS2;
  }
  v22 = 0;
  while ( 1 )
  {
    if ( isCMACCESS2_1 )
    {
      hcmse_1 = cmaccesss2((_BYTE *)calltbl_1, flCtrl_1, cmacc_1);// cmaccess2 — the important one
      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); // the important one
      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 client/server handle mismatch, error 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);// connected CodeMeter server too old, error 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 process
  CMACC->mcmCredential.mulPID = getpid(v4);
  v5 = (_BYTE *)sub_553BC0();
  if ( sub_554C80(v5) )                         // is it the Wine emulator?
    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);// invalid parameter, error 105.
    hcmse = 0;
  }
  buf[0] = (int)&YS0062::`vftable';
  sub_56E140(buf);
  return hcmse;
}

Reading through, cm_access is basically parameter validation, version checks, and error handling — trivial stuff. The important parameter, cmacc, ends up passed into send_cm_socket_req.

Looking at send_cm_socket_req’s cross-references, it’s entangled with essentially every 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)

So it’s safe to conclude authorization happens on the server (codemeter.exe); wibucm32/64 is just the errand boy.

0x04 First look at 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();                            // multithreading-related
  err_4 = sub_554DF0(v7, 0);                    // should return 1; CodeMeter server detection — if not running, suspend the service
  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 server not found, error 101.
        result = 0;
        break;
      case 4:
        v11 = get_function_table_006F8A58();
        (*(void (__thiscall **)(int, int))(*(_DWORD *)v11 + 4))(v11, 308);// data cannot be written to the secure area, error 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 return 0; encrypt the packet and send
    err_1 = err;
    if ( err && buf_1[2] == 238 )               // CodeMeter license server startup still pending, error 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 license server overloaded; request timed out and was rejected, error 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) )// cannot send the request to another CodeMeter server, error 102.
                                                // CodeMeter server not found, error 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;                                    // timeout detection (network, anti-debugging)
    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 server not found, error 101
      break;
    case 4:
      v20 = get_function_table_006F8A58();
      (*(void (__thiscall **)(int, int))(*(_DWORD *)v20 + 4))(v20, 0x134);// data cannot be written to the secure area, error 308.
      break;
    default:
      break;
  }
LABEL_32:
  sub_4FE380(v28);
  return err_1;
}

send_cm_socket_req doesn’t itself do the encryption or sending — apart from connecting to the server and some housekeeping, it hands everything to send_cm_req_encrypt.

0x05 “CodeMeter has never been cracked”… right?

Analyzing axprotector.exe (itself protected by AxProtector): even though the original code is heavily obfuscated, the shell body itself isn’t VM-protected. On a hunch I ran a BinDiff comparison — and what do you know, nearly all the CM APIs are present, including the coveted CmAccess and CmGetInfo, byte-for-byte identical to wibucm32. It still talks to the server via send_cm_socket_req and just waits for the server’s yes/no before decrypting.

So here’s a line of thinking, open to correction:

For Wibu AxProtector, rather than facing down the anti-debug / anti-dump tricks head-on, what if we reverse the protocol between the shell and the server (and between wibucm32/64.dll and the server) and stand up a fake server, making the shell believe it received a trusted authorization?

This approach has precedent: a well-known scene tool fakes wibucm32/64.dll to attack DLL-based licensing systems like Ap**s, and it demonstrably works.

0x06 The communication protocol (the rabbit hole)

Prior research has already laid out the private protocol between the CodeMeter server and client:

  1. Generate a time-related random key (getTickCount -> SHA1 -> randomkey).
  2. CRC32 the original message and extend the message with it.
  3. AES-128-CBC encrypt the extended message; key and IV are split out of the randomkey.

One of those writeups provided the implementation of encrypt_telegram, saving a lot of work.

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)
                                                //
                                                // ==============================
                                                // |                         |<=^
                                                // ==============================
                                                //
                                                // ===========================
                                                // |                         |
                                                // ===========================
                                                // ultimately makes the length a multiple of 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;             // original length
  return cm_aes_encrypt_cbc(this_1, buf, end_pos, 0);
}

After verifying, these core functions match the prior analysis logically, so the 7.30a-and-earlier communication protocol uses the same encryption and can be reused.

One gap worth noting: that prior writeup only analyzed encrypt_telegram and its inputs — it didn’t dig into the relationship between the input data and the operations invoked. If the end goal is a fake server, that’s not enough. So the next step is to analyze send_cm_socket_req and send_cm_req_encrypt and find their inner logic.

(To be continued — not sure whether to start a new post or continue here.)


Colin Xiao — I take on contract reverse-engineering work, and writeups like this are how I show what I can do. If you need help with anything in this space — including but not limited to the areas I cover above — I’m available. No project is too big or too small — if it’s reverse engineering and I can do it, I’ll take it. DM me on X (@colinxiao_lab) or Telegram (@ColinXiao666).

Get in touch