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插件, 云资产清单, 反混淆, 逆向工具, 逆向工程