# 常见密码学验证模式

> AI 编排器在检测到密码学算法特征时通过 Read 工具按需加载。

## 触发条件

反编译/反汇编代码中出现以下特征时加载本文档：
- MD5 初始化常量：`0x67452301`, `0xEFCDAB89`, `0x98BADCFE`, `0x10325476`
- RSA 指数：`65537`（`0x10001`）
- 256 字节 S-box 初始化 + i/j 双指针交换（RC4）
- Base64 字母表：`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`
- Hex 查找表：`0123456789ABCDEF`

---

## ⚠ 强制执行纪律：先验证，再分析

识别到算法特征后，**禁止逐项检查常量**。必须按以下顺序操作：

### 步骤 1：确认算法身份

从反编译代码中快速确认：
- 几个初始化常量？（MD5 有 4 个，SHA1 有 5 个）
- 循环次数？（MD5 64 步，SHA1 80 步）
- 输出长度？（MD5 16 字节，SHA1 20 字节）

### 步骤 2：快速对比验证（强制）

用**标准库**对测试输入计算，与程序的实际输出对比：

```python
import hashlib
test_input = b"test"  # 用一个简单输入
standard_result = hashlib.md5(test_input).hexdigest()
# 对比：standard_result 是否与程序对同一输入的输出一致？
```

**对比方法**：
- 如果有动态分析能力（调试器/Frida/code cave）→ 直接喂测试输入到程序，读取 hash 函数输出
- 如果只能静态分析 → 确认输入数据后用标准库计算，追踪程序对输出做了什么

### 步骤 3：根据对比结果分支

- **一致** → 算法是标准实现，直接用标准库计算目标值。跳到数据流追踪。
- **不一致** → 算法有变体，进入"差异定位"流程。

### 差异定位流程（仅在步骤 2 不一致时执行）

按以下顺序排查差异点（**不要逐项检查所有常量**，只查到差异即停）：

| 检查项 | 方法 | 常见变体 |
|--------|------|---------|
| 1. padding | 追踪 bit_count / msg_len 计算 | padding bug（如 `bit_count = (len+1)*8` 而非 `len*8`） |
| 2. init 常量 | 检查前 4 个/5 个初始化值 | 自定义 init 值（替换标准值） |
| 3. 输入构造 | 确认 hash 的输入到底是什么 | 输入拼接方式不同（如 username + reversed(username)） |
| 4. round function | 对比每轮的操作 | 自定义轮函数（较少见） |
| 5. 字节序 | 检查 store/load 是否 little-endian | 大端序存储 |

**每检查一项后重新对比验证**。找到差异点后立即用修正后的实现重新计算。

---

## MD5 + 比较

### 识别特征

- 4 个初始化常量：`0x67452301`, `0xEFCDAB89`, `0x98BADCFE`, `0x10325476`
- 64 个轮常量（T table）
- 4 轮变换，每轮 16 步

### 常见变体

| 变体 | 说明 | 逆向注意 |
|------|------|---------|
| 比较全部 16 字节 | `memcmp(md5, expected, 16)` | 需要完整的 MD5 碰撞 |
| 比较前 8 字节 | `memcmp(md5, expected, 8)` | 只需要匹配前半部分 |
| 转为 hex 字符串再比较 | 每字节 → 2 hex 字符，共 32 字符 | 需要 hex 编码后再比对 |
| 分开使用前后半 | 前 8 字节用于一个检查，后 8 用于另一个 | 分别分析两个检查的逻辑 |
| sscanf 解析 | `sscanf(serial, "%lx%lx%lx", &a, &b, &c)` | 输入被解析为多个十六进制整数 |

### MD5 Context 结构

```
offset 0:   state[4]     — 4 个 uint32（16 字节）
offset 16:  count[2]     — 2 个 uint32（8 字节，位计数）
offset 24:  buffer[64]   — 64 字节输入缓冲区
offset 88:  digest[16]   — MD5_Final 后的 16 字节摘要
```

### 逆向策略

1. **先做快速对比验证**（上述步骤 2）
2. 确认 MD5 的输入（哪个字符串/数据被哈希）
3. 确认 MD5 的输出用在哪里（全部 16 字节？前 8？转 hex？sscanf 解析？）
4. 确认比较目标（硬编码值？动态计算值？GF(2) 线性变换？）

