---
name: re-crypto-decrypt
description: >
  加密数据还原：定位解密函数、写解密脚本。
  触发词：解密、decrypt、还原数据、解密流量
capabilities: [crypto-decryption]
---

# 加密数据还原

## 何时使用 / 何时不用

- 用：需要把密文（数据 blob / 流量 / 配置段）还原成明文
- 用：算法与密钥已知（或已由 [[re-crypto-id]] / [[re-crypto-keys]] 得出），需要批量解密
- 用：从样本里还原解密逻辑并重写为独立可复用的脚本
- 不用：算法/密钥都未知（先 [[re-crypto-id]] → [[re-crypto-keys]]）
- 不用：静态可读的明文（[[re-triage]] 熵低直接读）
- 不用：想跑原样本看输出（那是 [[re-behavior]] / [[re-sandbox]] 的活——解密脚本是为了脱离样本复现）

## 工具准备

所有工具先验证再使用。本技能处理的是转储/反编译产物与密文数据，运行样本环节在 [[re-sandbox]] 内（[[re-analyze/platform-tips]] 最高原则）。

### python3 + pycryptodome —— 解密脚本主力

- Linux: `apt install python3 python3-pip` / `dnf install python3 python3-pip` / `pacman -S python python-pip`
- macOS: `brew install python`
- Windows: python.org 安装包（勾选 Add to PATH）；WSL 内 Linux 版
- 加密库: `pip install pycryptodome`（AES/DES/RSA/ChaCha 等标准算法）
- 验证: `python3 -c "from Crypto.Cipher import AES; print('ok')"`；`python3 --version`

### 目标程序转储/反编译产物 —— 还原算法的依据

- 转储: [[re-memdump]] 默认转储（gcore）——定位密文输入点与解密调用现场
- 反编译: [[re-ghidra]] / [[re-ida]] / [[re-radare2]] 的产物（函数反编译视图）
- 验证: `file out` 是 ELF core；反编译器里能找到目标函数（`ghidra` / `rizin` 可启动）

### angr（可选）—— 符号执行补足难还原的逻辑

- 全平台: `pip install angr`——**Python 版本兼容性以 [[re-angr]] 的「工具准备」为准**（版本矩阵只在该处维护一份，避免多处漂移）；依赖多，建议 venv: `python3 -m venv venv && venv/bin/pip install angr`
- 验证: `venv/bin/python -c "import angr; print(angr.__version__)"`
- 用途: 反编译分支爆炸/混淆严重时，用符号执行求解密函数输出（加载目标二进制 → 设密文输入为符号 → 约束求解）

## 操作步骤

按顺序执行，每步记下结果。前提：算法（[[re-crypto-id]]）与密钥（[[re-crypto-keys]]）已确认或至少有一方候选；脚本与验证结果（明文样本 + sha256）存档供报告引用。

0. **证伪"密文"：先定位真实密文边界**（跳过此步是加密分析最常见的失败方式）：
   - 高熵段可能是"伪密文"——容器/压缩数据流被误当加密层（如 zip 数据本体、附加在图片后的压缩流）。压缩数据熵同样是 8.0，与加密不可区分
   - 先做整体结构观察：文件头/尾魔数、格式 marker 分布（JPEG 逐段统计大小）、尾部已知结构当锚点（zip EOCD 的 `cd_offset`/`cd_size` 反推数据起点，EOCD 签名是已知明文）
   - 定格式边界**不要用 first/last 裸 marker 搜索**：标记字节对可以合法出现在载荷里——PNG 的 `IEND` 字样可落在 tEXt chunk 数据内、JPEG 的 `FF D9` 可落在 APP1 等 length-delimited 段内（拿首个命中当结尾会截掉合法数据）。正确做法是按格式规范解析：PNG 从 8 字节签名起按 `Length/Type/Data/CRC` 逐 chunk 遍历，直到结构合法且长度为 0 的 `IEND`（必要时逐块验 CRC）；JPEG 从 `SOI` 起按 marker/segment 解析，进入 `SOS` 后按 `FF00` stuffing 与 `RSTn` 规则处理，**语法位置成立的 `EOI`** 才是结尾。裸字节搜索只能用来产生候选位置，候选要经结构校验后才能定边界
   - 同尺寸文件先比尾部 16 字节/整体哈希——"换头副本"（同一 payload 的第二种封装，仅头部不同）直接省掉一整条支线
   - 压缩率是信号：解压后明显变大的层说明内容有结构（可压=编码/明文），接近 1:1 说明内层已压缩或加密
   - 确认是孤立高熵 blob 后再进入步骤 1 的密文定位

