13、加密与解密基础(下):字符串加密与解密、资源节中的加密数据、实战:解密一个被加密的配置文件
上一节我们聊了加密解密的基本概念和常见算法。说实话,那些都是理论铺垫。真正在逆向工程里跟加密数据打交道,你会发现——嗯,大部分时候你面对的不是一个完整的加密算法实现,而是被处理过的字符串、藏在资源节里的二进制块、或者一个被简单混淆的配置文件。
这一节,我们就来点实际的。我会带你看看真实样本里最常见的三种加密数据场景,以及我是怎么一步步把它们扒光的。
13.1 字符串加密:为什么程序要藏起自己的话?
你想想看,一个正常的程序里,字符串是最容易暴露意图的东西。比如一个恶意软件如果明文写着 CreateRemoteThread、WriteProcessMemory,杀毒软件一眼就能认出来。所以,稍微有点经验的开发者都会对字符串做一层处理。
我见过最粗暴的做法,就是把每个字符异或一个固定值。比如这样:
// 加密前的字符串
char *secret = "HelloWorld";
// 加密:每个字节异或 0x55
for (int i = 0; i < strlen(secret); i++) {
encrypted[i] = secret[i] ^ 0x55;
}
// 解密:同样的操作
for (int i = 0; i < strlen(encrypted); i++) {
decrypted[i] = encrypted[i] ^ 0x55;
}
这种加密方式,说白了就是一层窗户纸。但你别小看它,我在一个商业软件里见过类似的实现,他们用了 0xAA 作为密钥,藏在一个全局变量里。我当时第一反应是——嗯,这跟没加密差不多。
还有一种更常见的做法是使用自定义的编码表。比如把字符串的每个字符加上一个固定偏移量,或者用 Base64 变种。我记得有一次分析一个勒索软件,它的配置字符串用了自定义的 Base64 表——把标准表的 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 打乱顺序。我当时花了十分钟才意识到,原来它只是换了个表。
13.2 资源节中的加密数据:藏在 PE 文件里的秘密
很多程序会把加密的数据放在 PE 文件的资源节(.rsrc)里。为什么?因为资源节是 Windows 原生支持的,加载起来方便,而且不容易引起怀疑。
我遇到过这样一个样本:一个正常的图片查看器,资源节里藏了一个加密的 DLL。程序启动时,先从资源里读取这个 DLL 的加密数据,然后在内存中解密并加载。这种手法在恶意软件里非常常见,我称之为「资源节里的特洛伊木马」。
怎么发现这种隐藏数据?我的做法是:
- 用 Resource Hacker 或 PE-bear 查看资源节——看看有没有异常大的资源,或者类型奇怪的资源。
- 提取可疑资源——如果资源看起来像加密数据(熵值高、没有明显文件头),那基本就是加密的。
- 在 IDA 或 x64dbg 中定位解密函数——通常程序会调用
FindResource、LoadResource、LockResource这三个 API 来加载资源,然后紧接着就是解密逻辑。
我曾经分析过一个游戏外挂,它的配置文件就藏在资源节里,用 XOR + ROT13 两层加密。我当时在 x64dbg 里下断点,看到它解密后的内容竟然是明文的服务器地址和端口。嗯,这种加密强度,说实话,防君子不防小人。
13.3 实战:解密一个被加密的配置文件
好了,理论说完了,我们来动真格的。假设你拿到了一个程序,它读取一个叫 config.bin 的文件,但这个文件是加密的。你的任务是把它解密成可读的 JSON 或 INI 格式。
我一般会按这个流程走:
具体来说,我会这样做:
第一步:定位文件读取点
在 x64dbg 或 IDA 中,搜索 CreateFile、ReadFile、fopen 等 API。下断点,运行程序,观察它读取了哪个文件。如果读取的是 config.bin,那我们就找到了入口。
第二步:跟踪解密函数
在 ReadFile 返回后,数据会存放在一个缓冲区里。接下来程序会对这个缓冲区做处理。我一般会在 ReadFile 返回后,在缓冲区地址上下一个内存访问断点,看看谁在读取它。
我曾经遇到一个程序,它在读取文件后,直接调用了 memcpy 把数据复制到另一个缓冲区,然后才进行解密。我当时差点被这个假动作骗了——嗯,它只是把数据搬了个家,真正的解密在后面。
第三步:提取密钥
解密函数里通常会有固定的密钥。可能是硬编码的字符串,也可能是通过某些算法生成的。我见过最离谱的密钥是「12345678」——对,就是八个数字。也见过从系统信息(如 CPU ID、MAC 地址)生成的动态密钥。
第四步:写解密脚本
假设我们找到了一个简单的 XOR 加密,密钥是 0x7F。解密脚本可以这样写:
# Python 解密脚本
def decrypt_file(input_file, output_file, key=0x7F):
with open(input_file, 'rb') as f:
data = f.read()
decrypted = bytes([b ^ key for b in data])
with open(output_file, 'wb') as f:
f.write(decrypted)
print(f"解密完成,输出到 {output_file}")
# 使用
decrypt_file("config.bin", "config_decrypted.bin")
如果密钥是多字节的,比如 0x12, 0x34, 0x56, 0x78,那就循环使用:
def decrypt_multi_xor(input_file, output_file, key):
with open(input_file, 'rb') as f:
data = f.read()
key_len = len(key)
decrypted = bytes([data[i] ^ key[i % key_len] for i in range(len(data))])
with open(output_file, 'wb') as f:
f.write(decrypted)
print(f"解密完成,输出到 {output_file}")
# 使用
decrypt_multi_xor("config.bin", "config_decrypted.bin", b'\x12\x34\x56\x78')
我记得有一次,我花了整整一个下午去逆向一个自定义的加密算法。最后发现,它其实就是把数据按 4 字节分组,然后跟一个固定的 DWORD 做 XOR。我当时的心情——嗯,五味杂陈。
13.4 一些实用的小技巧
- 先看熵值:用 010 Editor 或 HxD 打开加密文件,看看熵值。如果熵值接近 8.0,那很可能是强加密(如 AES)。如果熵值在 4.0-6.0 之间,多半是 XOR 或简单替换。
- 找规律:如果文件里有重复的字节模式,那很可能是 XOR 加密。比如每隔 4 个字节出现一次
0x00,那密钥可能就是 4 字节的。 - 不要忽视硬编码:在 IDA 中搜索
xor、encrypt、decrypt、key等字符串,有时候开发者会不小心把密钥注释在代码里。 - 动态调试是王道:静态分析看不懂的时候,直接上调试器。在解密函数返回后,把内存 dump 出来,往往比分析算法快得多。
好了,这一节的内容就到这里。字符串加密、资源节隐藏数据、配置文件解密——这些都是逆向工程里最常遇到的场景。掌握了这些,你就能应付大部分「加密」的假把戏了。
记住,加密和解密是一场博弈。作为逆向工程师,我们的目标不是破解所有加密,而是找到那个最薄弱的环节——往往就是密钥本身。
公众号:蓝海资料掘金营,微信deep3321