首页
论坛
课程
招聘
[原创]Wibu Codemeter 7.3学习笔记——Codemeter服务端
2022-12-31 23:58 9417

[原创]Wibu Codemeter 7.3学习笔记——Codemeter服务端

2022-12-31 23:58
9417

看完了回个贴呗,你们的关注是我更新的动力

0x00 准备

  1. CodeMeter.exe Win32 v7.30

  2. 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编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (13)
雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-2 00:58
2
0

简单的标注一下常用的API

雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-2 09:34
3
0

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

  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文件的验证

最后于 2023-1-3 14:02 被ericyudatou编辑 ,原因:
雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-3 16:50
4
0

0x03 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

(未完待续)

最后于 2023-1-3 16:50 被ericyudatou编辑 ,原因:
雪    币: 176
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lxr677 活跃值 2023-1-3 17:56
5
0
license使用时间过期的能爆破么
雪    币: 12
活跃值: 活跃值 (150)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
whshizy 活跃值 2023-1-5 22:41
6
0
感谢大佬的分享!文章还更新吗?
雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-5 23:43
7
0
whshizy 感谢大佬的分享!文章还更新吗?
更,看着答辩一样的代码快吐了,缓两天
雪    币: 1233
活跃值: 活跃值 (1387)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 活跃值 2023-1-6 14:33
8
0
大佬什么时候研究一下SafeNet Sentinel LDK的证书授权
雪    币: 176
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lxr677 活跃值 2023-1-14 21:59
9
0
ericyudatou 0x03 CodeMeter所特有的ECDSA吐槽一下,FindCrypt貌似不支持ECDSA算法。其中我们主要关注checkECDSASignature&nbsp;ECDSAEncrypt& ...
最新版本7.5和7.3差异挺大,Licensor Public Key Signature is invalid. 字符串都找不到
雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-14 23:11
10
0
lxr677 最新版本7.5和7.3差异挺大,Licensor Public Key Signature is invalid. 字符串都找不到
可以试试搜截图里头的常数
雪    币: 176
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
lxr677 活跃值 2023-1-22 11:33
11
0
ericyudatou 可以试试搜截图里头的常数
最新版本那些常数都已经搜不到了,我看变动还挺大的,以前能找到的导入license的那些读取系统变量的也都找不到了,大概率重写了
雪    币: 4213
活跃值: 活跃值 (3234)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
ericyudatou 活跃值 2 2023-1-26 23:16
12
0
lxr677 license使用时间过期的能爆破么
有license就能做出永久的假服务器
雪    币: 162
活跃值: 活跃值 (61)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
CHYX 活跃值 6天前
13
0
狗内的授权可以提取?然后脱狗运行吗?
雪    币: 2433
活跃值: 活跃值 (1141)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiwushgya 活跃值 6天前
14
0
楼主太厉害了,膜拜
游客
登录 | 注册 方可回帖
返回