---
name: re-crypto-id
description: >
  加密算法识别：常量表指纹、自定义加密模式。
  触发词：加密识别、AES、XOR、算法指纹、custom encryption
capabilities: [crypto-identification]
---

# 加密算法识别

## 何时使用 / 何时不用

- 用：拿到密文（流量或数据 blob）不确定是什么算法时
- 用：样本里有加密实现，需要在反编译前先锁定算法范围
- 用：怀疑自定义加密（XOR/ROL/ROR 变换）而非标准算法
- 不用：算法已知（直接用 [[re-crypto-keys]] 找密钥、[[re-crypto-decrypt]] 解密）
- 不用：标准库 API 调用清晰可见（`CryptEncrypt`/OpenSSL 符号可直接查——见 [[re-crypto-keys]] 导入表线索）
- 不用：纯静态就能判定是明文（[[re-triage]] 熵低/可读字符串多）

## 工具准备

所有工具先验证再使用。本技能以静态/离线分析为主，可免沙箱；动态确认环节（Frida）只针对已运行样本（默认沙箱，[[re-analyze/platform-tips]] 最高原则）。

### python3 —— 指纹与熵分析脚本

- 安装与验证见 [[re-proto-rev]] 工具准备（python3）

### binutils —— strings/objdump 取常量与反汇编线索

- Linux: `apt install binutils` / `dnf install binutils` / `pacman -S binutils`（多数自带）
- macOS: `brew install binutils`（或系统自带 otool 替代）
- Windows/WSL: WSL 内 Linux 版；Windows 本机用 Ghidra 自带工具
- 验证: `strings --version`；`objdump --version`

### hexdump —— 十六进制查看密文/常量

- 安装与验证见 [[re-fw-extract]] 工具准备（hexdump）

### Detect It Easy（DIE）—— 可选，快速签名识别（Windows 常用）

- Windows: GitHub releases 下载便携版 https://github.com/horsicq/Detect-It-Easy（`diec.exe` CLI / `die.exe` GUI）；`choco install die`（部分镜像有）
- Linux: AUR `yay -S detect-it-easy` 或 releases 的 Linux 版
- macOS: 源码构建（Qt 依赖）或 Wine 跑 Windows 版
- 验证: `diec --help` 输出用法；`diec sample.bin` 能输出签名

## 操作步骤

按顺序执行，每步记下结果。判定产物（算法假设 + 证据）传给 [[re-crypto-keys]] / [[re-crypto-decrypt]]。

1. **常量表指纹（AES S-box / CRC 表 / MD5 IV）**：
   ```sh
   # 静态数据段找常量表候选：连续 256 字节、熵低、无 ASCII
   strings -n 8 sample.bin | head -50
   objdump -s -j .data sample.bin | head -60
   # 搜索 AES S-box 开头（前 16 字节特征）
   python3 - <<'EOF'
   data = open('sample.bin','rb').read()
   aes_sbox = bytes.fromhex('637c777bf26b6fc53001672bfed7ab76')
   crc32_tab_be = bytes.fromhex('0000000077073096ee0e612c990951ba')   # 标准 CRC32 表前 16 字节（poly 0xEDB88320，显示序/BE）
   crc32_tab_le = bytes.fromhex('00000000963007772c610eeeba510999')   # 同一表在 x86/ARM 小端二进制内存中的字节序
   for name, sig in [('AES_SBOX', aes_sbox), ('CRC32_TAB(BE)', crc32_tab_be), ('CRC32_TAB(LE)', crc32_tab_le)]:
       i = data.find(sig)
       while i != -1:
           print(f"{name} @ 0x{i:x}"); i = data.find(sig, i+1)
   EOF
   ```
   - 命中 AES S-box（256 字节表）→ AES 候选；命中 CRC 表 → 有 CRC/校验（可能配合 [[re-proto-rev]] 步骤 3）；命中 MD5 IV → MD5 候选
   - 字节序：上面列出的 hex 均为显示序（BE 阅读序）。小端二进制（x86/ARM）里常量表在内存中的实际字节为 LE 序——CRC 表同时搜 `00000000963007772c610eeeba510999`（脚本已含），MD5 IV 显示序为 `67452301efcdab89...`，LE 序列化为 `0123456789abcdeffedcba9876543210`（两种模式都搜）
   - 没命中 → 不排除动态生成表（见坑 2），继续下一步