1. **定位解密函数（交叉引用密文输入点）**：
   - 从密文偏移出发：[[re-crypto-id]] 步骤 2 的高熵区偏移 → 反编译器里找读取该偏移/该全局变量的函数 → 沿调用链看谁写入了它（写入方常是解密函数）
   - 从 API 出发：[[re-crypto-keys]] 步骤 4 找到的 `Crypt*`/`EVP_*` 调用点就是候选；观察入参的密文指针是否指向步骤 2 的偏移
   - 动态辅助: [[re-gdb]] / [[re-x64dbg]] 在候选函数下断点（沙箱内），打印入参/返回值，确认它输出可读明文
   - 找不到明确函数 → 密文可能由内联展开的算法处理（无调用边界），回 [[re-crypto-id]] 用数据特征定位（常量表引用处）

2. **反编译还原算法**：
   - 把反编译视图逐段抄译成伪代码，明确：算法（AES-CBC/自定义 XOR…）、密钥与 IV 来源（固定值/派生/上下文）、模式与填充（CBC 的 IV 在哪、PKCS7 还是零填充）
   - 自定义算法: 逐条翻译位运算（XOR/移位/查表），注意字节序（[[re-proto-rev]] 坑 1 同理——长度/密钥字段先试大小端）
   - 还原标准算法时留意细节：AES 用 CBC 还是 ECB、key 长度 16/24/32、IV 是否复用密钥（常见错误实现，见坑 1）
   - 反编译看不清的循环/查表逻辑 → angr 符号执行兜底（构造求解脚本，把函数当黑盒求输出）

3. **重写为独立脚本（python）**：
   ```python
   # decrypt.py —— 按反编译还原的算法重写
   from Crypto.Cipher import AES
   import sys
   key = bytes.fromhex("...")          # 来自 [[re-crypto-keys]] 步骤 1/2/5
   iv  = key[:16]                       # 样本实现: IV = key 前 16 字节
   data = open(sys.argv[1], 'rb').read()
   pt = AES.new(key, AES.MODE_CBC, iv).decrypt(data)
   print(pt)                            # 或写文件 + 后续校验
   ```
   - 脚本参数化（密钥/IV/输入文件走参数或配置），一次写对、反复复用——批量解流量用
   - 关键：脚本逻辑必须与样本一致（填充处理、尾部截断），不一致时回查边界条件（见坑 1）

4. **用已知明文验证**：
   - 已知明文来源：协议头 magic（如 `\xAA\x55`）、文件头（`PK` zip / `\x89PNG`）、报文字段（[[re-proto-rev]] 步骤 2 的固定头）、或 [[re-behavior]] 行为里观察到的明文串
   - 验证方式：解密输出里能找到已知明文片段 → 成功；找不到 → 依次检查：密钥/IV 是否对（[[re-crypto-keys]] 候选逐个试）、字节序、填充处理、是否还有外层加密（见坑 3）
   - 无已知明文时用"可读性"验证：输出可打印率 >70% 或通过 `file -` 识别出格式（PDF/zip/文本）→ 视为成功候选

