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:

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

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, andCmWebAdmin.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:
- Generate a time-related random key (
getTickCount -> SHA1 -> randomkey). - CRC32 the original message and extend the message with it.
- 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