我正在尝试对恶意软件进行逆向工程,并遇到了一段我怀疑是加密/解密过程的代码,但是我不确定。我可以识别出它是从其自己的.rsrc部分提取其有效负载的。
我用FindCrypt和IDAScope扫描了此算法,两个插件都未能将其识别为加密算法。

我不熟悉密码算法。

提取代码是由IDA生成的:

int __stdcall sub_9001320(int a1, unsigned int a2, int a3)
{
// a1 = a3 = 0x09003000
// a2 = 0x7600



  int result; // eax@2
  char v4; // al@23
  unsigned int l; // [sp+4h] [bp-4Ch]@33
  unsigned int k; // [sp+10h] [bp-40h]@19
  unsigned int v7; // [sp+14h] [bp-3Ch]@19
  int v8; // [sp+18h] [bp-38h]@40
  signed int j; // [sp+1Ch] [bp-34h]@13
  unsigned int v10; // [sp+20h] [bp-30h]@5
  unsigned int i; // [sp+24h] [bp-2Ch]@5
  unsigned int v12; // [sp+28h] [bp-28h]@5
  unsigned int v13; // [sp+2Ch] [bp-24h]@5
  signed int v14; // [sp+30h] [bp-20h]@13
  unsigned int v15; // [sp+34h] [bp-1Ch]@5
  unsigned int v16; // [sp+38h] [bp-18h]@5
  unsigned __int8 v17; // [sp+3Fh] [bp-11h]@5
  signed int v18; // [sp+40h] [bp-10h]@5
  _DWORD *lpAddress; // [sp+44h] [bp-Ch]@11
  unsigned int v20; // [sp+48h] [bp-8h]@13
  unsigned int v21; // [sp+4Ch] [bp-4h]@16

  if ( a2 > 0xC )
  {
    if ( *(_DWORD *)a1 == 1083581807 )
    {
      v16 = sub_9001300(a1);
      v18 = 12;
      v17 = 0;
      v15 = 9;
      v12 = 256;
      v13 = 256;
      v10 = 8 * a2 - 96;
      for ( i = 9; v10 >= i; v10 -= i )
      {
        if ( i != 12 && v13 == 1 << i )
          ++i;
        ++v13;
      }
      lpAddress = VirtualAlloc(0, 0xC000u, 0x1000u, 4u);
      if ( lpAddress )
      {
        v20 = 0;
        v14 = 256;
        for ( j = 0; j <= 255; ++j )
        {
          lpAddress[3 * j] = 0;
          lpAddress[3 * j + 2] = 0;
          lpAddress[3 * j + 1] = 1;
        }
        v21 = 0;
        while ( v21 < v16 && v12 < v13 )
        {
          v7 = 0;
          for ( k = 0; k < v15; ++k )
          {
            if ( (1 << (7 - (v17 + 8 * v18) % 8)) & *(_BYTE *)(a1 + (v17 + 8 * v18) / 8) )
              v7 |= 1 << k;
            v4 = (v17 + 1) % 8;
            v17 = (v17 + 1) % 8;
            if ( !v4 )
              ++v18;
          }
          if ( v7 > 0xFF )
          {
            if ( v20 < 0xF00 && v7 > v20 + 255 )
              return 0;
            if ( v21 + lpAddress[3 * v7 + 1] > v16 )
              return 0;
            for ( l = 0; l < lpAddress[3 * v7 + 1]; ++l )
              *(_BYTE *)(a3 + l + v21) = *(_BYTE *)(a3 + l + lpAddress[3 * v7]);
          }
          else
          {
            *(_BYTE *)(v21 + a3) = v7;
          }
          if ( v20 == 3840 )
          {
            if ( (unsigned int)++v14 >> 12 )
              v14 = 256;
          }
          if ( v20 >= 0xF00 )
          {
            v8 = v14 - 1;
            if ( v14 == 256 )
              v8 = 4095;
          }
          else
          {
            v8 = v20 + v14;
          }
          lpAddress[3 * v8] = v21;
          lpAddress[3 * v8 + 1] = lpAddress[3 * v7 + 1] + 1;
          lpAddress[3 * v8 + 2] = 0;
          if ( v20 < 0xF00 )
            ++v20;
          v21 = v21 + lpAddress[3 * v8 + 1] - 1;
          ++v12;
          if ( v15 < 0xC && 1 << v15 == v12 )
            ++v15;
        }
        if ( v21 >= v16 )
        {
          // sub_9001000 is only called onece and always return 0
          if ( *(_DWORD *)(a1 + 8) == sub_9001000(a3, v16) )
          {
            VirtualFree(lpAddress, 0, 0x8000u);
            result = 0;
          }
          else
          {
            result = 0;
          }
        }
        else
        {
          result = 0;
        }
      }
      else
      {
        result = 0;
      }
    }
    else
    {
      result = 0;
    }
  }
  else
  {
    result = 0;
  }
  return result;
}


int __stdcall sub_9001300(int a1)
{
  int result; // eax@2

  if ( *(_DWORD *)a1 == 1083581807 )
    result = *(_DWORD *)(a1 + 4);
  else
    result = 0;
  return result;
}

/>
文件信息:
文件格式pei-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00001000  09001000  09001000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rdata        00001000  09002000  09002000  00002000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .rsrc         00005000  09003000  09003000  00003000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  3 .reloc        00001000  09008000  09008000  00008000  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA


评论

它似乎是一种压缩算法,而不是加密算法。

#1 楼

TL; DR:我们这里所拥有的可能不是加密算法,从外观上看,它更可能是解压缩循环。它根本不做任何与加密类似的事情,甚至在远程也是如此。
加密算法分为两类。首先是流密码。来自维基百科:

流密码是一种对称密钥密码,其中明文数字与伪随机密码数字流(密钥流)组合在一起。在流密码中,每个明文数字每次都用密钥流的相应数字加密一次,以给出密文流的数字。由于每个数字的加密取决于密码的当前状态,因此也称为状态密码。实际上,一个数字通常是一个位,而组合操作则是异或(XOR)。

第二个是块密码。来自维基百科:

在密码学中,分组密码是一种确定性算法,可对固定长度的位组(称为块)进行操作,并具有由对称密钥指定的不变变换。分组密码是许多密码协议设计中的重要基本组成部分,被广泛用于实现批量数据的加密。
分组密码的现代设计基于迭代产品密码的概念。克劳德·香农(Claude Shannon)在1949年的重要著作《保密系统通信理论》中分析了产品密码,并建议将它们作为一种有效的安全性手段,通过结合诸如替换和置换之类的简单操作来实现。1迭代产品密码在多个回合中执行加密。它使用从原始密钥派生的另一个子密钥。这种密码的一种广泛实现,以Horst Feistel的名字命名为Feistel网络,尤其是在DES密码中实现。2分组密码的许多其他实现,例如AES,被归类为置换置换网络。

我们这里可能不是加密算法。它根本不做任何与加密类似的远程操作。一些在加密算法中经常发现但在这里不见的东西(不是完整的列表,只是我想出的前几件事):

没有基于加密算法生成的伪随机流内部状态和种子,如流密码中那样
使用XOR(或任何其他操作),不会将生成的流与输入缓冲区的顺序字符组合。
无块结构-在每个漂亮的分块部分上多次执行序列输入消息的内容。
没有冗长而复杂的置换(也没有置换表)。
没有针对分组密码通用的置换码的多次迭代。
没有密钥序列初始化(在两个流中都使用)和分组密码)。
对输入消息或流/状态没有足够的替代。

加密算法通常很长(有人说很难看)。通常对长重复序列中的所有字节执行操作时结构严谨。您经常会看到执行一个或几个操作的固定长度的多次迭代。
这更像是一种压缩/解压缩算法,因为字节序列在某些条件下被复制,而字节被构造到解压缩的缓冲区中
让我们遍历代码,然后查看:
int __stdcall sub_9001320(int a1, unsigned int a2, int a3)
{
// a1 = a3 = 0x09003000
// a2 = 0x7600

  int result; // eax@2
  char v4; // al@23
  unsigned int l; // [sp+4h] [bp-4Ch]@33
  unsigned int k; // [sp+10h] [bp-40h]@19
  unsigned int v7; // [sp+14h] [bp-3Ch]@19
  int v8; // [sp+18h] [bp-38h]@40
  signed int j; // [sp+1Ch] [bp-34h]@13
  unsigned int v10; // [sp+20h] [bp-30h]@5
  unsigned int i; // [sp+24h] [bp-2Ch]@5
  unsigned int v12; // [sp+28h] [bp-28h]@5
  unsigned int v13; // [sp+2Ch] [bp-24h]@5
  signed int v14; // [sp+30h] [bp-20h]@13
  unsigned int v15; // [sp+34h] [bp-1Ch]@5
  unsigned int v16; // [sp+38h] [bp-18h]@5
  unsigned __int8 v17; // [sp+3Fh] [bp-11h]@5
  signed int v18; // [sp+40h] [bp-10h]@5
  _DWORD *lpAddress; // [sp+44h] [bp-Ch]@11
  unsigned int v20; // [sp+48h] [bp-8h]@13
  unsigned int v21; // [sp+4Ch] [bp-4h]@16

函数定义
  if ( a2 > 0xC )
  {

确定一定的长度至少为96个字节。尽管有一些块密码接受此块大小,但它并不是太普遍,并且最广泛接受的块密码不支持此块大小。
    if ( *(_DWORD *)a1 == 1083581807 )
    {

确定缓冲区的某些第一个字节是固定的,这看起来
      v16 = sub_9001300(a1);

v16设置为输入缓冲区的第二个双字
      v18 = 12;
      v17 = 0;
      v15 = 9;
      v12 = 256;
      v13 = 256;
      v10 = 8 * a2 - 96;

更多变量初始化
      for ( i = 9; v10 >= i; v10 -= i )
      {
        if ( i != 12 && v13 == 1 << i )
          ++i;
        ++v13;
      }

根据原始长度增加v13。在这里无需过多说明,这看起来不像任何密码种子/初始化。
      lpAddress = VirtualAlloc(0, 0xC000u, 0x1000u, 4u);
      if ( lpAddress )
      {

分配硬编码的长度缓冲区以用作临时数据
        v20 = 0;
        v14 = 256;
        for ( j = 0; j <= 255; ++j )
        {
          lpAddress[3 * j] = 0;
          lpAddress[3 * j + 2] = 0;
          lpAddress[3 * j + 1] = 1;
        }

初始化表示缓冲区,2/3为0,1 / 3为1s。这可能是布尔缓冲区。
循环v16次。请记住,v16是用户提供的,它是提供的缓冲区中的第二个双字。魔术之后的第二个双字可能是长度。
        v21 = 0;
        while ( v21 < v16 && v12 < v13 )
        {

另一个循环,这一次是9次迭代。这意味着我们在猜测的length参数中为每个字符进行了9次迭代。大多数分组密码将具有更多的迭代,而这些迭代不会按字符进行。大多数流密码不会处理数据,而是生成要与XOR进行比较的字节流。我们也没有在这里看到异或。
          v7 = 0;
          for ( k = 0; k < v15; ++k )
          {

位仅被添加到v7,对于任何类型的加密算法也不起作用。
            if ( (1 << (7 - (v17 + 8 * v18) % 8)) & *(_BYTE *)(a1 + (v17 + 8 * v18) / 8) )
              v7 |= 1 << k;

这两个都是字节-大小的计数器。
            v4 = (v17 + 1) % 8;
            v17 = (v17 + 1) % 8;

增加另一个计数器,用于确定在v7中将设置多少位。整个循环决定v7的哪些位,取决于固定的顺序,并且输入缓冲区的某些位是否为
            if ( !v4 )
              ++v18;
          }

如果v7大于0xff,请进行一些检查(并返回无效状态/数据),然后复制一个字节序列,直到从a3到a3到达空终止符为止,偏移量由lpAddress的值确定,位于v7确定的特定偏移量处。
          if ( v7 > 0xFF )
          {
            if ( v20 < 0xF00 && v7 > v20 + 255 )
              return 0;
            if ( v21 + lpAddress[3 * v7 + 1] > v16 )
              return 0;
            for ( l = 0; l < lpAddress[3 * v7 + 1]; ++l )
              *(_BYTE *)(a3 + l + v21) = *(_BYTE *)(a3 + l + lpAddress[3 * v7]);
          }

如果v7小于或等于等于255,只需将其分配到指定位置即可。
根据偏移变量v8及其自身的值重置v4v20的值。
          else
          {
            *(_BYTE *)(v21 + a3) = v7;
          }

设置一些基于lpAddress的硬编码值(0)中的其他值和单个值的副本。
          if ( v20 == 3840 )
          {
            if ( (unsigned int)++v14 >> 12 )
              v14 = 256;
          }
          if ( v20 >= 0xF00 )
          {
            v8 = v14 - 1;
            if ( v14 == 256 )
              v8 = 4095;
          }
          else
          {
            v8 = v20 + v14;
          }

基于输入的更多计数器更新和溢出复位。
          lpAddress[3 * v8] = v21;
          lpAddress[3 * v8 + 1] = lpAddress[3 * v7 + 1] + 1;
          lpAddress[3 * v8 + 2] = 0;

如果输入无效,则返回0
          if ( v20 < 0xF00 )
            ++v20;
          v21 = v21 + lpAddress[3 * v8 + 1] - 1;
          ++v12;
          if ( v15 < 0xC && 1 << v15 == v12 )
            ++v15;
        }

声明缓冲区以硬编码双字开头(我们已经知道是由于前面的if语句导致的情况),然后在第二个dword中返回值。

评论


甚至没有一点P-box或S-box,更不用说旋转指令序列了。

–约翰·格林
19/12/2在22:10