13、加密与解密基础(下):字符串加密与解密、资源节中的加密数据、实战:解密一个被加密的配置文件

上一节我们聊了加密解密的基本概念和常见算法。说实话,那些都是理论铺垫。真正在逆向工程里跟加密数据打交道,你会发现——嗯,大部分时候你面对的不是一个完整的加密算法实现,而是被处理过的字符串、藏在资源节里的二进制块、或者一个被简单混淆的配置文件。

这一节,我们就来点实际的。我会带你看看真实样本里最常见的三种加密数据场景,以及我是怎么一步步把它们扒光的。

13.1 字符串加密:为什么程序要藏起自己的话?

你想想看,一个正常的程序里,字符串是最容易暴露意图的东西。比如一个恶意软件如果明文写着 CreateRemoteThreadWriteProcessMemory,杀毒软件一眼就能认出来。所以,稍微有点经验的开发者都会对字符串做一层处理。

我见过最粗暴的做法,就是把每个字符异或一个固定值。比如这样:

// 加密前的字符串
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 作为密钥,藏在一个全局变量里。我当时第一反应是——嗯,这跟没加密差不多。

我的习惯:遇到异或加密的字符串,先试试 0x00、0xFF、0x55、0xAA 这几个常见值。很多时候一次就解开了。

还有一种更常见的做法是使用自定义的编码表。比如把字符串的每个字符加上一个固定偏移量,或者用 Base64 变种。我记得有一次分析一个勒索软件,它的配置字符串用了自定义的 Base64 表——把标准表的 ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ 打乱顺序。我当时花了十分钟才意识到,原来它只是换了个表。

13.2 资源节中的加密数据:藏在 PE 文件里的秘密

很多程序会把加密的数据放在 PE 文件的资源节(.rsrc)里。为什么?因为资源节是 Windows 原生支持的,加载起来方便,而且不容易引起怀疑。

我遇到过这样一个样本:一个正常的图片查看器,资源节里藏了一个加密的 DLL。程序启动时,先从资源里读取这个 DLL 的加密数据,然后在内存中解密并加载。这种手法在恶意软件里非常常见,我称之为「资源节里的特洛伊木马」。

怎么发现这种隐藏数据?我的做法是:

  1. 用 Resource Hacker 或 PE-bear 查看资源节——看看有没有异常大的资源,或者类型奇怪的资源。
  2. 提取可疑资源——如果资源看起来像加密数据(熵值高、没有明显文件头),那基本就是加密的。
  3. 在 IDA 或 x64dbg 中定位解密函数——通常程序会调用 FindResourceLoadResourceLockResource 这三个 API 来加载资源,然后紧接着就是解密逻辑。
注意:有些程序会在资源节里放一个假的合法数据(比如一张小图片),真正的加密数据藏在图片的末尾。这叫「隐写术」,我见过最离谱的是把加密配置藏在 PNG 图片的 alpha 通道里。

我曾经分析过一个游戏外挂,它的配置文件就藏在资源节里,用 XOR + ROT13 两层加密。我当时在 x64dbg 里下断点,看到它解密后的内容竟然是明文的服务器地址和端口。嗯,这种加密强度,说实话,防君子不防小人。

13.3 实战:解密一个被加密的配置文件

好了,理论说完了,我们来动真格的。假设你拿到了一个程序,它读取一个叫 config.bin 的文件,但这个文件是加密的。你的任务是把它解密成可读的 JSON 或 INI 格式。

我一般会按这个流程走:

1. 定位文件读取点 2. 跟踪解密函数 3. 提取密钥 4. 解密 常见解密算法分支 ├─ XOR 单字节:直接写脚本暴力破解 ├─ XOR 多字节:需要提取完整密钥 ├─ 自定义算法:在调试器中单步跟踪 └─ 标准算法(AES/DES):寻找密钥和 IV

具体来说,我会这样做:

第一步:定位文件读取点

在 x64dbg 或 IDA 中,搜索 CreateFileReadFilefopen 等 API。下断点,运行程序,观察它读取了哪个文件。如果读取的是 config.bin,那我们就找到了入口。

第二步:跟踪解密函数

ReadFile 返回后,数据会存放在一个缓冲区里。接下来程序会对这个缓冲区做处理。我一般会在 ReadFile 返回后,在缓冲区地址上下一个内存访问断点,看看谁在读取它。

我曾经遇到一个程序,它在读取文件后,直接调用了 memcpy 把数据复制到另一个缓冲区,然后才进行解密。我当时差点被这个假动作骗了——嗯,它只是把数据搬了个家,真正的解密在后面。

第三步:提取密钥

解密函数里通常会有固定的密钥。可能是硬编码的字符串,也可能是通过某些算法生成的。我见过最离谱的密钥是「12345678」——对,就是八个数字。也见过从系统信息(如 CPU ID、MAC 地址)生成的动态密钥。

核心思路:不要试图去理解整个解密算法。你只需要找到密钥,然后自己写一个解密脚本。很多时候,你甚至不需要理解算法——直接在调试器里把解密后的内存 dump 出来就行了。

第四步:写解密脚本

假设我们找到了一个简单的 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 中搜索 xorencryptdecryptkey 等字符串,有时候开发者会不小心把密钥注释在代码里。
  • 动态调试是王道:静态分析看不懂的时候,直接上调试器。在解密函数返回后,把内存 dump 出来,往往比分析算法快得多。
我的经验:80% 的配置文件加密都是 XOR 或简单替换。真正用 AES 加密配置文件的程序,要么是安全软件,要么是开发者太闲了。你想想看,谁会为了一个配置文件去引入 OpenSSL 库?

好了,这一节的内容就到这里。字符串加密、资源节隐藏数据、配置文件解密——这些都是逆向工程里最常遇到的场景。掌握了这些,你就能应付大部分「加密」的假把戏了。

记住,加密和解密是一场博弈。作为逆向工程师,我们的目标不是破解所有加密,而是找到那个最薄弱的环节——往往就是密钥本身。


公众号:蓝海资料掘金营,微信deep3321