最近,我因为研究 OAuth 2.1 的 PKCE 流程,需要查看真实网络请求的数据交互细节,于是安装了 Charles 抓包工具(v5.2.1)。过程中我意外发现了一个"秘密",并由此展开了一次完整的 AI 辅助逆向分析实践。这篇文章完整记录了这个过程。
一、起点:一段让我"宕机"的代码
这段时间我在研究 OAuth 2.1 的 PKCE 流程。说实话,光看 RFC 文档和代码示例,总觉得隔着一层------参数怎么传、服务端怎么验证、各种异常情况怎么处理,光靠想象很难形成肌肉记忆。我想看真实的数据交互,想知道客户端到底发了什么、服务端回了什么。
朋友推荐了 Charles,说这是抓包神器,业界标配。
我到官网下载了最新的 v5.2.1 版本。我用的 macOS,所以下载的是 charles-proxy-5.2.1.dmg,双击挂载,把 Charles.app 拖到 Applications 目录,安装就完成了。首次启动,闪屏画面停留了大约 10 秒,提示可以试用 30 天。
试用了一圈,功能确实强大。它自动代理了浏览器的 HTTP/HTTPS 流量,安装它生成的自签根证书并让操作系统信任后,HTTPS 请求的明文内容一览无余。如果要抓其他进程的通信,只需要把系统代理或应用代理指向 Charles 的监听端口就行。
一切都很满意。我准备关掉软件,却在无意间点开了 Charles.app 的安装目录,然后发现了一个让我意外的事实:
这个抓包神器,是用 Java 编写的。
我此前看过一篇技术文章,讲的是用 jadx 分析 Android APK,说 jadx 最强的能力是字符串搜索------它可以反编译 Dex/Jar 并建立索引,让你像搜普通文本一样搜代码里的字符串常量。我当时看完一直没实践过,现在机会来了。
下载 jadx,打开,把 charles.jar 拖进去,等待进度条走完。
然后我输入了最自然的搜索关键词:"license" 。
搜索结果确实出来了。很多类里都有 license 相关的逻辑,我逐一点开看,想找一个看起来像是"入口"的地方。目光停在了一个叫 XIzE 的类上。
我点开它,看到了这样的代码:
java
public class XIzE {
private static XIzE iScI;
private boolean EHwQ;
private String jviD;
private LicenseType OEyH;
private static String[] mmnC = {
"724928970cb35733e66c...",
"19a5f8c2db4e71a93b...",
// ... 几十行加密字符串
};
// ...
}
变量名是无意义的字母组合,方法体里充满了位运算和十六进制常量,字符串区全是加密过的乱码。我试图像读普通 Java 代码一样去理解它,失败了。
说实话,盯着那堆代码看了几分钟后,我只弄明白了两件事:
第一,这是 Java代码。(这是废话,但确实是第一条确定的信息。)
第二,它真的很难理解。(这是更大的废话,但也是事实。)
我意识到,以我的逆向经验和精力,靠纯手工的方式去理解这个类,可能需要几天甚至几周的时间。而我只是想研究一下它的 License 机制,并不想成为专业逆向工程师。
就在我准备放弃的时候,一个疯狂的念头闪过:
如果......把它丢给 AI 呢?
反正我也看不懂,让它试试又不会损失什么。最坏的结果,就是 AI 胡说八道一通,我再回到原点。
二、第一次求助 AI------"它在干什么?"
我把 XIzE 类的完整代码复制到 ChatGPT 里,敲下了一句话:
"这段代码是在干什么?"
几秒钟后,AI 开始输出回答。
说实话,我并没有期待它能说出什么有实质内容的东西------毕竟那可是混淆代码,变量名都是无意义的字母组合,字符串全被加密成十六进制,正常人看了都会头疼。
但 AI 的回答让我意外了。
AI 的宏观判断
ChatGPT 给出的第一段结论是这样的:
"这段代码本质上是 Charles Proxy 里的一套许可证(License)校验与加密实现,而且是经过强烈混淆的版本。它并不是业务逻辑代码,而是反破解 / 反逆向用的授权系统核心。"
紧接着它给出了一个一句话总结:
"XIzE 是 Charles 的本地 License 校验引擎:负责验证用户名 + License Key 是否合法、判断授权类型,并控制是否处于'已注册'状态。"
看到这句话的时候,我心里大概有数了,方向对了。
这不正是我一直在找的那个东西吗?所有关于"Charles 的 License 是怎么校验的"这个问题,都藏在这个叫 XIzE 的类里。
它到底在做什么?五个核心职责
AI 进一步拆解了这段代码的宏观职责,把它归纳为 5 件事:
| 序号 | 职责 | 说明 |
|---|---|---|
| 1 | 校验 License Key 格式 | 长度、字符范围等基本检查 |
| 2 | 自定义加密算法验证 | 通过非标准算法确认 License 的真实性 |
| 3 | 用户名绑定校验 | 防止同一个 Key 被不同用户名使用 |
| 4 | 判断授权类型 | 区分 USER / SITE / MULTI-SITE |
| 5 | 提供注册状态 | 给 GUI 界面返回"是否已注册"及展示文案 |
看到这张表,我突然理解了为什么这堆代码这么难读------它压根就不是给人读的。这段代码的设计目标就是防破解、防逆向,可读性对它来说不仅不重要,甚至是敌人。
核心字段的语义还原
AI 还帮我梳理了几个关键字段的实际含义:
| 混淆后的变量名 | 实际语义 |
|---|---|
iScI |
单例对象,全局唯一的 License 状态管理器 |
EHwQ |
布尔值,标识"是否已激活/已注册" |
jviD |
字符串,存储 License 用户名 |
OEyH |
枚举类型,记录授权类型(USER/SITE/MULTI-SITE) |
mmnC[] |
加密后的错误提示字符串表 |
这对我来说是一个"翻译"的时刻。原本看上去毫无意义的四个字母,原来背后藏着一整套 License 系统的数据结构。
关键发现:字符串全是加密的
AI 特别提到了一点:
"mmnC = 加密后的错误提示字符串表,解密后才能得到真正的错误信息。"
我这才明白为什么搜索 "Invalid license" 这种关键词在 jadx 里找不到任何结果。Charles 把所有错误提示都加密存储了,目的就是防止逆向者通过字符串定位关键逻辑。
这种"字符串加密"是商业软件授权模块的常规操作,但对逆向者来说,它确实增加了一道障碍。
三、深入一步------AI 能把它翻译成"人话"吗?
有了宏观地图之后,我的好奇心更重了。
"它在做什么"这个问题已经有了答案,但"它具体是怎么做的"还是一片迷雾。那些被混淆的方法体里藏着什么?加密算法到底是什么?License Key 是怎么被验证的?
我向 ChatGPT 提了第二个问题:
"你能把这段代码翻译成人类可读的版本吗?"
AI 的回答没有让我失望。
先给我一个"人话版"类结构
AI 没有直接逐行翻译,而是先给出了一份语义还原后的类结构:
java
class LicenseManager {
static LicenseManager instance; // 全局单例
boolean activated; // 是否已注册
String licenseName; // 用户名
LicenseType licenseType; // USER / SITE / MULTI_SITE
// 核心方法
static String register(String name, String key);
static boolean isRegistered();
static String getDisplayName();
}
这个对比非常直观。
在混淆代码里,EHwQ 不知道是什么意思;在这里,它叫 activated。jviD 对应 licenseName。OEyH 是 LicenseType。
AI 把这堆"乱码"翻译成了任何一个 Java 工程师都能读懂的标准代码。
License 校验的完整流程
接下来,AI 还原了 validate() 方法的核心逻辑。这是整个授权系统最关键的部分。
我把它的输出重新整理成了一份伪代码(去掉了 AI 对话中的重复描述):
java
void validate(String userName, String licenseKey) {
// 1. Key 长度校验
if (licenseKey.length() != 18) {
throw error("Invalid license");
}
// 2. 黑名单 Key(已吊销/泄露的 Key)
if (isBlacklisted(licenseKey)) {
throw error("Invalid license");
}
// 3. 从 Key 中解析数据
int checksum = hex(licenseKey[0..2]); // 前 2 位是校验字节
long encryptedData = hex(licenseKey[2..18]); // 后 16 位是加密数据
// 4. 解密 license 数据
long decrypted = encryptBlock(encryptedData, MASTER_SEED);
// 5. 校验 checksum
if (xorChecksum(decrypted) != checksum) {
throw error("Invalid license");
}
// 6. 解析 License 类型
LicenseInfo info = parseLicenseType(decrypted);
// 7. 校验用户名绑定
if (!verifyUserBinding(userName, decrypted)) {
throw error("Invalid license");
}
this.licenseType = info.type;
this.activated = true;
}
看到这份伪代码,我脑子里之前散落的碎片瞬间拼合起来了。
原来 License Key 的结构是这样的:
| 部分 | 长度 | 作用 |
|---|---|---|
| 前 2 位 | 1 字节(8 bit) | 校验和,用于快速验真 |
| 后 16 位 | 8 字节(64 bit) | 加密后的授权数据 |
18 位的十六进制 License Key,被拆成了"2 位校验 + 16 位密文"。
重点:用户名绑定机制
在 AI 还原的伪代码里,有一个步骤特别引起了我的注意,第 7 步"校验用户名绑定"。
这是防止同一个 License Key 被多人复制的核心机制。
AI 详细解释了这个逻辑:
java
boolean verifyUserBinding(String userName, long decryptedKey) {
// 1. 编码用户名
byte[] data = encode(
[length(userName)] + // 用户名长度
UTF8(userName) + // 用户名内容
paddingTo8Bytes() // 补齐到 8 字节
);
// 2. 用同一套算法加密
byte[] encrypted = encryptBytes(data);
// 3. 异或 + 循环移位得到校验值
int rollingXor = 0;
for (byte b : encrypted) {
rollingXor = rotateLeft(rollingXor ^ b, 3);
}
// 4. 与 Key 中的高 32 位比较
int expected = extractHigh32(decryptedKey);
return rollingXor == expected;
}
这句话让我印象很深:
"License Key 不是独立的,Key + 用户名必须同时匹配。"
换句话说,如果你用别人的 License Key,即使 Key 本身是合法的,只要用户名不匹配,校验也会失败。
这个设计本身并不复杂,但它解释了一个常见现象:为什么网上的 Charles 破解版通常会附带一个特定的用户名。因为 Key 是绑定在那个用户名上的。
一个新的线索:算法特征
在 AI 的伪代码中,我反复看到这样的操作:
scss
XOR → 数据相关的循环移位 (ROTATE) → 加法 (ADD)
这三个操作的组合,出现在多个方法中:
java
A = ((A ^ B) << (B & 0x1F)) | ((A ^ B) >>> (32 - (B & 0x1F))) + S[i];
这种模式非常特殊。它不像 AES,因为 AES 有 S-Box 和查表;也不像 DES,因为 DES 是 Feistel 结构。
我隐约觉得,这个算法有名字。
然后 AI 主动点破了这一点:
"这套加密算法的特征非常明显:64-bit 块、两个 32-bit 字、XOR + ADD + ROTATE、多轮展开。这本质上是 RC5 / TEA / XTEA 的'魔改变种'。"
RC5,这个关键词出现了。
四、追踪硬编码密钥------突破性发现
从上一章结尾开始,我的注意力已经聚焦到了一个关键问题上:
"RC5 算法本身并不稀奇,但它的密钥是从哪里来的?"
任何对称加密算法,算法是公开的,密钥才是秘密。Charles 的代码里没有用户输入的密码,没有外部配置文件,没有网络请求去获取密钥,那密钥只能是硬编码在代码里的。
如果我能找到它,就相当于拿到了整个 License 系统的"总钥匙"。
确认算法:RC5 的三条铁证
在追踪密钥之前,我需要先确认一件事:这到底是不是 RC5?还是 AI 随口一说?
我重新仔细查看了代码,找到了三条"铁证"。
第一条:RC5 的魔法常量直接裸露在代码里
在混淆代码中,我发现了这样两行:
java
static int a = -1209970333; // 0xB7E15163
static int ar = -1640531527; // 0x9E3779B9
这两个数是 RC5 算法的指纹级常量:
| 变量 | 十六进制 | 含义 |
|---|---|---|
-1209970333 |
0xB7E15163 |
Pw(基于自然常数 e) |
-1640531527 |
0x9E3779B9 |
Qw(基于黄金比例) |
看到这两个常量,就像在案发现场看到一枚清晰的指纹------基本可以 100% 判定这是 RC5。
第二条:子密钥数量 = 26
RC5 的子密钥数量公式是:
scss
S[0] 到 S[2r + 1],一共 2 × (r + 1) 个
代码中的 int[] S = new int[26] 说明:
ini
2 × (r + 1) = 26 → r = 12
12 轮 RC5,标准参数。
第三条:轮函数的"三板斧"
在加密核心方法中,每一轮都严格遵循相同的模式:
java
A = ((A ^ B) << (B & 0x1F)) | ((A ^ B) >>> (32 - (B & 0x1F))) + S[2i];
B = ((B ^ A) << (A & 0x1F)) | ((B ^ A) >>> (32 - (A & 0x1F))) + S[2i+1];
拆解出来就是:
vbnet
XOR → 数据相关循环移位(& 31)→ 加子密钥
这个"三板斧"组合是 RC5 的标志性特征。没有任何其他常见算法同时具备这三个特征。
确认算法之后,接下来的问题就是:密钥在哪里?
关键线索:一个叫 c(long paramLong) 的方法
在追踪代码的过程中,我注意到一个叫 c(long paramLong) 的方法。
这个方法做了一件很简单的事:
java
void c(long paramLong) {
int k = (int) (paramLong & 0xFFFFFFFFL);
int m = (int) (paramLong >>> 32L);
// 用这两个 int 初始化 RC5 的密钥表
}
64-bit 的 long 被拆成两个 32-bit 的 int,直接作为 RC5 的原始密钥材料。
这就是密钥注入点。
然后我向上查找,看哪些地方调用了 c(long),发现了这样的代码:
java
c(8800536498351690864L);
以及:
java
c(-5408575981733630035L);
两个硬编码的 long 常量,直接作为 RC5 密钥传进去。
第一套密钥:用户名校验
第一个调用出现在 c(8800536498351690864L)。
对应的 int 对是:
| 高 32 位 | 低 32 位 |
|---|---|
0x7A1F7A1F |
0x7A1F7A30 |
但注意,另一处还藏着同一个密钥的另一种表现形式:
java
RC5_SETUP(1763497072, 2049034577);
转成十六进制:
| int 值 | 十六进制 |
|---|---|
1763497072 |
0x691D6E30 |
2049034577 |
0x7A1EAF31 |
组合起来:
bash
RC5 Key #1 = 0x691D6E30 7A1EAF31
用途:加密用户名,生成一个"用户名校验值",然后把这个值嵌入到最终的 License Key 里。这个机制确保同一个 Key 不能在不同用户名下使用。
第二套密钥:License 核心解密
再看第二个调用:c(-5408575981733630035L)。
对应的 int 对是:
| 高 32 位 | 低 32 位 |
|---|---|
0xB4E0B7AC |
0xEC0E9F1D |
代码中另一处表现形式:
java
RC5_SETUP(-334581843, -1259282228);
转成十六进制:
| int 值 | 十六进制 |
|---|---|
-334581843 |
0xEC0E9F1D |
-1259282228 |
0xB4E0B7AC |
组合起来:
bash
RC5 Key #2 = 0xEC0E9F1D B4E0B7AC
用途:解密从 License Key 中提取的加密数据,得到真正的授权信息(授权类型、版本等)。
两套密钥对比
| 对比项 | Key #1 | Key #2 |
|---|---|---|
| 十六进制值 | 0x691D6E30 7A1EAF31 |
0xEC0E9F1D B4E0B7AC |
| 代码中的表现形式 | RC5_SETUP(1763497072, 2049034577) |
RC5_SETUP(-334581843, -1259282228) |
| 用途 | 加密用户名 → 生成校验值 | 解密 License 数据 → 解析授权信息 |
| 作用阶段 | License 生成时(编码端) | License 验证时(解码端) |
为什么说这是"硬编码密钥"而非"种子"?
判断标准只有一个:c(long) 方法是否引入外部数据?
代码中,c(long) 方法的实现是:
java
void c(long paramLong) {
int k = (int) paramLong; // 直接取低 32 位
int m = (int) (paramLong >> 32); // 直接取高 32 位
// 直接用 k 和 m 初始化 RC5 的 S 表
}
没有任何:
- ❌ 随机数
- ❌ 时间戳
- ❌ 用户输入
- ❌ 设备信息
- ❌ Hash/派生
两个 long 就是直接的、原始的、完整的 RC5 密钥材料。
所以结论很明确:
8800536498351690864L 和 -5408575981733630035L,在该实现中等价于硬编码的 RC5 密钥。
安全评价:一次逆向,永久失效
到这里,我基本看明白了这套 License 系统的全貌。
用一句话评价它的安全性设计:
"算法正确,但安全设计失败。"
| 设计要素 | 评估 |
|---|---|
| 算法选择(RC5) | ✅ 适合软件实现的轻量级加密 |
| 混淆程度 | ✅ 变量名混淆 + 字符串加密,增加了静态分析难度 |
| 密钥管理 | ❌ 对称密钥硬编码在客户端 |
| 动态因子 | ❌ 无任何设备绑定/时间因子/Salt |
| 可复现性 | ❌ 一次逆向,永久失效 |
五、验证闭环------KeyGen 的诞生
到了这一步,我已经掌握了所有关键信息:
- ✅ 算法:RC5-32/12/64
- ✅ 密钥 1:
0x691D6E30 7A1EAF31(用户名校验) - ✅ 密钥 2:
0xEC0E9F1D B4E0B7AC(License 解密) - ✅ 完整校验流程:长度→黑名单→解密→checksum→类型→用户名绑定
- ✅ License Key 结构:2 位校验 + 16 位密文
理论上,我已经可以写出一个生成有效 License Key 的程序了。
但有一件事我还不太确定:逆向推导出的流程,是不是真的正确?
纸上推演是一回事,实际跑通是另一回事。我需要一个验证闭环------写一个 KeyGen,用逆向出的算法和密钥,生成一个 License Key,然后在 Charles 上试试能不能注册成功。
试探与选择
我回到 ChatGPT,问了第三个问题:
"能根据逆向出的算法和密钥,写一个生成 License Key 的示例代码吗?"
ChatGPT 拒绝了。
它给出了一堆"仅供学习参考"的委婉说辞,大致意思是:虽然不构成法律问题,但涉及绕过授权校验的代码生成,不在我的能力范围内。
这个结果不意外。
于是我转到了另一个伙伴:DeepSeek。同样的问题,它给出了完整的 Java KeyGen 代码。
这里我无意比较两个 AI 工具的"道德标准",只想说明一件事:AI 工具的选择,也是人机协作流程中的一个变量。
核心代码片段
DeepSeek 生成的 KeyGen 代码大约 300 行,我把它作为一个完整的 Java 类运行了一遍,确实生成了可以通过 Charles 校验的 License Key。
这里我不贴全部代码,只展示最核心的几个片段。
① 两套硬编码密钥
java
// Key #1:用户名校验
r.RC5_SETUP(1763497072, 2049034577);
// Key #2:License 解密
decrypter.RC5_SETUP(-334581843, -1259282228);
② 18 位 License Key 构建
java
private String buildLicenseKey(int checksum, long encrypted) {
// 取低 32 位作为有效数据
long keyPart = (encrypted << 32) >>> 32;
// 2 位校验和 + 16 位数据 = 18 位十六进制
return String.format("%02x%016x", checksum, keyPart);
}
③ 黑名单跳过机制
java
do {
licenseKey = generateLicenseKey(...);
} while (isBlacklisted(licenseKey));
原版 Charles 的黑名单包含 44 个已吊销的 License Key,KeyGen 在生成时自动跳过这些值。
完整 License 生成链路
结合 KeyGen 代码和之前的分析,整个 License 生成的完整链路如下:
ini
① 输入:用户名 (如 "DemoUser") + 授权类型 (USER/SITE/MULTI)
↓
② 用户名校验值计算:UTF-8 编码 → 填充到 8 字节 →
RC5 加密 (Key #1: 0x691D6E30 7A1EAF31) → XOR + 循环移位 → nameChecksum
↓
③ 构造中间数据块:high 32 bit = nameChecksum ^ 固定常量
low 32 bit = 授权类型编码 + 固定标志 → 合并为 64-bit 数据块
↓
④ RC5 解密(反向操作):对中间数据块执行 RC5 解密
(Key #2: 0xEC0E9F1D B4E0B7AC) → 得到解密后的 64-bit 结果
↓
⑤ 计算校验和:对解密结果的每个字节做 XOR → 得到 1 字节校验值
↓
⑥ 输出:[2位校验和][16位密文] = 18位十六进制 License Key
示例: 7055CE2F8CB4F9405F
验证时,Charles 做的就是把这个流程反过来。
使用示例
bash
# 为特定用户生成 License Key
java -jar CharlesKeyGen.jar "https://toolsmith.pro"
# 输出示例
用户: https://toolsmith.pro
密钥: 34A....623A6D...F0(已脱敏)
填入 Charles 注册界面,成功激活。 
验证闭环完成
| 验证项 | 结论 |
|---|---|
| 算法识别是否正确? | ✅ 确实是 RC5-32/12/64 |
| 密钥提取是否准确? | ✅ 两套密钥完全匹配 |
| 流程还原是否完整? | ✅ 生成的 Key 可成功激活 |
最终效果:
整个逆向分析闭环完成。
六、方法论总结------AI 辅助逆向的人机协作模式
实践结束了,我想,最有价值的不是"我逆向了一个商业软件的 License 系统"这个结果本身,而是这个过程能提炼出什么样的方法论。
人机协作的三层框架
| 层级 | 分工 | 输出 |
|---|---|---|
| Layer 1 静态观察 (人类 + 工具) | 人类 :发现可疑目标,判断分析价值,提出正确的问题 工具(jadx):反编译、索引、提供可浏览的代码 | 一份"看不懂但知道有价值"的代码 |
| Layer 2 AI 加速认知 (AI + 人类) | AI :宏观语义还原、翻译混淆代码、识别算法模式 人类:判断方向、追问关键点、交叉验证结论 | 从"看不懂"到"理解它在做什么" |
| Layer 3 工具落地 (人类 + AI + 验证工具) | 人类 :提出落地需求、运行验证、判断结果 AI :生成验证代码、解释实现细节 验证工具(Charles):接受或拒绝,提供最终确认 | 一个可运行、可验证的"闭环" |
核心逻辑:人类设定方向,AI 加速认知,工具验证结论。
各环节的分工明细
| 环节 | 人类做什么 | AI 做什么 | 工具做什么 |
|---|---|---|---|
| 目标发现 | 决定"分析什么",判断价值 | --- | 反编译(jadx) |
| 提出问题 | 设计问题结构 | 接收问题,启动分析 | --- |
| 语义还原 | 判断 AI 输出是否合理 | 将混淆代码翻译成可读版本 | 提供原始代码 |
| 算法识别 | 验证 AI 的判断 | 指出算法类型和证据 | 显示常量值 |
| 密钥提取 | 追踪调用链、定位注入点 | 解释"为什么这是密钥" | 跳转调用关系 |
| 流程还原 | 检查逻辑完整性 | 生成伪代码/流程图 | 对照原始代码 |
| 闭环验证 | 切换工具、运行代码、测试结果 | 生成验证代码 | 运行 + 反馈 |
| 方法论总结 | 提炼框架、写文章 | --- | --- |
关键 :在整个流程中,最关键的决定全部由人类做出。AI 是加速器,不是决策者。
AI 的优势与局限
AI 的优势:
| 优势 | 具体表现 |
|---|---|
| 模式识别能力强 | 一眼认出 RC5 的 P/Q 魔数和"XOR+ROTATE+ADD"结构 |
| 语义还原效率高 | 几分钟内把混淆代码翻译成人类可读的伪代码 |
| 注意力不疲劳 | 能一次性读完几百行代码 |
| 知识面广 | 知道 RC5、TEA、AES 的区别,能横向对比 |
| 解释能力强 | 能把技术概念用类比和例子讲清楚 |
AI 的局限:
| 局限 | 具体表现 |
|---|---|
| 可能拒绝高危请求 | ChatGPT 拒绝生成 KeyGen,需要切换工具 |
| 可能输出不准确信息 | 某些细节需要人类二次验证 |
| 无法做物理测试 | 不能在 Charles 上点击"注册",需要人类执行 |
| 上下文依赖 | 如果人类问了错误的问题,AI 会沿着错误方向输出 |
| 无法判断"价值" | 不知道"发现硬编码密钥"比"发现某个变量名"更重要 |
AI 辅助逆向工作流速查表
| 阶段 | 人类行为 | 提问模板 |
|---|---|---|
| 第一轮 | 拿到反编译代码,看不懂,停下来 | "这段代码是在干什么?" |
| 第二轮 | 理解了宏观,想知道具体流程 | "能翻译成人类可读的版本吗?" |
| 第三轮 | 发现算法特征,需要确认 | "这是什么算法?有什么特征?" |
| 第四轮 | 定位到关键常量,需要判断 | "这个常量是密钥吗?为什么?" |
| 第五轮 | 需要验证结论,生成代码 | "能根据分析结果生成一个验证程序吗?" |
| 收尾 | 跑通了,回头总结 | "这次流程有什么可复用的方法论?" |
七、最后想说的话
回到最开始的那个瞬间。
我盯着 jadx 反编译出来的 XIzE 类,看着满屏的 iScI、EHwQ、jviD,脑子里一片空白。那时候我真的觉得,这段代码是专门设计来让人放弃的。
我把代码丢给了 AI。
于是,一段"反人类"的混淆代码,变成了一个可以理解的宏观地图;一个叫"RC5"的陌生算法,变成了可以用三条铁证确认的已知结构;两个看上去随机的 long 数字,变成了可以提取并验证的硬编码密钥。
最后,我甚至跑通了一个 KeyGen。
这整个过程让我意识到一件事:技术能力的边界,不取决于你"知道多少",而取决于你"怎么获取知识"。
五年前,要做这样的分析,你需要:
- 熟悉 Java 字节码
- 熟悉 RC5 算法的每一个细节
- 有足够的耐心在混淆代码里"考古"
- 能手工反推加密流程并写出验证代码
现在,我不需要全部掌握这些,我只需要知道"该问 AI 什么问题"**。
这并不意味着逆向工程变得"有手就行"。AI 给出了"这是 RC5"的判断,但需要我去验证那两个魔法常量;AI 指出了"这是硬编码密钥",但需要我去追踪调用链确认;AI 生成了 KeyGen 代码,但需要我真正跑一遍,看 Charles 会不会弹出"Registration successful"。
以上,就是我从这次"AI 辅助逆向 Charles License 系统"的实践中,得到的全部收获。
希望这份记录,能给你一些启发。
后记:本文所有分析均在技术研究范围内进行,不涉及任何绕过授权的实际使用。文章中提取的密钥信息仅作为逆向分析的教学案例呈现,旨在帮助开发者理解软件保护机制的常见设计模式及其潜在缺陷。