确定解密方案

逆向工程 拆卸 加密
2021-06-15 10:07:13

我正在尝试确定某些许可机制使用的解密方案。我花了一些时间才意识到这是加密,这是我的另一个问题的某种跟进:从验证算法创建密钥生成器算法

我做了一些功课,在我看来,这个解密的关键特征如下:

1-首先对输入进行 base64 解码(使用一些非标准密钥表,但算法完全相同)
2-使用与输入长度相同的密钥对其进行解密。这可能是一次性的,或者只是用于非常短数据的非常长的密钥(都是 512 位)
3-最重要的是,让我感到困惑的是解密算法使用 NO xor。我见过的每个密码都在解密的某个时刻使用 XOR,但这里根本没有。

第3点是我在这里真的不明白。我看不到如何生成无需 XOR 即可解密的数据。在此过程中,使用位移位和 AND/OR 掩码似乎会丢失很多信息。

是否有任何已知的密码匹配这些特征?

编辑 :

好的,这里是对算法的更深入的描述:

有一个函数被多次调用。它首先使用数据和解密密钥创建 2 个表。第二个是在第一个的基础上。

此函数首先使用数据创建一个表,然后使用另一个表中的解密密钥处理该数据。

从数据创建的表是通过从数据中处理每个 32 位块(主块)与整个数据加上加法来制作的,以便它unsigned int为 16 个 32 位块中的每一个生成一个对此的呼吁看起来像这样

hash_block(&result1, &result2, current_master_block, data_array[i]);

hash_block 看起来像这样:

void hash_block(int *result1, int *result2, unsigned int pMaster_block, unsigned int pCurrent_block)
{
    int current_block;
    unsigned int current_high_short;
    int master_high_times_master; 
    unsigned int master_high_master_short;
    int master_high_times_current;
    unsigned int sum_of_mixes;

    current_block = (unsigned __int16)pCurrent_block;

    current_high_short = pCurrent_block >> 16;
    master_high_times_master = HIWORD(pCurrent_block) * (unsigned __int16)pMaster_block;
    master_high_master_short = pMaster_block >> 16;
    *(_DWORD *)result1 = current_block * (unsigned __int16)pMaster_block;
    master_high_times_current = current_block * HIWORD(pMaster_block);
    sum_of_mixes = master_high_times_current + master_high_times_master;
    *(_DWORD *)(result2) = (unsigned __int16)current_high_short * (unsigned __int16)master_high_master_short;
    if ( master_high_times_current > sum_of_mixes )
        *(_DWORD *)(result2) += 65536;
    *(_DWORD *)result1 += sum_of_mixes << 16;
    if ( sum_of_mixes << 16 > *(_DWORD *)result1 )
        ++*(_DWORD *)(result2);
    *(_DWORD *)(result2) += sum_of_mixes >> 16;
}

我目前正在清理代码,我将跟进有关算法第二部分如何工作的更多信息

编辑 2:

还有一个非常复杂的函数,我无法理解在这个函数中运行了 248 次:

