看完了回个贴呗,你们的关注是我更新的动力
0x00 准备
CodeMeter.exe Win32 v7.30
IDA Pro
0x01 初见——反调试
之前我说Codemeter的反调试很猛,我收回。Scylla Hide调至VMP档,运行后将爆出的几个错误全部Pass to the Application && Do not suspend or log即可
0x02 初见——从何入手(通信协议部分)
有了前面分析的经验,感觉还是从通信协议入手比较好,顺便看看咱跟人家写的有什么差距。根据前面的分析,我们知道了Codemeter私有协议中最终要的两个函数就是encrypt_telegram和decrypt_telegram。服务器接受到客户端的请求那就必定调用decrypt_telegram解密,向客户端回复数据必定通过encrypt_telegram解密。因此我们第一步就需要确定这两个函数。
用Bindiff就可以轻而易举的解决这个问题。
先对decrypt_telegram下断,跑起来看看谁调用她。然后我们就轻而易举的找的了这个函数
char __thiscall decrypt_package(void *this, int a2, unsigned __int8 a3, int pbuf, _BYTE *plen, int flag)
{
char v6; // dl
char result; // al
v6 = 1;
if ( a3 == 160 )
{
if ( (*plen & 0xF) != 0 ) // len 应当是16的倍数
return 0;
return (*(int (__fastcall **)(void *, char, int, _BYTE *, int))(*(_DWORD *)this + 16))(this, 1, pbuf, plen, flag);// decrypt_telegram
}
else if ( a3 == 161 || a3 == 163 )
{
if ( (*plen & 0xF) == 0 )
{
result = (*(int (__fastcall **)(int, char, int, _BYTE *, _DWORD))(*(_DWORD *)a2 + 16))(a2, 1, pbuf, plen, 0);
*(_DWORD *)plen = *(_DWORD *)(*(_DWORD *)plen + pbuf - 4);
return result;
}
return 0;
}
return v6;
}
对encrypt_telegram下断,断在这里
char __thiscall encrypt_package(struct_communication *this, int a2, unsigned __int8 a3, int buf, int *length, int flag)
{
char result; // al
int v8; // ecx
int v9; // eax
char v10; // bl
int v11; // [esp+10h] [ebp-18h] BYREF
int v12[5]; // [esp+14h] [ebp-14h] BYREF
v12[0] = a2;
result = 1;
if ( a3 == 160 )
{
v12[0] = 0;
sub_219A20(dword_800FC4 + 268);
v9 = *(_DWORD *)this->gap0;
v12[4] = 0;
v10 = (*(int (__thiscall **)(struct_communication *, int, int *, int))(v9 + 12))(this, buf, length, flag);// encrypt_telegram
sub_219A90(v12);
return v10;
}
else if ( a3 == 161 || a3 == 163 )
{
v11 = *length;
sub_3578E0(buf, length, 0);
v8 = *length;
*(_DWORD *)(buf + v8 - 8) = v11;
v11 = v8 - 4;
return (*(int (__thiscall **)(int, int, int *, _DWORD))(*(_DWORD *)v12[0] + 12))(v12[0], buf, &v11, 0);
}
return result;
}
查看decrypt_package和encrypt_package的交叉引用,有一个函数同时引用这两个函数
void __thiscall cm_client_encrypt_req(
_DWORD *this,
_DWORD *message_buf,
unsigned __int8 a3,
unsigned int final_len,
unsigned int a5)
{
int v6; // ecx
unsigned int v7; // eax
size_t v8; // esi
void *v9; // eax
size_t v10; // ecx
size_t v11; // eax
size_t v12; // esi
_BYTE *buf_1; // esi
int v14; // ecx
int v15; // ecx
size_t v16; // edx
int v17; // eax
int v18; // ecx
char v19; // al
int v20; // ecx
int v21; // ecx
_DWORD *v22; // ecx
unsigned int v23; // edx
_BYTE *v24; // eax
void *p_Block; // edx
_DWORD *v26; // eax
_DWORD *v27; // esi
void *v28; // eax
int v29; // eax
int v30; // eax
int v31; // eax
int v32; // eax
int v33; // eax
int v34; // eax
int v35; // [esp-10h] [ebp-298h]
int v36; // [esp-10h] [ebp-298h]
int v37; // [esp-Ch] [ebp-294h]
int v38; // [esp-Ch] [ebp-294h]
int v39; // [esp-8h] [ebp-290h]
int v40; // [esp-8h] [ebp-290h]
int v41; // [esp-4h] [ebp-28Ch]
int v42; // [esp-4h] [ebp-28Ch]
int v43; // [esp-4h] [ebp-28Ch]
int v44; // [esp-4h] [ebp-28Ch]
int v45; // [esp+0h] [ebp-288h]
char pExceptionObject[140]; // [esp+Ch] [ebp-27Ch] BYREF
char *v47; // [esp+98h] [ebp-1F0h]
int buf; // [esp+9Ch] [ebp-1ECh]
unsigned int v49; // [esp+A4h] [ebp-1E4h]
int v50; // [esp+A8h] [ebp-1E0h]
size_t v51; // [esp+ACh] [ebp-1DCh]
_DWORD *v52; // [esp+B0h] [ebp-1D8h]
char v53; // [esp+B7h] [ebp-1D1h]
char v54[160]; // [esp+B8h] [ebp-1D0h] BYREF
char v55[160]; // [esp+158h] [ebp-130h] BYREF
void **v56; // [esp+1F8h] [ebp-90h] BYREF
__int128 v57; // [esp+1FCh] [ebp-8Ch]
__int128 v58; // [esp+20Ch] [ebp-7Ch]
int v59; // [esp+21Ch] [ebp-6Ch]
int v60; // [esp+220h] [ebp-68h] BYREF
void **v61; // [esp+224h] [ebp-64h] BYREF
void *Block; // [esp+228h] [ebp-60h] BYREF
int v63; // [esp+238h] [ebp-50h]
unsigned int v64; // [esp+23Ch] [ebp-4Ch]
size_t Size[4]; // [esp+240h] [ebp-48h] BYREF
int v66; // [esp+250h] [ebp-38h]
__int128 v67; // [esp+254h] [ebp-34h]
int v68; // [esp+264h] [ebp-24h] BYREF
int v69; // [esp+268h] [ebp-20h] BYREF
unsigned int len_1; // [esp+26Ch] [ebp-1Ch] BYREF
int len; // [esp+270h] [ebp-18h] BYREF
char v72; // [esp+276h] [ebp-12h] BYREF
char v73; // [esp+277h] [ebp-11h] BYREF
int v74; // [esp+284h] [ebp-4h]
v52 = message_buf;
v57 = 0i64;
v56 = &YS0076::YS0306::`vftable';
v58 = 0i64;
v59 = 0;
v74 = 0;
sub_EB4620(v55);
v6 = this[63];
LOBYTE(v74) = 1;
(*(void (__thiscall **)(int, char *))(*(_DWORD *)v6 + 64))(v6, v55);
sub_ECA0E0(v55);
sub_ECA3F0(v41);
v47 = v55;
v7 = a5;
HIDWORD(v67) = 0;
if ( a5 < 0x1000 )
v7 = 4096;
*(_OWORD *)Size = 0i64;
if ( final_len > v7 )
v7 = final_len;
Size[0] = (size_t)&YS0073::YS0080<unsigned char>::`vftable';
memset(&Size[1], 0, 12);
v66 = 1;
v49 = ((v7 + 39) & 0xFFFFFFF0) + 1;
v8 = ((v7 + 39) & 0xFFFFFFF0) + 17;
v51 = v8;
v67 = xmmword_126CA30;
LOBYTE(v74) = 3;
v9 = (void *)unknown_libname_56(v8);
Size[3] = v8;
v10 = (size_t)v9;
Size[1] = (size_t)v9;
Size[2] = v8;
if ( (_DWORD)v67 == 1 )
{
memset(v9, 0, v8);
v11 = Size[2];
v10 = Size[1];
}
else
{
v11 = v51;
}
v12 = 0;
LOBYTE(v74) = 4;
if ( v11 )
v12 = v10;
buf_1 = (_BYTE *)(v12 + 15);
if ( a3 == 0xA2 )
(*(void (__thiscall **)(_DWORD))(*(_DWORD *)this[63] + 20))(this[63]);
v51 = 0;
v53 = 1;
while ( 1 )
{
if ( !(*(unsigned __int8 (__thiscall **)(_DWORD))(*(_DWORD *)this[63] + 84))(this[63]) )
{
sub_EB4620(v54);
v14 = this[63];
LOBYTE(v74) = 5;
(*(void (__thiscall **)(int, char *))(*(_DWORD *)v14 + 64))(v14, v54);
v15 = this[63];
LOBYTE(v50) = a3 != 0xA2;
(*(void (__thiscall **)(int))(*(_DWORD *)v15 + 100))(v15);
sub_DB90E0(this, v54, 3500, v50, 1);
if ( !(*(unsigned __int8 (__thiscall **)(_DWORD))(*(_DWORD *)this[63] + 84))(this[63]) )
{
v32 = sub_D6E030(v45);
v33 = sub_D6E030(v32);
v34 = sub_D6E030(v33);
v36 = sub_D6E030(v34);
sub_D70090(100, v36, v38, v40, v44);
goto LABEL_70;
}
LOBYTE(v74) = 4;
sub_EB4790(v54);
}
v69 = 0;
sub_D79A20(this + 1);
v16 = 0;
len = final_len;
if ( Size[2] )
v16 = Size[1];
len_1 = v49;
LOBYTE(v74) = 6;
v17 = *v52;
buf = v16 + 16;
if ( !(*(unsigned __int8 (__stdcall **)(size_t, int *))(v17 + 4))(v16 + 16, &len) || len != final_len )
{
v52[2] = 100;
LABEL_67:
LOBYTE(v74) = 4;
sub_D79A90(&v69);
LABEL_68:
v29 = sub_D6E030(v45);
v30 = sub_D6E030(v29);
v31 = sub_D6E030(v30);
v35 = sub_D6E030(v31);
sub_D70090(v52[2], v35, v37, v39, v43);
LABEL_70:
_CxxThrowException(pExceptionObject, (_ThrowInfo *)&_TI2_AVException_wbs__);
}
if ( !encrypt_package((int *)&v56, (int)(this + 2), a3, buf, &len, 0) )
{
v52[2] = 302;
goto LABEL_67;
}
++len;
*buf_1 = a3;
v68 = 0;
v63 = 0;
v64 = 15;
LOBYTE(Block) = 0;
v61 = &wbs::StringBase<char>::`vftable';
v18 = this[63];
LOBYTE(v74) = 7;
v19 = (*(int (__thiscall **)(int, _BYTE *, int, _DWORD, int *, void ***))(*(_DWORD *)v18 + 28))(
v18,
buf_1,
len,
0,
&v68,
&v61);
if ( v19 == 1 )
{
if ( len )
{
*(_QWORD *)(dword_1360FC4 + 520) += (unsigned int)len;
++*(_DWORD *)(dword_1360FC4 + 528);
}
}
else if ( !v19 )
{
if ( v68 )
{
p_Block = &Block;
if ( v64 >= 0x10 )
p_Block = Block;
(*(void (**)(int, const char *, ...))(*(_DWORD *)dword_13610C4 + 4))(
dword_13610C4,
"HTTP ERROR %i: %s\n",
v68,
p_Block);
}
v52[2] = 102;
if ( (*(unsigned __int8 (__thiscall **)(_DWORD))(*(_DWORD *)this[63] + 84))(this[63]) )
(*(void (__thiscall **)(_DWORD, int))(*(_DWORD *)this[63] + 16))(this[63], 1);
v61 = &wbs::StringBase<char>::`vftable';
if ( v64 < 0x10 )
goto LABEL_38;
v22 = Block;
v23 = v64 + 1;
v24 = Block;
LOBYTE(v74) = 8;
goto LABEL_35;
}
sub_EB82A0(v52);
v20 = this[63];
v73 = 0;
v72 = 0;
if ( (*(unsigned __int8 (__thiscall **)(int, size_t *, unsigned int *, char *, char *))(*(_DWORD *)v20 + 40))(
v20,
Size,
&len_1,
&v73,
&v72) )
{
break;
}
LABEL_31:
v52[2] = 103;
if ( (*(unsigned __int8 (__thiscall **)(_DWORD))(*(_DWORD *)this[63] + 84))(this[63]) )
(*(void (__thiscall **)(_DWORD, int))(*(_DWORD *)this[63] + 16))(this[63], 1);
v61 = &wbs::StringBase<char>::`vftable';
if ( v64 < 0x10 )
goto LABEL_38;
v22 = Block;
v23 = v64 + 1;
v24 = Block;
LOBYTE(v74) = 9;
LABEL_35:
if ( v23 >= 0x1000 )
{
v22 = (_DWORD *)*(v22 - 1);
if ( (unsigned int)(v24 - (_BYTE *)v22 - 4) > 0x1F )
_invalid_parameter_noinfo_noreturn();
}
sub_F17EF9(v22);
LABEL_38:
LOBYTE(v74) = 4;
LOBYTE(Block) = 0;
v64 = 15;
v63 = 0;
sub_D79A90(&v69);
if ( (int)++v51 >= 2 )
goto LABEL_68;
}
while ( 1 )
{
if ( (int)len_1 <= 0 )
goto LABEL_31;
buf_1 = 0;
if ( Size[2] )
buf_1 = (_BYTE *)Size[1];
*(_QWORD *)(dword_1360FC4 + 504) += len_1;
++*(_DWORD *)(dword_1360FC4 + 512);
if ( !decrypt_package(&v56, (int)(this + 2), a3, (int)buf_1, &len_1, (v73 & 0xF0) == 112) )
goto LABEL_64;
v60 = 0;
if ( !(unsigned __int8)sub_EB8AC0(buf_1, len_1, &v60) )
break;
v21 = this[63];
v73 = 0;
v72 = 0;
if ( !(*(unsigned __int8 (__thiscall **)(int, size_t *, unsigned int *, char *, char *))(*(_DWORD *)v21 + 40))(
v21,
Size,
&len_1,
&v73,
&v72) )
goto LABEL_31;
}
if ( len_1 > a5 )
{
LABEL_64:
v52[2] = 302;
sub_D63570(&v61);
goto LABEL_67;
}
if ( !(*(unsigned __int8 (__thiscall **)(_DWORD *, _BYTE *, unsigned int))(*v52 + 8))(v52, buf_1, len_1) )
{
v53 = 0;
v52[2] = 100;
}
sub_D63570(&v61);
LOBYTE(v74) = 4;
sub_D79A90(&v69);
if ( v53 != 1 )
goto LABEL_68;
v26 = (_DWORD *)DWORD2(v67);
v27 = (_DWORD *)DWORD1(v67);
LOBYTE(v74) = 10;
for ( Size[0] = (size_t)&YS0073::YS0080<unsigned char>::`vftable'; v27 != v26; ++v27 )
{
if ( *v27 )
{
(*(void (__thiscall **)(_DWORD, _DWORD))(*(_DWORD *)*v27 + 4))(*v27, 0);
v26 = (_DWORD *)DWORD2(v67);
}
}
if ( (_BYTE)v66 )
{
v28 = (void *)Size[1];
if ( Size[1] )
{
if ( (_DWORD)v67 == 1 )
{
memset((void *)Size[1], 0, Size[2]);
v28 = (void *)Size[1];
}
j_j__free(v28);
}
memset(&Size[1], 0, 12);
LOBYTE(v66) = 1;
}
sub_D6CCA0();
LOBYTE(v74) = 11;
sub_ECA0E0(v55);
sub_ECA6B0(v42);
sub_EB4790(v55);
}
这不由得让我们联想到函数,以为轻松秒杀。但事情真的由这么简单吗?在这个cm_client_encrypt_req中是先调用encrypt_package加密数据包再发送、接收,最后在decrypt_package。这显然与我们对Codemeter服务器加解密逻辑相违背,服务器应当先接受客户端得请求、解密、进行处理之后再加密数据包发送给客户端。因为cm_client_encrypt_req在运行中不会被调用,我怀疑这只是未被移除的测试代码,但cm_client_encrypt_req却是一块敲门砖,观察她得交叉引用
引用不少,凭借直觉以及一眼丁真的分析,大概所有得codemeter api都得跟她有一腿。看看她是如何被调用的
(api_cm_access_2)
这调用方式不由得让我们联想起她同父同母的亲兄妹send_cm_socket_req,*buf+36便是API code(此处 100对应CmAccess2)。但不必花太长时间重命名一下函数,这只是一块敲门砖,用完就扔,我们就重点分析一下api_cm_access_2。
看一下对api_cm_access_2的引用,因为这里只是阐述分析Codemeter API在服务器上的实现,所以就不深入探讨这些函数的具体意义,今后有需要再细说
这堆函数就是CmAccess2在服务器上的实现,假设存在一个API switch,通过客户端加密请求数据包中的API code来调用API。那么这个API swicth一定会调用这个树状图最上头的节点来传递参数。逐一排查,发现一个有意思的函数
void __thiscall api_cm_access_2_entry(int this)
{
CMTIME *v2; // eax
char v3; // cl
CMTIME v4; // xmm0
char v5[16]; // [esp+4h] [ebp-2D4h] BYREF
CMACCESS2 cmacc; // [esp+14h] [ebp-2C4h] BYREF
cmacc.mulReserved1 = 0;
cmacc.mulReserved2 = 0;
memset(&cmacc.mulLicenseQuantity, 0, 0x88u);
memset(cmacc.mabCmActId, 0, 0x110u);
memset(&cmacc.mcmCredential.mulCreationTime, 0, 0xE0u);
cmacc.mflCtrl = *(_DWORD *)(this + 44);
cmacc.mulFirmCode = *(_DWORD *)(this + 48);
cmacc.mulProductCode = *(_DWORD *)(this + 52);
cmacc.mulFeatureCode = *(_DWORD *)(this + 56);
v2 = (CMTIME *)getCMTime(v5, 0);
v3 = *(_BYTE *)(this + 72);
v4 = *v2;
cmacc.mulUsedRuntimeVersion = *(_DWORD *)(this + 60);
cmacc.mulProductItemReference = *(unsigned __int16 *)(this + 68);
cmacc.mbMinBoxMajorVersion = *(_BYTE *)(this + 76);
cmacc.mbMinBoxMinorVersion = *(_BYTE *)(this + 77);
cmacc.musBoxMask = *(_WORD *)(this + 78);
cmacc.mulSerialNumber = *(_DWORD *)(this + 80);
cmacc.mcmCredential.mulPID = *(_DWORD *)(this + 64);
cmacc.mcmCredential.mulSession = *(unsigned __int16 *)(this + 70);
cmacc.mcmCredential.mulCleanupTime = 0;
cmacc.mcmCredential.mulMaxLifeTime = 0;
cmacc.mcmReleaseDate = v4;
if ( v3 || *(_BYTE *)(this + 73) || *(_BYTE *)(this + 74) || *(_BYTE *)(this + 75) )
sub_D9EB30(cmacc.mszServername, 0x80u, 0x80u, "%i.%i.%i.%i", v3);
if ( !*(_BYTE *)(dword_1360FC4 + 760) && (cmacc.mflCtrl & 0x200000) != 0 )
*(_DWORD *)(this + 20) = 0x80000000;
api_cm_access_2__3(
*(_DWORD *)(this + 40),
&cmacc,
(int *)(this + 220),
(_DWORD *)(this + 8),
*(void ***)(this + 16),
0,
*(_DWORD *)(this + 28));
}
这一眼就看得出这个函数不一般,因为C++答辩一样的虚函数,所以只能下断运行。
int __thiscall api_handler(_DWORD *this)
{
int v2; // eax
char v3; // al
int api_class; // ecx
int v5; // ecx
char v6; // dl
int v7; // ecx
int v8; // esi
int v9; // eax
bool v10; // zf
_DWORD v12[8]; // [esp+0h] [ebp-38h] BYREF
char v13; // [esp+23h] [ebp-15h] BYREF
_DWORD *v14; // [esp+28h] [ebp-10h]
int v15; // [esp+34h] [ebp-4h]
v14 = v12;
v12[7] = this;
v13 = 0;
v12[6] = &v13;
v15 = 0;
do
{
if ( (*(unsigned __int8 (__thiscall **)(int))(*(_DWORD *)((char *)this + *(_DWORD *)(*this + 4)) + 32))((int)this + *(_DWORD *)(*this + 4)) )
{
*(_DWORD *)(this[2] + 8) = 0xD0010003;
this[1] = 0;
v15 = 1;
goto LABEL_17;
}
LOBYTE(v15) = 2;
v2 = _Mtx_trylock((_Mtx_t)(dword_1360FC4 + 220));
if ( v2 )
{
if ( v2 != 3 )
std::_Throw_C_error(v2);
v3 = 0;
}
else
{
v3 = 1;
}
LOBYTE(v15) = 0;
}
while ( !v3 );
v13 = 1;
if ( !*(_BYTE *)(dword_1360FC4 + 150) )
{
*(_DWORD *)(this[2] + 8) = 238;
this[1] = 0;
v15 = 3;
LABEL_17:
v10 = v13 == 0;
goto LABEL_18;
}
api_class = this[2];
LOBYTE(v15) = 4;
(*(void (__thiscall **)(int))(*(_DWORD *)api_class + 16))(api_class);// <==============call api
v15 = 0;
_Mtx_unlock((_Mtx_t)(dword_1360FC4 + 220));
v5 = this[2];
v6 = 0;
v13 = 0;
if ( *(int *)(v5 + 20) >= 0 )
{
v7 = *(_DWORD *)(v5 + 8);
if ( v7 )
{
if ( v7 != 112 && v7 != 209 )
{
v8 = *(_DWORD *)dword_13610C4;
v9 = sub_D8CD00(v7);
(*(void (**)(int, const char *, ...))(v8 + 4))(
dword_13610C4,
"API Error %u (%s) occurred!\n",
*(_DWORD *)(this[2] + 8),
v9);
v6 = v13;
}
}
}
this[1] = 0;
v15 = 6;
v10 = v6 == 0;
LABEL_18:
if ( !v10 )
_Mtx_unlock((_Mtx_t)(dword_1360FC4 + 220));
return 0;
}
在此处下断,看看调用其他CodeMeter API的反应
(*(void (__thiscall **)(int))(*(_DWORD *)api_class + 16))(api_class);
也能断下,*this+0x24也与监听到的数据包相符。
(TO BE CONTINUE)