devirt-dev/devirt-core
GitHub: devirt-dev/devirt-core
一款以编译器方式构建的通用、与样本无关的 JavaScript 反混淆器,通过不动点算法进行语义保持的 AST 重写来恢复代码可读性。
Stars: 1 | Forks: 0
# devirt-core
[](https://github.com/devirt-dev/devirt-core/actions/workflows/ci.yml)
[](#license)
[](https://github.com/devirt-dev/devirt-core/releases)
**一个通用的、与样本无关的 JavaScript 反混淆器,以编译器的形式构建。**
它使用 [oxc](https://github.com/oxc-project/oxc) 解析输入,运行保持语义的 AST 重写的
不动点算法,并打印出可读的 JavaScript。**没有针对特定样本的
规则** —— 每一趟处理都是一个通用转换,并且输出经验证
保持了可观察的行为。
```
deob obfuscated.js > readable.js
```
## 安装
针对 Linux、macOS 和 Windows(x86_64 + arm64)的预编译 `deob` 二进制文件已附在
每个[发布版本](https://github.com/devirt-dev/devirt-core/releases)中。
```
# 无需 Rust toolchain — 下载预编译的 binary:
cargo binstall --git https://github.com/devirt-dev/devirt-core devirt-cli
# …或者从 source 构建并安装(需要 Rust toolchain):
cargo install --git https://github.com/devirt-dev/devirt-core devirt-cli
```
这两种方式都会将 `deob` 二进制文件放到你的 `PATH` 中。你也可以从发布页面
下载归档文件,并手动将 `deob` 放到你的 `PATH` 中的某个位置。
## 用法
```
deob # deobfuscate, print readable JS to stdout
deob --format # reprint with NO transforms ("before" side of a diff)
```
统计信息和任何错误都会输出到 **stderr**,因此 stdout 保持纯净,便于管道传输:
```
deob in.js > out.js # just the code
diff <(deob --format in.js) <(deob in.js) # see only real changes
```
`--format` 共享反混淆器完全相同的格式化工具,因此与正常输出进行 diff 对比时,只会显示真正的反混淆变化,而没有格式化带来的干扰。
## 库
该反混淆器也是一个普通的 Rust crate,你可以将其嵌入:
```
use devirt_core::{deobfuscate, format_only};
let report = deobfuscate(source, "input.js"); // filename only steers source-type inference
println!("{}", report.code);
```
`deobfuscate` 返回一个 `Report`,其中包含转换后的 `code`、解析错误、
不动点统计数据,以及一个 `error` 字段(如果流水线失败,会设置该字段,而不是直接 panic)。
`format_only` 仅进行重新打印而不做任何转换,用于 diff 对比中的“修改前”部分。
## 工作原理
流水线按顺序运行每一趟处理,并不断重复,直到一整轮下来没有任何变化(不动点)。这些处理趟位于 `crates/core/src/passes/` 目录中,并在 `default_pipeline()`(`crates/core/src/passes/mod.rs`)中注册。它们分为以下几个组:
- **语法标准化器**(成员访问、序列拆分),用于暴露结构。
- **数据流核心**(常量折叠、内联、死存储和死代码消除),在各轮处理中不断累积效果。
- **控制流恢复**(switch 分支反平坦化、CFG 重建)。
- **解码器处理** —— 一个 [Boa](https://github.com/boa-dev/boa) 沙盒用于
执行字符串解码器函数,以便将其结果提升到源码中。
- **重命名**,通过推断出的角色分配有意义的名称。
控制流恢复使用基于 SSA 的 IR(`crates/core/src/ir/`),结合支配树分析和 relooper,从扁平化的调度器中重建结构化代码。
### 健壮性
反混淆器在对抗性输入上运行深层机制(oxc 遍历、SSA IR、Boa 沙盒),因此我们遏制了两种导致宿主崩溃的途径:
- 流水线在一个具有大型显式栈(`crates/core/src/util.rs`)的专用工作线程上运行。Boa 的递归解析器和 VM 极度依赖本地栈,而栈溢出会导致直接中止(abort)而不是展开(unwind),因此深度上限被设置得很大,并且独立于调用方。
- Panic 会被捕获:内部 panic(oxc 的 bug,或者 Boa 的 `i32::MIN % -1` 溢出)会降级为工作线程的 `join()` 返回 `Err`,而 `deobfuscate` 会回退到返回经过干净重新格式化的输入,同时设置 `Report::error`。调用方永远不会看到崩溃。
## 仓库布局
这是一个 Cargo workspace,在 `crates/` 目录下包含三个 crate:
| Crate | 路径 | 提供 |
|---|---|---|
| `devirt-core` | `crates/core` | 库(`devirt_core`):处理趟、SSA IR、沙盒 |
| `devirt-cli` | `crates/cli` | `deob` 命令行前端 |
| `devirt-report` | `crates/report` | `report` 语料库评分 / 等价性检查工具(维护者工具) |
## 开发
```
cargo build --release # build the whole workspace
cargo nextest run --release # full test suite (what CI runs)
cargo test --release # same, without nextest
cargo clippy --release --all-targets -- -D warnings # lint gate
```
测试套件是自包含的:每一个测试夹具(fixture)都是嵌入在 Rust 测试中的内联 JS 代码片段,因此不需要拉取任何语料库,并且它仅关注正确性。请参阅 [CONTRIBUTING.md](CONTRIBUTING.md) 了解如何添加新的处理趟,以及 [SECURITY.md](SECURITY.md) 了解威胁模型和如何报告漏洞。
## 语料库
混淆 JS 的语料库位于**此仓库之外**,存放在一个 Hugging Face 数据集中
([`devirt-dev/devirt-corpus`](https://huggingface.co/datasets/devirt-dev/devirt-corpus))。
HF 数据集是普通的 git 仓库,因此不需要额外的工具;`./samples` 目录被 gitignore 忽略。实时的可读性评分以及可浏览的输入→输出对照表已发布在[数据集页面](https://huggingface.co/datasets/devirt-dev/devirt-corpus)上。
```
scripts/pull-corpus.sh # clone/update ./samples from the dataset
CORPUS_REV= scripts/pull-corpus.sh # pin a revision for reproducible scores
cargo run --release --bin report # per-source readability scoreboard
cargo run --release --bin report -- --equiv # differential behavioral-equivalence gate
cargo run --release --bin report -- --json samples/metrics.jsonl --markdown # JSONL + card
```
`--equiv` 是针对可读性评分的健全性检查对应项:它在反混淆前后通过全程序测试工具(`sandbox::behavior_signature`)运行每个样本,并要求签名完全一致(终端结果 + console 输出序列),一旦发现不匹配就会以非零状态码退出。对于那些原本就无法进行可运行性对比的样本(超时,或者测试工具无法构建宿主环境),将被直接跳过。
该报告按来源对样本进行分组(`generated`、`real/npm`、`real/tranco`、`real/httparchive`),并显示 `kept%` 以及**opaque% in→out** —— 即处理前后具有机器生成特征标识符的比例。在处理真实的*压缩*(minified)输入时,`kept%` 会超过 100%,因为引擎会对其进行重新格式化/展开;体现可读性提升的是 opaque-% 的下降,而不是字节数。
### 扩充语料库
```
node scripts/make-seeds.mjs --count 200 # varied plain seeds
node scripts/gen-corpus.mjs # obfuscate them under ~21 profiles
node scripts/gen-corpus.mjs --profiles strong,controlflow # …or a subset
```
真实世界的样本来自三个来源(npm / Tranco 抓取 / HTTP Archive),
每一个都使用相同的方式进行过滤和去重 —— 参见
[`scripts/real/README.md`](scripts/real/README.md)。生成样本需要在本地安装
`javascript-obfuscator`(`npm i javascript-obfuscator`);它仅作为本地工具使用,
绝不是 crate 的一部分。
## 许可证
根据以下任一许可证进行授权:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
- MIT license ([LICENSE-MIT](LICENSE-MIT))
由你选择。
除非你明确声明,否则任何由你提交以包含在本作品中的有意贡献,根据 Apache-2.0 许可证的定义,均应按上述方式获得双重许可,无需任何额外的条款或条件。
标签:CMS安全, JavaScript, Rust, 代码反混淆, 可视化界面, 编译器, 网络流量审计, 通知系统