void __fastcall sub_4D5AC0(int* result, int* serial_copy, unsigned int last_keyitem)
{
  unsigned int v3; // edi@1
  unsigned int v4; // edi@2
  int v5; // eax@4
  int v6; // eax@7
  unsigned int v7; // eax@11
  //int return_value; // ecx@25
  int* v10; // [sp+0h] [bp-24h]@1
  unsigned int v11; // [sp+4h] [bp-20h]@4
  int v12; // [sp+4h] [bp-20h]@16
  unsigned __int64 v13; // [sp+4h] [bp-20h]@18
  int v14; // [sp+8h] [bp-1Ch]@1
  unsigned int v15; // [sp+8h] [bp-1Ch]@6
  unsigned int v16; // [sp+10h] [bp-14h]@16

  v3 = *(serial_copy + 1);
  v14 = *(serial_copy + 1);
  if ( HIWORD(last_keyitem) == -1 )
    v4 = v3 >> 16;
  else
    v4 = v3 / ((unsigned int)HIWORD(last_keyitem) + 1);
  v5 = (unsigned __int16)last_keyitem * (unsigned __int16)v4 << 16;
  v11 = *serial_copy - v5;
  //if ( -1 - v5 < v11 )
  if ( ~v5 < v11 )
    --v14;
  v15 = v14 - ((unsigned __int16)last_keyitem * (unsigned int)(unsigned __int16)v4 >> 16) - HIWORD(last_keyitem) * (unsigned __int16)v4;
  while ( 1 )
  {
    if ( HIWORD(last_keyitem) >= v15 )
    {
      v7 = HIWORD(last_keyitem);
      if ( HIWORD(last_keyitem) != v15 )
        break;
      v7 = (unsigned __int16)last_keyitem << 16;
      if ( v7 > v11 )
        break;
    }
    v6 = (unsigned __int16)last_keyitem << 16;
    v11 -= v6;
    if ( -1 - v6 < v11 )
      --v15;
    v15 -= HIWORD(last_keyitem);
    ++v4;
  }
  if ( HIWORD(last_keyitem) == -1 ){
    //LOWORD(v7) = v15;
    v7 &= 0xFFFF0000;
    v7 |= v15 & 0xFFFF;
  }else{
    v7 = ((v11 >> 16) + (v15 << 16)) / ((unsigned int)HIWORD(last_keyitem) + 1);
  }
  v16 = HIWORD(last_keyitem) * (unsigned __int16)v7;
  v12 = v11 - (unsigned __int16)last_keyitem * (unsigned __int16)v7;
  if ( v12 > -1 - (unsigned __int16)last_keyitem * (unsigned int)(unsigned __int16)v7 )
    --v15;
  //LODWORD(v13) = v12 - (v16 << 16);
  v13 &= 0xFFFFFFFF00000000;
  v13 |= (v12 - (v16 << 16)) & 0xFFFFFFFF;
  if ( -1 - (v16 << 16) < (unsigned int)v13 )
    --v15;
  // HIDWORD
  v13 &= 0x00000000FFFFFFFF;
  v13 |= (v15 - (v16 >> 16)) & 0xFFFFFFFF00000000;
  while ( last_keyitem <= v13 )
  {
      v13 &= 0xFFFF0000;
      v13 |= (v13 - last_keyitem) & 0xFFFF;
    //LODWORD(v13) = v13 - a3;
    v13 = (v13 - last_keyitem) & 0xFFFF;
    if ( (unsigned int)v13 > -1 - last_keyitem ){
      //--HIDWORD(v13);
        unsigned __int64 high_word = v13 & 0xFFFFFFFF00000000;
        high_word++;
        v13 &= 0x00000000FFFFFFFF;
        v13 |= high_word;
    }
    ++v7;
  }
  *result = (unsigned __int16)v7 + ((unsigned __int16)v4 << 16);
}
1个回答

第一个函数(.text:004D5A40 处的函数)计算两个 32 位无符号操作数的 64 位乘积,并通过 EAX 存储结果。它可能是一些用半成品 32 位编译器重新编译的半成品 16 位代码......

为了快速测试,您可以将反汇编粘贴到 __asm 块中以使其可重新编译。然后你可以制作一个调用存根来帮助实验:

__declspec(naked)
void sub_4D5A40 ()
{
   __asm
   {
/*4D5A40*/                 push    ebx
... BIG SNIP ...
/*4D5ABE*/                 pop     ebx
/*4D5ABF*/                 retn
   }
}

__declspec(noinline)
uint64_t halfassed_mul (uint32_t multiplicand, uint32_t multiplier)
{
   uint64_t result;

   __asm
   {
      mov   ecx, multiplicand
      mov   edx, multiplier
      lea   eax, result

      call sub_4D5A40
   }

   return result;
}

这样您就可以轻松地将结果与参数的 64 位乘积进行比较/验证。

注意:除非您将堆栈变量引用更改为word ptr [esp]. 如果您忘记了word限定符,那么编译器将假定访问字节大小,这将使事情变得糟糕。

用于您的目标的编译器非常糟糕,Hex-Rays 显然无法理解代码......如果您使用体面的编译器和完全优化重新编译所选函数的反编译器输出,以便删除尽可能多的冗余(不要忘记__declspec(noinline))。如果你反编译它,那么事情应该更清楚。

在 Stack Overflow 和 Code Review 上浏览一些与 bigint 相关的主题,以刷新您对 bigint 代码是什么样子的记忆,尤其是与比赛相关的主题,人们必须重新发明轮子而不是仅仅调用一些bigint 库。例如a^b 中的数字总和主题这应该可以更容易地识别代码中的相关模式。

更新: 我处理sub_4D5AC0是因为我想知道它执行了什么神秘的计算,但是即使在我删除了很多冗余指令之后,试图理解代码还是让我头晕目眩所以我决定给它黑盒处理,假设它执行一些基本的算术计算。代码的基本操作以一种相当复杂的方式表示除法。结合签名(64 位运算 32 位,具有 32 位结果),唯一适合的运算是 MOD。所以我将代码粘贴到一个 .asm 中,它编译顺利(向 IDA 致敬!),制作了一个调用存根并将一些输入输入到黑匣子中。结果表明,该函数将 *EDX 处的 64 位整数除以 ECX,并通过 EAX 存储商的低 32 位。

当我编写一个测试来验证一些随机输入的函数输出时,我像往常一样输入了 1000000000 作为循环计数。Biiig 错误,因为 1000 次调用已经花费了大约一秒钟......