coleleavitt/js-beautify-rs
GitHub: coleleavitt/js-beautify-rs
这是一个基于 Rust 和 oxc 构建的高性能 JavaScript 美化与去混淆工具,专门用于处理大型生产环境代码包及复杂的混淆攻击载荷。
Stars: 3 | Forks: 1
# js-beautify-rs
[](https://crates.io/crates/js-beautify-rs)
[](LICENSE.md)
一个使用 Rust 编写的快速 JavaScript 美化器和去混淆器,由 [oxc](https://github.com/oxc-project/oxc) 提供支持。
接收经过压缩、混淆的 webpack/esbuild/Bun bundle 并输出可读的 JavaScript。
能够处理真实的生产环境 bundle —— 已针对 11MB+ 的构建文件进行测试。
## 安装
```
cargo install js-beautify-rs
```
或者从源代码构建:
```
git clone https://github.com/coleleavitt/js-beautify-rs
cd js-beautify-rs
cargo build --release
# binary 位于 ./target/release/jsbeautify
```
## 快速开始
```
# 美经混淆的 JavaScript
jsbeautify input.js -o output.js
# 美经 + 去混淆 (20 阶段 AST 管道)
jsbeautify input.js --deobfuscate -o output.js
# 从 stdin 管道输入
cat bundle.js | jsbeautify - > output.js
# 提取 webpack 模块到单独文件
jsbeautify bundle.js --extract-modules --module-dir ./modules
# 生成依赖图 (DOT 格式)
jsbeautify bundle.js --extract-modules --dependency-graph deps.dot
# 跨版本对齐,附带 sourcemap 名称恢复
jsbeautify v1.js --sourcemap v1.js.map --align-with v2.js --align-output v2.aligned.js -o v1.aligned.js
# 从 Bun bundles 中提取名称 (MR exports, this.name, displayName)
jsbeautify bundle.js --bun-extract --sourcemap bundle.js.map -o output.js
```
## CLI 参考
```
Usage: jsbeautify [OPTIONS]
Arguments:
Input JavaScript file (use "-" for stdin)
Options:
-o, --output Write output to a file instead of stdout
-d, --deobfuscate Enable AST-based deobfuscation (20-phase pipeline)
--split-chunks Split webpack chunks into separate files
--chunk-dir Directory for chunk output [default: ./chunks]
--chunk-map Write chunk metadata to a JSON file
--extract-modules Extract webpack modules to separate files
--module-dir Directory for module output [default: ./modules]
--dependency-graph Generate a dependency graph in DOT format
--source-maps Generate source maps
--sourcemap Sourcemap for extracting original variable names
--names-json Name mappings from extract-names.ts
--align-with Second bundle to align with (produces stable diffs)
--align-output Output path for the aligned second bundle
--raw Skip beautification, output raw aligned code
--bun-extract Extract names from Bun bundle patterns
--indent-size Indentation size in spaces [default: 4]
--indent-with-tabs Use tabs for indentation instead of spaces
-h, --help Print help
-V, --version Print version
```
## 库用法
js-beautify-rs 也可以作为 Rust 库使用:
```
use js_beautify_rs::{beautify, Options};
let code = "function test(){console.log('hello');}";
let options = Options::default();
let beautified = beautify(code, &options).expect("beautification failed");
```
用于去混淆:
```
use js_beautify_rs::AstDeobfuscator;
let obfuscated = std::fs::read_to_string("bundle.js").unwrap();
let mut deobfuscator = AstDeobfuscator::new();
let clean = deobfuscator.deobfuscate(&obfuscated).unwrap();
```
## 去混淆流水线
`--deobfuscate` 标志会运行一个 **阶段 0 预处理器**,随后是一个 **20 阶段的 AST 转换流水线**。每个阶段的结果会输入到下一个阶段 —— 顺序很重要。
### 阶段 0:加密 Eval 解密(Pre-AST)
在 AST 解析之前,jsbeautify 会检测并解密钓鱼工具包(Tycoon2FA 及类似工具)使用的一种特定混淆模式:
```
// Input: Encrypted eval pattern
var data = "NjFiMjZkZDA6MTcwNDcy:SGVsbG8gV29ybGQh..."; // Base64 with PRNG seed
var chars = ['\x23e64','\x23v05','\x23a0B','\x23l2C']; // Steganographic "eval"
// ... PRNG XOR + Caesar cipher decryption logic ...
window[chars.map(c => c[1]).join('')](decrypted); // eval(decrypted)
// Output: Decrypted code directly in source
console.log("Hello World!");
```
移除的加密层:
1. Base64 解码,带有冒号分隔的 PRNG 种子和计数器
2. 通过自定义 PRNG(基于种子)进行 XOR 密钥流处理
3. 变量移位凯撒密码(每个字符移位值为 1-25)
4. 用于 `eval` 调用的颜色十六进制隐写术
该模式是从一次实时的钓鱼攻击活动中逆向工程得出的。解密后的载荷会原位替换整个加密块,然后继续进行 AST 处理。
### AST 转换阶段(1-20)
| 阶段 | 传递 | 作用 |
|------:|------|-------------|
| 1 | 控制流平坦化还原 | 从基于 switch 的状态机中重构原始控制流 |
| 2 | 字符串数组旋转 | 检测并应用数组旋转(shift/push IIFE 模式) |
| 3 | 解码器 / 字符串数组 / 分发器内联 | 解析解码器函数(base64, RC4, XOR, offset)并内联字符串值 |
| 4 | 调用代理内联 | 检测单次使用的包装函数并内联其目标 |
| 5 | 运算符代理内联 | 解析包装二元运算符的代理函数 |
| 6 | 表达式简化 | 括号转点符号、`!0`->`true`、`void 0`->`undefined`、常量折叠、代数简化、强度削减 |
| 7 | 死代码消除 | 移除 `if(false)`、`while(false)`、return/throw 之后不可达的代码 |
| 8 | 死变量消除 | 移除从未被读取的变量 |
| 9 | 函数内联 | 内联具有简单主体的单次使用函数 |
| 10 | 数组解包 / 动态属性 / 三元 / try-catch | 解析常量数组索引,简化常量三元表达式,移除空的 catch 块 |
| 11 | Unicode / 布尔值 / void / 对象稀疏化 | 标准化 unicode 标识符、布尔字面量、void 表达式、稀疏对象模式 |
| 12 | 变量重命名 | 将十六进制编码的标识符(`_0x1a2b`)重命名为可读名称 |
| 13 | 空语句清理 | 移除先前传递中残留的空语句 |
| 14 | 序列表达式拆分 | 将逗号表达式(`a(), b(), c()`)拆分为单独的语句 |
| 15 | 多变量拆分 | 将 `let a=1, b=2, c=3` 拆分为单独的声明 |
| 16 | 三元转 if/else | 将独立的三元表达式语句转换为 if/else 块 |
| 17 | 短路转 if | 将独立的 `a && b()` / `a || b()` 转换为 if 语句 |
| 18 | IIFE 展包 | 将零参数箭头 IIFE 展包为内联语句 |
| 19 | esbuild 辅助函数检测 | 识别 `__commonJS`、`__esm`、`__export`、`__toESM`、`__toCommonJS` 运行时辅助函数 |
| 20 | 模块标注 | 使用注释分隔符标记 webpack、esbuild 和 Bun 模块边界 |
## 跨版本对齐
通过恢复原始名称并在版本之间匹配语句,在压缩 bundle 的不同版本之间生成稳定的 diff:
```
# 使用 sourcemap 名称恢复对齐两个 bundle 版本
jsbeautify v2.1.88.js \
--sourcemap v2.1.88.js.map \
--align-with v2.1.96.js \
--align-output v2.1.96.aligned.js \
-o v2.1.88.aligned.js
```
三层规范命名:
1. **Sourcemap 名称** —— 从 `.map` 文件中恢复的原始标识符(36,000+ 个名称)
2. **基于槽的名称** —— 基于 Bun 字母表派生的 `sN` 名称,用于未映射的标识符
3. **基于哈希的名称** —— 用于不匹配代码的 AST 结构哈希后备 `_rN`
结果:94.7% 的语句匹配率,bundle 版本之间 diff 减少 76%。
## Bun Bundle 支持
从 Bun 特定的 bundle 模式中提取原始名称:
```
jsbeautify bundle.js --bun-extract --sourcemap bundle.js.map -o output.js
```
检测到的模式:
- `MR(target, { exportName: () => minifiedVar })` 导出映射
- 类构造函数中的 `this.name = "ClassName"`
- `Component.displayName = "ComponentName"` 赋值
## 测试
```
cargo test --lib
```
## 许可证
[MIT](LICENSE.md)
标签:Bun, CMS安全, ESBuild, JavaScript, oxc, Rust, SourceMap, URL提取, Webpack, WebSocket, 云安全监控, 代码格式化, 代码美化, 代码还原, 依赖分析, 反混淆, 反编译, 可视化界面, 网络流量审计, 自动化payload嵌入, 通知系统, 静态分析