一
准备
  1. CodeMeter.exe Win32 v7.30
  2. IDA Pro
二
初见——反调试

之前我说Codemeter的反调试很猛,我收回。Scylla Hide调至VMP档,运行后将爆出的几个错误全部Pass to the Application && Do not suspend or log即可。

三
初见——从何入手(通信协议部分)

有了前面分析的经验,感觉还是从通信协议入手比较好,顺便看看咱跟人家写的有什么差距。

根据前面的分析,我们知道了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",          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(https://bbs.pediy.com/thread-275395.htm)函数,以为轻松秒杀。但事情真的由这么简单吗?

在这个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!",          *(_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也与监听到的数据包相符。

简单的标注一下常用的API

三
初见——服务端的主要加密算法

1、CRC32

主要用于通信协议中的校验,和函数签名的校验

2、SHA1

仅用于getRandomkey,其余没有调用

3、SHA256

用于签名校验,配合ECDSA

4、AES

用于数据包的加解密,以及license文件的加解密

5、ECDSA

secp224r1{      p = 0xffffffffffffffffffffffffffffffff000000000000000000000001      a = 0xfffffffffffffffffffffffffffffffefffffffffffffffffffffffe      b = 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4      n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFF16A2E0B8F03E13DD29455C5C2A3D     Gx = 0xB70E0CBD6BB4BF7F321390B94A03C1D356C21122343280D6115C1D21     Gy = 0xBD376388B5F723FB4C22DFE6CD4375A05A07476444D5819985007E34}

ECDSA 是研究的重点,涉及License文件的验证。

四
CodeMeter所特有的ECDSA

吐槽一下,FindCrypt貌似不支持ECDSA算法。

其中我们主要关注checkECDSASignature ECDSAEncrypt ECDSASign这几个函数,因为ECDSA算法的原理及其实现论坛上一抓一大把,我这里只说说这些函数在CodeMeter中的用处以及定位方法。

checkECDSASignature

BOOL __cdecl checkECDSASignature(        char *ecdsa,        unsigned __int8 *pubKey,        unsigned __int8 *pbSignature,        unsigned __int8 *mabDigest){  BOOL result; // eax  void *curve_n; // [esp+10h] [ebp-A8h] BYREF  void *curve_G_y; // [esp+14h] [ebp-A4h] BYREF  void *curve_G_x; // [esp+18h] [ebp-A0h] BYREF  __int128 pSignature_2[2]; // [esp+1Ch] [ebp-9Ch] BYREF  char a2[16]; // [esp+3Ch] [ebp-7Ch] BYREF  __int128 v10; // [esp+4Ch] [ebp-6Ch]  int pSignature_1[7]; // [esp+5Ch] [ebp-5Ch] BYREF  int pubkey[4]; // [esp+78h] [ebp-40h] BYREF  __int128 v13; // [esp+88h] [ebp-30h]  char plaintext[4]; // [esp+98h] [ebp-20h] BYREF   result = sub_B1C960(ecdsa, (int)pubKey, 32);  if ( result )  {    bignum_copy((int)pSignature_1, pbSignature, 0x1Cu);    bignum_copy((int)pSignature_2, pbSignature + 32, 0x1Cu);    bignum_copy((int)plaintext, mabDigest, 0x1Cu);    get_ecdsa_curve(&curve_n, &curve_G_x, &curve_G_y);    result = bignum_compare_int((int)pSignature_1, (int)curve_n, 7);    if ( !result )    {      result = bignum_compare_int((int)pSignature_1, 0, 7);      if ( result != 3 )      {        result = bignum_compare_int((int)pSignature_2, (int)curve_n, 7);        if ( !result )        {          result = bignum_compare_int((int)pSignature_2, 0, 7);          if ( result != 3 )          {            sub_B1FBE0(pSignature_2, (int)a2, curve_n, 7);            sub_B1FE90((int)plaintext, (int)a2, curve_n, 7);            sub_B1FE90((int)a2, (int)pSignature_1, curve_n, 7);            bignum_copy((int)pubkey, pubKey, 0x1Cu);            bignum_copy((int)pSignature_2, pubKey + 32, 0x1Cu);            result = init_ecdsa_curve_G(ecdsa, a2, 7, pubkey, pSignature_2);            if ( result )            {              *(_OWORD *)a2 = *(_OWORD *)ecdsa;              v10 = *((_OWORD *)ecdsa + 1);              pSignature_2[0] = *((_OWORD *)ecdsa + 2);              pSignature_2[1] = *((_OWORD *)ecdsa + 3);              *(_OWORD *)pubkey = *((_OWORD *)ecdsa + 4);              v13 = *((_OWORD *)ecdsa + 5);              result = init_ecdsa_curve_G(ecdsa, plaintext, 7, curve_G_x, curve_G_y);              if ( result )              {                result = sub_B1BC30(ecdsa, a2, (int)pSignature_2, (int)pubkey);                if ( result )                {                  result = bignum_compare_int((int)(ecdsa + 64), 0, 7);                  if ( result != 3 )                  {                    init_ecdsa_curve_p(ecdsa);                    result = bignum_compare_int((int)ecdsa, (int)pSignature_1, 7);                    if ( result == 3 )                      return 1;                  }                }              }            }          }        }      }    }  }  return 0;}

用处:

client CmValiateSignature验证签名

server License授权校验 Licensor Public Key检查 etc

破解方法:

函数尾xor eax,eax改成mov al,1 就能爆掉证书检查

定位方法:

1、搜索字符串 “Licensor Public Key Signature is invalid.”,串参找引用,只有一个函数。

2、这个便是checkECDSASignature。