逆向工程实战手册 第 4 课

第四课:钥匙被锁进了盒子——初识加密与算法

对应原书第 9 章《常见加密与算法模式识别》的入门部分


第四天,陈师傅又拿出那只钟。这次,锁孔里塞着一张纸条,上面写着:

19640807

"这不是昨天那把钥匙吗?"小林说,"锁已经开了呀。"

"开了,但里面还有一层。"陈师傅说,"昨天打开的是外锁,里面这扇门,钥匙已经不是数字了,是'一句话'。而那句话……"他顿了顿,"被写的人用'密语'藏起来了。"

陈师傅指着钟里面的一圈小齿轮:"你看这些齿轮的齿,有的被磨平了,有的被加高——这圈齿轮就像一台'绞肉机'。任何话塞进去,出来就变成一堆乱码。写程序的人,把真正的钥匙先塞进绞肉机,再存起来。你拿到的永远是'绞出来的肉',不是'原来的肉'。"

小林皱眉:"那怎么办?还能拿回原来的话吗?"

"能。关键看你懂不懂这台绞肉机的'转法'。"陈师傅笑了,"而且,写程序的人有个坏毛病——他们总爱用同一台绞肉机。我们只要知道转法,把'肉'反着绞一遍,就还原了。"


课程正文

在程序里,"加密"最常见的形态不是高级密码学,而是程序员自己的小聪明——XOR(异或)

XOR 是什么(一分钟学会)

XOR 是个运算,记作 ^,规则就一句话:

相同为 0,不同为 1

它的神奇之处:同一个数 XOR 两次,等于什么都没做。

A ^ K = B        (A 用 K 加密成 B)
B ^ K = A        (B 再用 K 解密回 A)

所以 XOR 自带"加密+解密"两用功能。程序里大量出现的 ^ 操作,九成都是这个套路。

实战例子:单字节 XOR

假设程序里有段"密文":

0x9d 0x1e 0xf2 0x3a ...

反编译后你看到一行:

out[i] = cipher[i] ^ 0x5A;    // 每个字节 XOR 0x5A

解密就是再 XOR 一次 0x5A:

key = 0x5A
flag = bytes(c ^ key for c in cipher)
print(flag)     # 原文出现!

关键思路:知道 key,解密就是一行代码。不知道 key?那就试——一共只有 256 种可能!

穷举:不知道 key 也能解

for key in range(256):                    # 256 种 key 全试一遍
    out = bytes(c ^ key for c in cipher)
    if b"flag{" in out or b"secret" in out:   # 出现"人话"就是它!
        print(f"key={key} -> {out}")

这就是暴力破解(爆破)最经典的样子:空间小到可以穷举,就枚举所有可能。

两个识别信号(看到就警觉)

1. 反编译代码里出现很多 ^ 0xXX 或 ^ i
2. 数据区有一段"看起来像乱码、但长度很整齐"的数据

只要看到这两个信号,就可以套"XOR + 穷举"的流程。

如果 key 是变长的(循环 XOR)

密文 = 明文 ^ (i % key_len) 位置的 key 字节
→ 先猜 key 长度(比如 3~16)
→ 对每个长度,按位置分组统计
→ 每一组用单字节 XOR 的方式解
→ 找到让整段变成"人话"的长度和 key

陈师傅说:"绞肉机再花哨,绞出来的肉也逃不过一个事实——它原本是块正常的肉。" 只要结果看着像人话,方向就对了。


本课口诀

XOR 两次等于没做。密钥只有 256 种?那就穷举。

动手做

  1. 用 Python 写一段"XOR 加密",再把"穷举 256 种 key"的脚本跑一遍,亲手感受解密。
  2. 找一个带 XOR 校验的小程序(或让 AI 生成),用反编译找到 key 和密文。
  3. 想一想:如果密文不是 XOR 而是"查表"(256 字节的表),怎么还原?(答案:建反向表)

想学更深的

见原书 第 9 章《常见加密与算法模式识别》(含完整 4 层加密链实战)、第 22 章《枚举与爆破》