jgajek/cff-deobfuscator
GitHub: jgajek/cff-deobfuscator
一款 IDA Pro 插件,通过多层静态分析和字节补丁逆转 x86-64 二进制文件的控制流平坦化混淆,恢复可读的控制流和伪代码。
Stars: 0 | Forks: 0
# CFF 反混淆器
一个独立的 IDA Pro 插件,能够通过一个富有弹性且多趟扫描的过程,一次性逆转 x86-64 二进制文件上的**控制流平坦化 (CFF)**。它将被平坦化的、间接调度的函数还原为可读的控制流,并对混淆的 import / API 调用进行标注,从而让 Hex-Rays 的输出再次变得有意义。
## 功能说明
该插件按顺序运行三个反混淆层:
| 层级 | 用途 |
| --- | --- |
| **Layer 1 — 去间接化** | 静态解析每个 CFF 解码 gadget 的 `jmp ` 到其具体目标,并将其重写为直接跳转,以便 Hex-Rays 能够查看完整的(但仍处于平坦化状态的)函数。 |
| **Layer 2 — 反平坦化** | 恢复每个平坦化状态机的真实控制流图 (control-flow graph) 并通过字节补丁移除调度器,随后折叠剩余的不透明谓词 (opaque-predicate) gadget。 |
| **Layer 3 — Import 标注** | 解析加法盲化的 import / 库调用(`mov reg, cs:off; add reg, key; call reg`),并在反汇编和伪代码中标注每一个已恢复的调用点。 |
### 弹性与幂等性
这些步骤会对数据库进行字节补丁,这无法安全地重复执行。该插件将其进度记录在一个私有的 netnode 中,因此:
- 能够检测到**已完成**的数据库,第二次完整运行将是一次优雅的空操作(它不会重新打补丁并破坏状态);
- **被中断**的运行会从第一个未完成的阶段恢复,而不是重新执行已完成的阶段。
### 仅包含两个操作
两者都显示在 **Edit > Plugins** 下(以及插件自身的运行对话框中):
- **CFF Deobfuscator: Dry run (report only)** — 只读。报告每一层针对数据库当前状态将要执行的操作。从不进行写入。
- **CFF Deobfuscator: Full run (patch + annotate)** — 应用所有三个层(因为会修改 IDB,所以会先要求确认)。
进度和最终摘要将打印到 **Output** 窗口。
## 范围:通用与特定样本
该**框架是通用的**;但**识别器并非如此**。大致而言,从最不特定到最特定:
| 组件 | 通用 / 可复用 | 特定于此保护器 |
| --- | --- | --- |
| 编排 | 多趟驱动、netnode 幂等性 / 运行状态、空运行与完整运行分离、控制台报告、“通过模拟解析,拒绝猜测,仅覆盖可证明为已死的字节”的原则 | — |
| **Layer 1** | 用于解析计算出的 `jmp ` 的常量微型模拟 | 解码 gadget 指令集、“可安全覆盖”的尾部指令(`add/mov/lea/nop`)、Win64 易失性寄存器假设 |
| **Layer 2** | 不透明谓词 (opaque-predicate) 折叠在代数上通常是合理的 | 位于栈槽位中的 32 位状态变量;作为**有符号二分搜索比较树**的调度器(`cmp eax, IMM; jg/jle` 作为内部节点,`cmp eax, STATE; jz/jnz` 作为叶子节点);**奇偶 gadget** `lea Rd,[Rs-1]; imul Rd,Rs; test Rd,1; jz/jnz`;栈镜像与跳转表调度族 |
| **Layer 3** | 密钥是**动态发现的**(`collect_keys` + `build_blind_map` 中的族一致性),而非硬编码 | 加法盲化调用方案 `mov reg, cs:off; add reg, KEY; call reg`;`.bss` 运行时填充的 import 被标记为 `p_` / `__imp_` |
不同的平坦化工具(例如 OLLVM 风格的 switch 调度器)将无法匹配 Layer 2/3;Layer 1 最有可能被直接复用。要将该插件重新定位到另一个保护器,你需要为右侧列中的项目替换新的匹配器,同时保持左侧列完全不变。
## 样本
该插件是针对单个样本开发和验证的:
| | |
| --- | --- |
| 文件名 | `FortiEndpoint_Patch.exe` |
| 类型 | PE32+ 可执行文件,x86-64 (控制台),18 个节 |
| 大小 | 4,019,070 字节 |
| MD5 | `338662fd0c4d750a0ba203a32b59f081` |
| SHA-1 | `17e771c78430cc67e71d4547f8996a1a488e9d3f` |
| SHA-256 | `0da123adf9251957a4b850a3f6bd6a753dd4892be176a84a18450e899534cc5e` |
## 环境要求
- IDA Pro 9.0–9.3 以及 **Hex-Rays x86-64 反编译器**。
- 64 位 (x86-64) 目标。无需任何第三方 Python 包。
## 安装说明
插件本身位于 [`plugins/ida/`](plugins/ida/)。
### 选项 A — 自动安装程序(推荐)
```
cd plugins/ida
python3 install.py
```
这会将插件复制到你的 IDA 用户插件目录中
(`$IDAUSR/plugins/cff-deobfuscator/`,在 Windows 上默认为
`%APPDATA%\Hex-Rays\IDA Pro`,在 Linux/macOS 上为 `~/.idapro`)。重启 IDA,
插件将通过其 `ida-plugin.json` 描述符被自动发现。
```
python3 install.py --dir "/path/to/ida/userdir" # custom IDA user dir
python3 install.py --uninstall # remove a previous install
```
### 选项 B — 手动复制(独立文件夹)
`plugins/ida/` 文件夹是一个独立的 IDA 插件(它包含了
`ida-plugin.json`)。将其内容复制到你的 IDA 用户 `plugins` 目录中的一个文件夹里,例如:
```
~/.idapro/plugins/cff-deobfuscator/
ida-plugin.json
cff_deobfuscator.py
cff/
```
将 `cff_deobfuscator.py`、`ida-plugin.json` 和 `cff/` 包保持在同一个目录中。重启 IDA。
### 选项 C — 不安装仅加载一次
在 IDA 中:**File > Script file…** 并选择 `plugins/ida/cff_deobfuscator.py`。
该插件会将其自身目录添加到 `sys.path` 中,因此只要 `cff/` 包与入口脚本位于同一位置,它就会被找到。
## 使用说明
1. 在 IDA 中打开(已分析的)目标数据库。
2. 选择 **Edit > Plugins > CFF Deobfuscator: Dry run** 以预览工作负载。
3. 选择 **Edit > Plugins > CFF Deobfuscator: Full run** 进行应用;确认提示。观察 Output 窗口以查看逐阶段的进度。
4. 在同一个数据库上重新运行完整运行是安全的——它会报告工作已经完成,并且不会进行任何更改。
所有补丁都是正常的 IDB 编辑,并且可以撤销;记录的运行状态存在于私有的 netnode 中,不会改变你的分析。
### 从控制台驱动引擎(可选)
这些层是普通的库,可以直接调用:
```
from cff import orchestrator
orchestrator.dry_run() # read-only report
orchestrator.full_run() # full patch + annotate pass
from cff import layer1 as L1, layer2 as L2, imports as L3
L1.patch_all() # Layer 1 only
L2.unflatten_all() # Layer 2 only
L3.annotate_all(apply=True) # Layer 3 only
from cff import runstate
runstate.reset() # forget recorded progress (IDB is left untouched)
```
## 布局
```
plugins/ida/
cff_deobfuscator.py IDA plugin entry (PLUGIN_ENTRY, the two menu actions)
cff_string_decoder.py standalone XOR-29 string recovery (IDA + offline modes)
ida-plugin.json Plugin Manager descriptor
install.py Cross-platform installer / uninstaller
cff/
orchestrator.py dry_run() / full_run() multi-pass driver
runstate.py netnode-backed idempotency state
log.py console banners / status
layer1.py Layer 1 engine (de-indirection)
layer2.py Layer 2 engine (unflattening)
imports.py Layer 3 engine (import / API resolver)
docs/
CFF-DEOBFUSCATOR.md in-depth field guide (obfuscation + deobfuscation)
MALWARE-ANALYSIS-FortiEndpoint_Patch.md capability/behavior analysis report
```
## 字符串恢复工具
[`plugins/ida/cff_string_decoder.py`](plugins/ida/cff_string_decoder.py) 恢复
样本中混淆的字符串池(一个 29 字节的重复 XOR 方案;详见分析报告的 §2.1)。它有两种模式:
```
# 离线:直接从 PE 中清理 .rdata 池(仅需 `pefile`)
python3 plugins/ida/cff_string_decoder.py scan FortiEndpoint_Patch.exe -o strings.txt
```
```
# 在 IDA 内部:忠实恢复(精确地址)+ 将明文写为注释
import cff_string_decoder as d
d.run_ida(annotate=True, out_json=r"C:\temp\cff_strings.json")
```
标签:IDA插件, 云资产清单, 反混淆, 逆向工具, 逆向工程