5. **流量场景：解出明文流量流**：
   - 已按 [[re-crypto-id]] / [[re-crypto-keys]] 确认流量加密算法与密钥后，从 pcap 提取密文载荷（[[re-netcap]] 步骤 3 tshark 导出）：
     ```sh
     tshark -r c2.pcap -Y 'tcp.payload' -T fields -e data.data | sed 's/://g' | xxd -r -p > payloads.bin
     ```
   - 写批量脚本：按流切分（每 TCP 流一段）、逐段调用解密逻辑（同步骤 3 的脚本），输出明文流文件
   - 验证: 明文流里能看到协议结构（会话序号/命令字），再转 [[re-proto-rev]] 做状态机重建
   - 注意会话密钥变化（每次握手重新派生）→ 脚本里为每个会话取对应密钥（[[re-crypto-keys]] 步骤 5 的派生还原）

## 跨域联合

- [[re-protocol]]：本网关工作流第 4 步（解密）——加密通信链路的落地点（crypto-id → crypto-keys → crypto-decrypt → proto-rev）
- [[re-malware]]：C2 流量解密——re-malware 第 4 步；解出的明文（指令/配置）进行为判断与 IOC（[[re-ioc]]）
- [[re-firmware]]：固件加密层/加密通信解密——配合 [[re-fw-extract]] 解包失败时的加密层处理
- [[re-crypto-id]] / [[re-crypto-keys]]：上游——算法与密钥的输入来源
- [[re-memdump]]：密文输入点定位与解密调用现场（转储产物）
- [[re-anti-analysis]]：解密在壳内时先脱壳（见坑 2）；[[re-gdb]] / [[re-x64dbg]] 动态辅助确认函数行为
- 解出的明文转 [[re-proto-rev]] 重建状态机，或按 [[re-firmware]] / [[re-malware]] 流程继续

## 常见坑与陷阱