2. **熵分析定位密文（区分密文与明文区域）**：
   ```python
   data = open('sample.bin','rb').read()
   import math, collections
   for base in range(0, len(data), 4096):
       blk = data[base:base+4096]
       if not blk: break
       c = collections.Counter(blk); n = len(blk)
       h = -sum((v/n)*math.log2(v/n) for v in c.values())
       if h > 7.0: print(f"0x{base:x}: entropy={h:.2f}  <- 高熵区(密文/压缩候选)")
   ```
   - 高熵区（>7.0）→ 密文或压缩数据候选，记偏移供 [[re-crypto-decrypt]] 定位输入点
   - 低熵区但看起来"乱"（无 ASCII、无结构）→ 可能自定义加密或简单变换（下一步）

3. **XOR / ROL / ROR 单字节模式检测**：
   ```python
   data = open('sample.bin','rb').read()
   for key in range(256):
       dec = bytes(b ^ key for b in data)
       score = sum(1 for b in dec if 32 <= b < 127)
       if score > len(data) * 0.6: print(f"XOR key=0x{key:02x}, printable={score/len(data):.0%}")
   ```
   - 检测结果"可打印率 >60%" → 单字节 XOR；解密交给 [[re-crypto-decrypt]]
   - ROL/ROR：观察密文相邻字节关系（`x ^ rol(x)` 对同一 key 重复出现）；或找 256 轮换表（与 S-box 类似但值呈循环移位特征）
   - 有密码学直觉也行：单字节变换的结果通常保留原分布特征，先试最简单的再升级（见坑 1）

4. **常见算法流程特征（轮数 / 分组）**：
   - 反汇编/反编译里找特征函数形态：AES 有 10/12/14 轮（128/192/256 位）循环结构 + 常数表引用（配合步骤 1）；DES 有 16 轮 + 置换表（64 位分组）；RC4 有 256 字节 KSA/PRGA 循环
   - 数据侧：**长度只能辅助判断 mode，不能区分 primitive**。ECB/传统 CBC 等 pad 到整块的 mode 常见密文为块长整数倍；但基于分组密码的 CTR/OFB/CFB 可产出**与明文等长的任意长度**密文（NIST SP 800-38A 中 OFB/CTR 末块允许截到 u bit，CFB 的粒度是 segment size s），CBC-CTS 也能避免 padding 扩长。因此「长度任意」**推不出** RC4/XOR/ChaCha——同一份 37 字节明文，`aes-128-ctr/ofb/cfb` 密文都是 37 字节，`aes-128-cbc/ecb` 才是 48 字节
   - primitive 识别要靠**常量表 / 轮函数形态 / key schedule / IV-nonce-counter 数据流**：常量表指纹 + 轮数（如 AES 10/12/14 轮）定 primitive，长度与 mode 特征（是否需要 padding、是否有 IV/nonce 每次变化、counter 是否递增）定 mode。**AES 的 block size 恒为 128 bit（16 字节），192/256 是 key size**，不存在「24/32 字节分组」的 AES
   - 反编译工具有 auto-detection 时先用它（Ghidra 的 FindCrypt 脚本 / IDA 的 FindCrypt2）交叉确认
   - 反编译工具有 auto-detection 时先用它（Ghidra 的 FindCrypt 脚本 / IDA 的 FindCrypt2）交叉确认

5. **动态侧确认（Frida 断在加密函数）**：
   - 静态结论有歧义（多个候选）时，沙箱内（[[re-sandbox]]）运行样本，Frida hook 可疑调用：
     ```sh
     pip install frida-tools
     frida -p <pid> -l hook.js
     ```
     ```js
     // hook.js: 断在疑似加密函数，打印入参（密文/明文）与返回
     Interceptor.attach(Module.findGlobalExportByName("crypt_fn"), {   // Frida 17+：旧写法 Module.findExportByName(null, ...) 已移除
       onEnter(args) { console.log("arg0:", hexdump(args[0])); },
       onLeave(ret)  { console.log("ret:", hexdump(ret)); }
     });
     ```
   - 观察入参是否为高熵密文（对应步骤 2 的偏移）、返回是否变可读 → 确认该函数就是加密/解密点
   - hook 目标名不确定时先 `frida -p <pid> -l /dev/stdin` 里用 `Process.enumerateModules()` 找动态加载的加密库
   - 动态确认结果反哺静态假设：哪个候选函数真的吃到密文，就用哪个（见坑 4 的算法组合：最内层先确认）

