各位老铁,大家好!今天咱们来聊聊字符串加密/解密这事儿,这可是代码混淆中的重头戏。很多时候,我们拿到一个程序,字符串都被加密得七荤八素,想分析都无从下手。别慌,今天咱们就来扒一扒,如何在不执行代码的情况下识别加密算法和密钥,以及运行时解密 Hook 技术的那些事儿。
一、不执行代码识别加密算法和密钥:静态分析的艺术
首先,我们要明确一点:不执行代码就想完全还原所有加密算法和密钥,这几乎是不可能的。但我们可以通过静态分析,尽可能地逼近真相。
-
特征码识别法:大海捞针也要捞准
很多加密算法都有一些标志性的常量、运算或者函数调用。我们可以通过搜索这些特征码,来缩小算法的范围。
-
常见算法特征:
算法 特征 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算法。
-
-
交叉引用分析:顺藤摸瓜找密钥
找到了加密算法,下一步就是找密钥。密钥通常会被用在加密/解密函数中。我们可以通过交叉引用分析,找到使用加密/解密函数的代码,然后追踪密钥的来源。
-
实战案例:追踪密钥的来源
假设我们找到了一个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);
我们需要根据实际情况,分析密钥的来源,然后提取出密钥。
-
-
字符串引用分析:定位关键字符串
有些程序会先解密字符串,然后再使用。我们可以通过字符串引用分析,找到哪些字符串被解密后使用,从而定位关键字符串。
-
实战案例:查找被解密后使用的字符串
在IDA Pro中,我们可以打开 "Strings window"(字符串窗口),找到我们感兴趣的字符串,然后右键点击,选择 "References to"(引用),就可以看到哪些地方使用了这个字符串。
如果这个字符串是被解密后使用的,那么我们就可以找到解密函数的调用,然后分析解密过程。
-
二、运行时解密 Hook 技术:动态追踪的利器
静态分析虽然可以帮助我们识别加密算法和密钥,但有时候,加密算法非常复杂,密钥动态生成,静态分析就力不从心了。这时候,就需要运行时解密 Hook 技术了。
-
什么是 Hook?
Hook,顾名思义,就是“钩子”。它是一种拦截系统调用或者函数调用的技术。通过Hook,我们可以在目标函数执行前后,插入我们自己的代码,从而实现监控、修改、甚至是替换目标函数的行为。
-
运行时解密 Hook 的原理
运行时解密 Hook 的原理很简单:找到解密函数,Hook它,在解密函数执行后,把解密后的字符串打印出来或者保存下来。
-
Hook 的实现方式
Hook 的实现方式有很多种,常见的有:
- Inline Hook: 直接修改目标函数的指令,插入跳转指令到我们的Hook函数。这是最常用的Hook方式,但需要小心处理指令的修改,以免破坏程序的稳定性。
- IAT Hook: 修改 Import Address Table(IAT),IAT是PE文件中记录程序使用的外部函数地址的表。通过修改IAT,我们可以让程序调用我们自己的函数,而不是原来的函数。这种Hook方式比较安全,但只能Hook导入函数。
- VTable Hook: 用于Hook C++ 类的虚函数。通过修改虚函数表(VTable),我们可以让程序调用我们自己的虚函数,而不是原来的虚函数。
-
实战案例:使用 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
函数,并在每次调用时打印加密和解密后的字符串。
-
-
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技术来保护他们的代码。我们需要不断学习新的技术,才能更好地应对各种挑战。
记住,技术是把双刃剑,我们要用它来维护网络安全,而不是用于非法用途。
今天的讲座就到这里,希望对大家有所帮助。下次有机会再和大家聊聊其他技术话题。 谢谢大家!