字符串加密/解密混淆中,如何在不执行代码的情况下识别加密算法和密钥?探讨其在内存中的运行时解密 Hook 技术。

各位老铁,大家好!今天咱们来聊聊字符串加密/解密这事儿,这可是代码混淆中的重头戏。很多时候,我们拿到一个程序,字符串都被加密得七荤八素,想分析都无从下手。别慌,今天咱们就来扒一扒,如何在不执行代码的情况下识别加密算法和密钥,以及运行时解密 Hook 技术的那些事儿。

一、不执行代码识别加密算法和密钥:静态分析的艺术

首先,我们要明确一点:不执行代码就想完全还原所有加密算法和密钥,这几乎是不可能的。但我们可以通过静态分析,尽可能地逼近真相。

  1. 特征码识别法:大海捞针也要捞准

    很多加密算法都有一些标志性的常量、运算或者函数调用。我们可以通过搜索这些特征码,来缩小算法的范围。

    • 常见算法特征:

      算法 特征
      XOR 简单的位异或操作,可能会有循环异或的特征。
      AES S盒(Substitution Box)的查找表,固定的轮常量,以及AddRoundKey、SubBytes、ShiftRows、MixColumns等操作。
      DES/Triple DES 固定的初始置换表、逆初始置换表、S盒、密钥置换表等。
      RC4 状态数组(S盒)的初始化和伪随机数生成算法(PRGA)。
      Base64 固定的索引表("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),以及填充字符(’=’)。
      zlib 0x78 0x9C 开头的魔数,Deflate算法的特征压缩码。
      Blowfish P数组和S盒的初始化过程。
      TEA/XTEA 固定的Delta值 (0x9E3779B9) 和轮函数结构。
    • 实战案例:识别AES算法

      AES算法的S盒是一个256字节的查找表,我们可以搜索这个表。虽然不同的实现方式S盒的存放方式可能不同,但数据内容是固定的。我们可以用IDA Pro、Binary Ninja等工具搜索这个S盒的特征数据。

      S盒的开头几个字节 (十六进制):

      63 7C 77 7B F2 6B 6F C5 30 01 67 2B FE D7 AB 76

      如果找到了类似的数据,那基本可以确定使用了AES算法。

      代码示例 (Python,用于在文件中搜索S盒特征):

      import re
      
      def find_aes_sbox(file_path):
          sbox_pattern = b"x63x7cx77x7bxf2x6bx6fxc5x30x01x67x2bxfexd7xabx76"
          with open(file_path, 'rb') as f:
              content = f.read()
              match = re.search(sbox_pattern, content)
              if match:
                  print(f"找到 AES S盒特征,位置:{match.start()}")
              else:
                  print("未找到 AES S盒特征")
      
      # 替换成你的文件路径
      file_path = "your_file.exe"
      find_aes_sbox(file_path)

      这个脚本会在指定的文件中搜索S盒的开头几个字节。如果找到了,就说明很可能使用了AES算法。

  2. 交叉引用分析:顺藤摸瓜找密钥

    找到了加密算法,下一步就是找密钥。密钥通常会被用在加密/解密函数中。我们可以通过交叉引用分析,找到使用加密/解密函数的代码,然后追踪密钥的来源。

    • 实战案例:追踪密钥的来源

      假设我们找到了一个AES解密函数的调用,在IDA Pro中,我们可以右键点击这个函数调用,选择 "Xrefs to"(交叉引用),就可以看到哪些地方调用了这个函数。

      然后,我们需要分析调用这个函数的地方,看看密钥是从哪里来的。密钥可能直接硬编码在代码中,也可能从配置文件、注册表或者其他地方读取。

      代码示例 (伪代码,展示密钥的传递):

      // 密钥硬编码
      unsigned char key[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
      AES_decrypt(encrypted_data, decrypted_data, key);
      
      // 从配置文件读取密钥
      char key_file_path[] = "config.ini";
      char key[16];
      read_key_from_file(key_file_path, key);
      AES_decrypt(encrypted_data, decrypted_data, key);
      
      // 从注册表读取密钥
      char key_name[] = "Software\MyProgram\Key";
      char key[16];
      read_key_from_registry(key_name, key);
      AES_decrypt(encrypted_data, decrypted_data, key);

      我们需要根据实际情况,分析密钥的来源,然后提取出密钥。

  3. 字符串引用分析:定位关键字符串

    有些程序会先解密字符串,然后再使用。我们可以通过字符串引用分析,找到哪些字符串被解密后使用,从而定位关键字符串。

    • 实战案例:查找被解密后使用的字符串

      在IDA Pro中,我们可以打开 "Strings window"(字符串窗口),找到我们感兴趣的字符串,然后右键点击,选择 "References to"(引用),就可以看到哪些地方使用了这个字符串。

      如果这个字符串是被解密后使用的,那么我们就可以找到解密函数的调用,然后分析解密过程。

二、运行时解密 Hook 技术:动态追踪的利器

静态分析虽然可以帮助我们识别加密算法和密钥,但有时候,加密算法非常复杂,密钥动态生成,静态分析就力不从心了。这时候,就需要运行时解密 Hook 技术了。

  1. 什么是 Hook?

    Hook,顾名思义,就是“钩子”。它是一种拦截系统调用或者函数调用的技术。通过Hook,我们可以在目标函数执行前后,插入我们自己的代码,从而实现监控、修改、甚至是替换目标函数的行为。

  2. 运行时解密 Hook 的原理

    运行时解密 Hook 的原理很简单:找到解密函数,Hook它,在解密函数执行后,把解密后的字符串打印出来或者保存下来。

  3. Hook 的实现方式

    Hook 的实现方式有很多种,常见的有:

    • Inline Hook: 直接修改目标函数的指令,插入跳转指令到我们的Hook函数。这是最常用的Hook方式,但需要小心处理指令的修改,以免破坏程序的稳定性。
    • IAT Hook: 修改 Import Address Table(IAT),IAT是PE文件中记录程序使用的外部函数地址的表。通过修改IAT,我们可以让程序调用我们自己的函数,而不是原来的函数。这种Hook方式比较安全,但只能Hook导入函数。
    • VTable Hook: 用于Hook C++ 类的虚函数。通过修改虚函数表(VTable),我们可以让程序调用我们自己的虚函数,而不是原来的虚函数。
  4. 实战案例:使用 Fridahook 解密字符串

    Frida 是一个强大的动态插桩工具,可以让我们在运行时修改程序的行为。下面我们用Frida来实现一个简单的运行时解密 Hook。

    • 假设目标程序有一个解密函数 decrypt_string(char* encrypted_string, char* decrypted_string),我们需要Hook这个函数,把解密后的字符串打印出来。

    • Frida 脚本 (JavaScript):

      // 找到解密函数的地址
      var decrypt_string_address = Module.findExportByName(null, "decrypt_string");
      
      if (decrypt_string_address) {
          // Hook 解密函数
          Interceptor.attach(decrypt_string_address, {
              onEnter: function (args) {
                  // args[0] 是 encrypted_string 的地址
                  // args[1] 是 decrypted_string 的地址
                  console.log("加密字符串:", Memory.readUtf8String(args[0]));
              },
              onLeave: function (retval) {
                  // retval 是解密函数的返回值
                  // args 是 onEnter 函数的参数,这里可以直接访问
                  console.log("解密字符串:", Memory.readUtf8String(args[1]));
              }
          });
      
          console.log("成功 Hook decrypt_string 函数");
      } else {
          console.log("未找到 decrypt_string 函数");
      }

      这个脚本首先找到 decrypt_string 函数的地址,然后使用 Interceptor.attach 函数来Hook这个函数。onEnter 函数在解密函数执行前被调用,onLeave 函数在解密函数执行后被调用。

      onEnter 函数中,我们打印加密字符串。在 onLeave 函数中,我们打印解密后的字符串。

    • 运行 Frida 脚本:

      首先,我们需要把 Frida 安装到我们的设备上。然后,运行目标程序,并使用以下命令来运行 Frida 脚本:

      frida -U -f your_program_name -l your_frida_script.js

      -U 表示连接到 USB 设备。-f 表示启动目标程序。-l 表示加载 Frida 脚本。

      运行后,Frida 会Hook decrypt_string 函数,并在每次调用时打印加密和解密后的字符串。

  5. Hook 的注意事项

    • 选择合适的 Hook 方式: 根据实际情况选择合适的Hook方式。Inline Hook 比较灵活,但需要小心处理指令的修改。IAT Hook 比较安全,但只能Hook导入函数。
    • 处理好线程同步: 如果目标程序是多线程的,需要处理好线程同步,以免Hook代码出现竞争条件。
    • 避免死循环: Hook 代码要避免调用被Hook的函数,否则容易造成死循环。
    • 注意兼容性: Hook 代码要考虑兼容性,不同的操作系统、不同的编译器、不同的CPU架构,Hook代码可能需要做不同的调整。

三、反 Hook 技术:猫鼠游戏永不停歇

有Hook就有反Hook。攻击者会使用各种反Hook技术来保护他们的代码。常见的反Hook技术有:

  • 检测 Hook: 程序会检测关键函数是否被Hook,如果被Hook,就退出或者采取其他措施。
  • 修改 Hook: 程序会定期修改关键函数的代码,让Hook失效。
  • 隐藏 Hook: 程序会使用一些技巧来隐藏Hook代码,让分析者难以发现。

面对反Hook技术,我们需要不断学习新的Hook技术和反反Hook技术,才能更好地分析和破解程序。

四、总结

字符串加密/解密是代码混淆中的重要一环。通过静态分析和运行时解密 Hook 技术,我们可以有效地识别加密算法和密钥,从而分析和破解程序。但攻击者也会使用各种反Hook技术来保护他们的代码。我们需要不断学习新的技术,才能更好地应对各种挑战。

记住,技术是把双刃剑,我们要用它来维护网络安全,而不是用于非法用途。

今天的讲座就到这里,希望对大家有所帮助。下次有机会再和大家聊聊其他技术话题。 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注