看完了回个贴呗,你们的关注是我更新的动力
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); }
这不由得让我们联想到send_cm_socket_req函数,以为轻松秒杀。但事情真的由这么简单吗?在这个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++答辩一样的虚函数,所以只能下断运行。
发现*this+0x24便是已经解密了的客户端请求包,这个函数并非CmAccess2的Entry,而是CmAccess的Entry,这个函数将CmAccess转发到CmAccess2。返回看看是哪个小可爱调用的她
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)
[2023春季班]《安卓高级研修班(网课)》月薪两万班招生中~
最后于 2023-1-2 11:50 被ericyudatou编辑 ,原因: