关于异或游戏解密算法的帮助

逆向工程 拆卸 静态分析 解密 异或
2021-07-10 22:46:41

我目前正在尝试使用聊天消息包对旧在线游戏的解密算法进行逆向工程,因为它包含易于识别的文本。

我使用数据包嗅探器从服务器获取接收到的数据包(已加密):

5e 00 09 32 3c c1 6e b5 ae be 90 4a 70 8f b7 3d
3a 81 c5 3a f9 11 a6 06 36 3d f3 68 a3 dd 72 a3
ff e1 c3 e9 12 28 d7 c5 a0 f1 ce 38 35 59 19 b6
85 90 76 23 42 af 50 6f 66 52 ec ad f9 da 61 3f
5d 09 ee 8d dd 9e ff ee 7d 27 0f 5c 1e df ba 30
de d0 c8 8c 1b 93 1d 53 66 13 98 ff 29 db

前 4 个字节是已知且未加密的:

5E 00是大小,这里09 3294。 是大端的 OPCODE:这里是 2354。接下来是加密的有效载荷。

知道加密数据包后,我使用 Cheat Engine 在 winsocket 接收函数之后设置断点,然后搜索存储数据包的缓冲区。

需要注意的重要事项:显然,存储收到(加密)数据包的缓冲区也将存储解密后的数据包。

检查写入此地址的内容会导致精确的一条指令。 在此处输入图片说明

由于包含加密数据包的缓冲区也包含解密数据包,我可以肯定地说,这必须是解密该值的函数。

解密后的数据包如下所示(最后一位包含聊天消息,存储在用户名之间的某处):

5e 00 09 32 00 00 00 00 4d 00 6e 60 00 00 00 00
06 0a 2c 00 00 00 74 00 65 00 73 00 74 00 63 00
68 00 61 00 72 00 31 00 00 00 91 12 ad 14 5e 75
00 00 cf 00 00 00 00 00 28 cd b8 69 fc f7 91 12
aa f1 07 4d 00 00 cf 00 00 00 00 00 10 74 65 73
74 5f 6d 65 73 73 61 67 65 5f 31 32 33 00

然后我使用 Ghidra 来检查处理解密部分的具体指令——这是我目前卡住的地方,不知道如何继续,因为我对这个话题没有深入的了解。

    undefined4 decrypt_package(int type,int param_2,int param_3,int param_4,int param_5)
    {
      undefined4 success;
      int counter;
      
      if ((param_3 == 0) || (param_4 == 0)) {
        s = 0;
      }
      else {
          // Old encryption function which only used byte ^ 255
          // I assume it's still in here, because the code wasn't cleaned up.
        if (type == 0) {
          counter = 0;
          while (counter < param_5) {
            *(byte *)(param_3 + counter) = *(byte *)(param_4 + counter) ^ 0xff;
            counter = counter + 1;
          }
        }
        else {  // Don't know when this is used - at least not for decryption
          if (type == 1) {
            param_2 = 0x48473c;
            counter = 0;
            while (counter < param_5) {
              *(byte *)(param_3 + counter) = *(byte *)(param_4 + counter) ^ (byte)((uint)param_2 >> 8);
              param_2 = ((uint)*(byte *)(param_3 + counter) + param_2) * 0x2ba339 + 0x2cad2b5;
              counter = counter + 1;
            }
          }
          else {
              // This block of code is called for the decryption part.
            if (type == 2) {
              counter = 0;
              while (counter < param_5) {
                *(byte *)(param_3 + counter) = *(byte *)(param_4 + counter) ^ (byte)((uint)param_2 >> 8)
                ;
                param_2 = ((uint)*(byte *)(param_3 + counter) + param_2) * 0x8e9a99 + 0x685b24;
                counter = counter + 1;
              }
            }
          }
        }
        success = 1;
      }
      return success;
    }

(type == 0的注意事项:游戏刚发布时,只使用了简单的255的异或作为加密,这显然后来停产了)

根据我的理解,代码执行以下操作(假设 param_2 是数据包的缓冲区?):

  1. 循环缓冲区,只要 counter < param_5 (我假设这是要解密的有效负载的长度?)
  2. 将字节与一个值进行异或(需要解密的第一个字节将在位置 5,所以我假设 param_3 将是一种跳过前 4 个字节的偏移量。)
  3. 无论解密是否有效,都返回一个布尔值(不知道为什么它被声明为未定义)。

下面我还贴出了这块代码的汇编代码(从while循环中的counter=0开始,for type == 2) 在此处输入图片说明


正如开头所说,我对这种事情没有经验。

因此,我想要一些关于我将如何从这里继续的附加信息,以便我可以用我选择的编程语言实现解密 - 上面的代码块到底在做什么?

我希望我包含了所有需要的信息。

感谢阅读(和帮助)!:)

1个回答

从 type == 0 可以看出它写入 param_3 并从 param_4 读取。所以这些参数必须分别是指向输出和输入缓冲区的指针。实际上,如果将它们更改为正确的类型(字节 *),那么我相信 Ghidra 应该能够将其余代码简化为易于理解的形式。

修改 param_2 的部分是一个简单的伪 RNG。请注意通过右移摆脱其输出的非随机低位的经典技巧。

另外,我自己没有检查这个,但我打赌类型 1 和类型 2 做相反的事情。也就是说,其中一个加密,另一个解密。