- **还原脚本与样本行为不一致 → 回查边界条件（长度/填充）**：现象——脚本解出的明文与样本自身输出不一样（多/少字节、尾部乱码）；原因——边界条件没对齐：填充方式（PKCS7/zero）、长度字段是否含填充、IV 是否每包变、密文尾部是否截断；对策——反编译里逐条核对填充与长度处理代码，脚本里显式实现，再回步骤 4 用已知明文验证
- **解密在壳内 → 先脱壳**：现象——在加壳样本里找不到解密函数，或找到的函数只是壳的解压；原因——密文数据/解密逻辑被壳包着，静态看是壳的初始状态（[[re-memdump]] 坑 2 同理）；对策——先 [[re-anti-analysis]] 脱壳（按明文/代码 materialization 定转储时机，不把 OEP 当通用判据），脱壳产物重新做定位
- **多轮解密链**：现象——解出第一层后仍是乱码/高熵（熵 7.0+）；原因——多层加密（先 XOR 再 AES，或嵌套压缩+加密，见 [[re-crypto-id]] 坑 3）；对策——每层单独验证（解一层测一次熵与可读性），分层还原；可用"熵下降即前进一层"作为停止条件
- **密钥/IV 顺序用错解出乱码**：现象——已知明文验证失败，但算法确定是 AES；原因——key/iv 参数顺序（样本是 key||iv 拼接还是分开传）、密钥字节序、或 IV 复用/固定 IV；对策——把 [[re-crypto-keys]] 的候选（含大小端变体、IV=key 前缀等常见错误实现变体）做成参数组合循环试解，命中即可读性/已知明文判定
- **受限符号集 = 编码不是加密（伪加密）**：现象——熵 7.9+ 但字节值域明显受限（如只有 66 个符号、分布均匀、无周期），xortool/IC 对所有密钥长度全平；原因——这是"编码 + 单字节 XOR"：**单字节 XOR 不改变频率分布与符号集**（只平移值），多字节 XOR 才会把符号打散成 256 值；对策——收集全部符号值集合，与已知字符集做 XOR 匹配（对 256 个 key 做集合比较，毫秒级，别用穷举）：Base64 64 字符 + `\n` + `=` = 66 符号、Ascii85 85、hex 16 等；命中后 XOR 还原 → 解码；交叉验证：填充符（如 `=` 加密后仅出现 1 次在文件尾）、换行符频率、解码后魔数
- **xortool/IC 结果全平 = 换思路的信号**：现象——密钥长度候选全部 ~10-20% 且无突出峰值；结论——统计上不存在周期结构，多字节重复密钥 XOR 基本排除，别换参数继续跑；对策——退回观察数据本身（值域/符号集/熵），通常是编码层或一次性密钥流（流密码）
- **伪密文已确认但层数不止一层 → 分层剥离纪律**：每层剥完用魔数/可读性验证产物完整性再进下一层；中间产物全部保留命名（可回滚）；熵不降 / 解出仍高熵 = 还有外层（如 XOR 外再 Base64、再压缩）
- **像素级 XOR（视觉密码学/图像隐写）**：现象——两张"随机噪点"图，题目提示叠加；原因——视觉密码学把信息分成两张 share，XOR（或 ADD）像素后可见；对策——`ImageChops.logical_xor` 或 numpy `a^b` 逐像素 XOR 两图（RGB 三通道）；结果常是"99.5% 纯白背景 + 极浅灰文字"（文字 254 vs 背景 255）——直接看/反色都不可见，**用阈值增强**：`==255 → 黑、其余 → 白`，文字立现；位图小字再按字符网格（5x7）切分逐字读，重复字符用两两位图相似度矩阵验证（如 `d562333d` 的重复模式）
- **ADD 饱和加法是 XOR 的近亲**：现象——XOR 结果无内容时；原因——share 可能由加法式秘密共享生成（XOR 得到噪声，ADD/SUB 才出图）；对策——同样试 `clip(a+b)` 饱和加法与 `a-b` 减法，多运算组合对比
- **新版混淆重 → 老版本回溯定位**：现象——最新版核心函数混淆后体积异常庞大，IDA 分析卡顿，硬啃不现实；原因——同一 SDK 早期版本未上重混淆，核心逻辑更清晰；对策——按行为特征（如"同意隐私协议即触发采集"）装历史版本包（豌豆荚等渠道）→ 点同意 → 抓包确认最早出现该参数的版本；选行为规范的版本做分析起点（调试方便：spawn/attach 都能复现）；老版本还原出的逻辑反过来指导确认新版采集了哪些参数
- **分段/分层加密上报（每段算法不同）**：现象——抓到的上报请求字段单看都像密文，但统一按一种算法解全是乱码；原因——客户端加密流程分多段：**单字段加密 → 分段加密 → 整体加密**，每段的算法/密钥不同（如第一层随机数做 key 逐字节加密、第二层固定 key、第三层拼接组合字段后再加密）；且字段本身可能是设备采集的风险特征（svc 指令采集、`r_1_0` 类字段）；对策——按"每层单独验证"纪律分层剥（熵降/可读即前进一层，见多轮解密链坑）；注意同源 SDK 的"整体加密"可能再套 VM 化算法（约 70 个 handle 模拟基本指令、handle 未混淆时可逐个识别指令语义）；key 来源分随机（抓包不可复现）与固定（可静态找）两类，先固定后随机
- **双重 AES + 加密字符串（key/iv 也加密存）**：现象——确认 AES 后直接解仍失败，或 key/iv 每次会话都变；原因——实现常见套路：第一层 key/iv 固定（**以加密字符串形式硬编码在 so，运行时经解密函数解出**），第二层 key/iv 每次变化；两次加密间拼接固定前缀（如 `03000001`）再进同一函数；且加解密可能是同一函数（标志位控制方向）；对策——hook 解密函数返回点批量导出全部解密字符串（JNI_OnLoad 前 memcpy 的加密串 → 全局变量 → 解密函数），key/iv 直接在其中搜索；找加密点用 findcrypt 扫特征常量（AES S-box 等）→ xref 定位校验 key/iv 长度（16）的函数；动态 hook 打印两次调用前后数据对照验证