---

## RSA 签名验证

### 识别特征

- 常量 `65537`（`0x10001`）— 最常见的公钥指数
- 大数运算（多字整数乘法、模幂）
- `modpow` 函数：`base^exp mod n`

### 常见变体

| 变体 | 公式 | 逆向策略 |
|------|------|---------|
| 公钥验证 | `sig^e mod n == expected` | 找到 n → 分解 n → 计算 d → 伪造 sig |
| 私钥签名 | `msg^d mod n` | 找到 n、d |
| 数字字符串签名 | `BigInt(digits)^e mod n` | digits 是十进制大整数 |

### 逆向步骤

1. 找到模数 n（十六进制字符串或大端字节序列）
2. 分解 n → 得到 p、q（用 `factordb.com` 或 Python `sympy.factorint`）
3. 计算 `d = pow(e, -1, (p-1)*(q-1))`
4. 计算签名 `sig = pow(target, d, n)`
5. 验证：`pow(sig, e, n) == target`

---

## RC4

### 识别特征

- **KSA**：256 字节 S-box 初始化 + 两指针交换
- **PRGA**：i/j 双指针递增 + 交换 + XOR

### 伪代码

```python
# KSA
S = list(range(256))
j = 0
for i in range(256):
    j = (j + S[i] + key[i % len(key)]) % 256
    S[i], S[j] = S[j], S[i]

# PRGA
i = j = 0
for byte in data:
    i = (i + 1) % 256
    j = (j + S[i]) % 256
    S[i], S[j] = S[j], S[i]
    k = S[(S[i] + S[j]) % 256]
    output.append(byte ^ k)
```

### 关键特性

- **对称**：加密 = 解密（同一密钥）
- **密钥长度可变**：1-256 字节

### 逆向策略

1. 找到 RC4 密钥（通常硬编码）
2. 找到被加密/解密的数据
3. 用相同密钥重新加密/解密

---

## Base64 编解码

### 识别特征

- 字母表字符串：`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`
- 4 字符 → 3 字节映射
- `=` 填充字符

### 逆向注意

- 常用于编码 Serial（二进制 → ASCII）
- 解码后数据通常包含加密密文 + 数字签名

---

## Hex 编解码

### 识别特征

- 查找表：`0123456789ABCDEF`（或小写）
- 每字节拆为高/低 nibble

### 常见用途

- 二进制数据（MD5 hash）→ hex 字符串比较
- hex 字符串 → 大整数（RSA 比较）

---

## 组合模式

### 最常见组合

```
Base64Decode(Serial)
  → 前 N 字节：RC4 解密 → 与 MD5(Name) 的某部分比较
  → 后续字节：十进制数字字符串 → BigInt(digits)^e mod n == BigIntFromHex(MD5(Name))
```

### 识别策略

1. **从字符串反推**：找到 Base64 字母表 → 引用函数 → 确认 Base64
2. **从常量反推**：找到 MD5 init 常量 → 确认 MD5
3. **从大数反推**：找到 `65537` → modpow → 确认 RSA
4. **从 S-box 反推**：256 字节初始化循环 → 确认 RC4
5. **从 hex 表反推**：`0123456789ABCDEF` → hex 编解码

### 数据流追踪

**关键**：逐字节追踪比较操作，确认两个操作数分别来自哪里。不要假设——从反编译代码中确认。

---

## 验证策略（强制）

> 完整的验证决策树、方案模板和判断标准见 `verification-patterns.md`。
> 本节保留核心原则：

1. 先定位验证函数 → 能定位走直接调用（Unicorn/ctypes/Hook），不能定位走程序运行（subprocess/ctypes/gui_verify.py）
2. **绝对禁止**作弊式验证（用自己重实现代码验证自己重实现结果）
3. 验证优先用 Hook 读返回值（代码层面 100% 可靠），后备观察程序多维行为

### 模拟执行 vs 手动重实现

**何时手动重实现**（必须满足至少一条）:
- Unicorn 无法模拟（特殊硬件指令、自修改代码）
- 需要大量测试不同输入（Unicorn 单次调用开销大）
- 需要理解算法内部细节以进行修改

**其他情况** → 优先模拟执行（避免重实现引入 bug）。