## 跨域联合

- [[re-protocol]]：本网关工作流第 2 步（加密识别）——流量是密文时的必经环节
- [[re-malware]]：C2 通信加密识别（re-malware 第 4 步：netcap → crypto-id → crypto-keys → crypto-decrypt）
- [[re-firmware]]：固件内加密通信/加密固件层的算法识别（配合 [[re-fw-extract]] 解包失败时的加密层判断）
- [[re-crypto-keys]] / [[re-crypto-decrypt]]：下游——识别出算法后找密钥、写解密
- [[re-binary-core]]：反编译佐证（[[re-ghidra]] / [[re-ida]] / [[re-radare2]] 的 FindCrypt 类脚本）；动态确认在 [[re-sandbox]] 内
- [[re-anti-analysis]]：加壳样本先脱壳再做常量表指纹（壳层常量会污染指纹）

## 常见坑与陷阱

- **自定义加密先试简单模式（XOR）再升级**：现象——花半天做 AES 指纹，最后发现是单字节 XOR；原因——先入为主假设标准算法，没先做廉价检查；对策——步骤 3 的单字节 XOR/ROL/ROR 检测 30 秒内做完，再上常量表指纹与轮数分析（便宜假设先行）
- **表隐藏（动态生成）→ 指纹失效**：现象——静态数据段找不到 AES S-box/CRC 表，误判"非标准算法"；原因——算法运行时动态生成常量表（常见反分析手法，见 [[re-anti-analysis]] 域）；对策——步骤 5 动态确认：运行后内存（[[re-memdump]]）里搜表特征，或 Frida 断在轮函数看引用
- **算法组合（先 XOR 再 AES）需分层识别**：现象——按 AES 解出"明文"仍是乱码，或 XOR 检测可打印率不足；原因——多层加密叠加，单层假设不全；对策——先剥最内/最外层（观察哪个层次剥掉后熵下降、可读性上升），一层层确认，每层识别结果独立记录再组合（见步骤 5 的最内层优先原则）
- **把压缩当加密**：现象——熵 >7.0 高熵区按加密处理，解密脚本对不上；原因——zlib/LZMA 压缩同样高熵；对策——先看高熵区前 2-4 字节是否有压缩格式标识（gzip 头 `1F 8B`；`78 9C` 是常见 zlib CMF/FLG 组合而非 gzip，两字节不足以作唯一判据），有则先用 `zlib.decompress`/`binwalk`（见 [[re-fw-extract]]）试解压再谈加密
- **只搜 S-box 会漏掉变体实现**：现象——搜 256 字节 S-box 表没命中，误判"非 AES"，实际是 AES；原因——实现用位切片/即时计算 S-box（不存表），但密钥调度仍常保留 16 字节 Rcon 表，或改用 MixColumns 乘法表（GF(2^8) 乘 2/3/9/11/13/14）；对策——补充搜 Rcon 序列（`01 02 04 08 10 20 40 80 1B 36 ...`，0x1B 是特征值）与乘法表布局，多表交叉确认再定性
- **指纹命中 ≠ 加密函数在用**：现象——搜到 AES S-box/CRC 表就按该算法分析半天，实际业务是别的加密；原因——常量表可能来自未调用的静态库代码或壳层常量（先脱壳再指纹，见 [[re-anti-analysis]]）；对策——指纹命中后必须 xref 确认表被引用（谁引用、是否在加密路径上），与轮数/常量表布局/IV-nonce 数据流交叉（**不要用密文长度对齐做 primitive 判据**），动态侧（步骤 5）最终确认
- **常见签名模式速查**：现象——签名算法识别慢；原因——签名算法有固定模式族；对策——按模式快速对照：HmacSHA256(sorted_params, key) 最常见；MD5(params + salt + timestamp) 较老系统；AES(JSON.stringify(params), key) 是加密而非签名；RSA sign 少见（多为金融类）
（来源：reverse-skill field-journal，MIT）
