0
期待后续...
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
最后于 1天前 被ericyudatou编辑 ,原因:
0
0x09 通信协议初探
Codemeter在通信上除了使用一个私有的算法进行加密并与系统时间挂钩以外并没有别的门槛,只是一个简单的winsock通信框架,Wireshark能抓包。这里观察一下通信上的行为。
可以发现端口50777的客户端与端口22350的Codemeter服务器间的通信行为:
客户端对服务端的通信行为可以概括如下
首先发送一个长度为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};
}
发送一个长度为length的加密包package02,内容全部为加密后的数据,没有长度,magic等信息
等待服务器回话
但服务端对客户端的通信行为却有点不同
服务器对客户端倒像一个高冷御姐,没有了单独的握手包,但是却把握手包与数据杂合到一块。
服务器返回的数据包前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 [email protected]@[email protected] 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 [email protected]<al>( _DWORD *[email protected]<ecx>, int [email protected]<ebx>, int [email protected]<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的魔改,导出这些重要的函数以减少工作量,不用复现那些加解密算法
expand_plaintext encrypt_telegram decrypt_telegram reallocateMem memset memmove allocmem bitnegation init_crc crc32 sha1_init sha1_update sha1_final cm_aes_decrypt_cbc getRandomkey ...
最后于 1天前 被ericyudatou编辑 ,原因: