关于 reverse-engineer-js
面向开发者的 JavaScript 反混淆技能,借助 LLM 将压缩或混淆代码恢复为更易读的形式,并指导源码映射检查、模块提取及处理不同规模的打包文件。
主要功能
- 01优先检查源码映射
- 02基于 LLM 重命名标识符
- 03用 webcrack 提取模块
- 04处理 TTY 与输出文件问题
适用场景
- 01恢复有效混淆代码中的名称
- 02处理大型打包文件中的指定模块
- 03诊断 CI 或 Shell 中的 humanifyjs 故障
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vladmdgolam/ agent-skills --skill reverse-engineer-js安装后在 Claude Code 中输入/reverse-engineer-js确认已加载。
详细介绍
逆向工程 JS
借助 humanifyjs 和用于提取及重新集成模块的配套工具,使用 LLM 辅助反混淆缩小或混淆后的 JavaScript。
工具一览
| 工具 | 用途 | 使用时机 |
|---|---|---|
humanifyjs (npx) | 借助 LLM 重命名混淆的变量/函数 | 代码混淆,但本身是有效的 JS |
prettier / js-beautify | 重新格式化单行缩小代码 | 如有需要,在 humanify 之前使用 |
webcrack | 将 webpack / browserify 包拆分为单个模块文件 | 包是一个大文件时 |
| Source map 追踪 | 包旁边可能存在 // # sourceMappingURL=... | 务必先检查——效果胜过 LLM 重命名 |
在耗费 token 使用 humanify 之前,务必先检查 source map(bundle.js.map 或文件中的 sourceMappingURL 注释)。如果找到了,就可以免费还原原始源代码。
humanify 默认调用方式
除非用户另有要求,否则使用 Gemini:
npx humanifyjs gemini -m "gemini-3.8-flash" -o <outputDir> <inputFile>
需要在环境变量中设置 GEMINI_API_KEY(通常放在项目根目录的 .env 文件中)。
其他后端:
# OpenAI
npx humanifyjs openai --apiKey="$OPENAI_API_KEY" --model gpt-4.1-mini bundle.js
# Local (slower, free)
npx humanifyjs local bundle.js
关键注意事项
1. 非交互式 shell 中需要伪 TTY
humanify 会调用 process.stdout.cursorTo(...) 来显示进度条。在 Claude Code / CI / 管道环境中,stdout 不是 TTY,因此会因 TypeError: process.stdout.cursorTo is not a function 崩溃。
请使用 script -q /dev/null 包装调用:
script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o "$OUT" "$IN"
(macOS/BSD 的 script 语法。Linux 上使用:script -qc "<cmd>" /dev/null。)
2. 输出文件名固定
humanify 始终将结果写入 <outputDir>/deobfuscated.js,与输入文件名无关。不要指望输入 module_1234.js 后会输出 module_1234.js。
3. 文件越长,运行时间越久
humanify 会按顺序处理顶层声明,每个声明都会调用一次 LLM。一个 5 MB 的包可能需要 30 分钟以上。可采用以下策略:
- 对从包中提取的单个模块运行,而不是处理整个包
- 如果包使用 webpack 打包,先用
webcrack拆分 - 选择能充分理解代码且成本最低的模型。这里默认使用
gemini-3.8-flash;对于非常大的包,轻量版(gemini-3.5-flash-lite)能降低每个声明的成本,但处理密集代码时准确度会有所下降
4. 保留原始文件存档
进行任何重写之前,先将未经修改的包复制到 archive/bundle.original.js。humanify 的结果并非确定性的,你可能会想换个模型重新运行。
工作流 A:整个包的反混淆
适用于较小的包(<500 KB)或时间充裕的情况:
mkdir -p archive out
cp bundle.js archive/bundle.original.js
script -q /dev/null npx humanifyjs gemini \
-m "gemini-3.8-flash" \
-o out bundle.js
# result: out/deobfuscated.js
之后可以选择运行 prettier --write out/deobfuscated.js,以改善格式。
工作流 B:提取单个模块(大型包)
常见于 fxhash / 生成艺术包:单个包包含许多 id: (params) => { ... } 模块,而你只想对其中几个有意思的模块运行 humanify。
- 确定模块 ID——在包中搜索独特字符串,然后追溯到外层的
<id>: (e, t, n) => {开始位置。 - 提取模块主体——找到开始位置后,统计花括号数量(注意字符串),直到匹配的
}。 - 包装为独立箭头函数——将
(e, t, n) => { <body> }写入临时文件。humanify 需要有效的顶层代码。 - 运行 humanify——
script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o "$OUT" "$IN"。 - 拆除包装——从结果中移除
(e, t, n) => { ... }外壳。 - 保存并打桩——将反混淆后的主体写入
modules/module_<id>_<name>.js,并将包中的主体替换为类似<id>: (e, t, n) => { window.DEOBF[<id>](e, t, n); }的桩代码。
如果项目已有 scripts/deobfuscate.js(或类似脚本),请重复使用——花括号计数和字符串转义逻辑相当棘手,每次处理新包都重新编写会浪费时间。典型脚本会接收数字模块 ID,从 bundle.js 提取主体,对包装后的函数运行 humanify,拆除包装,并将结果写入 modules/module_<id>_<name>.js。
工作流 C:Webpack 打包文件 → 单模块文件
处理 Webpack 打包文件时,webcrack 会在运行 humanify 之前清晰地拆分模块:
npx webcrack bundle.js -o webcracked/
# Then humanify each module of interest:
script -q /dev/null npx humanifyjs gemini \
-m "gemini-3.8-flash" \
-o humanified webcracked/modules/123.js
示例
示例 1:反混淆一个小型 fxhash 打包文件
用户说:“humanify 这个 fxhash 项目中的 bundle.js”
操作:
- 检查 sourcemap:
head -c 4096 bundle.js | tail -c 200— 查找sourceMappingURL。没有?继续。 - 确认
.env中有GEMINI_API_KEY。加载环境变量:set -a; source .env; set +a。 - 归档:
mkdir -p archive && cp bundle.js archive/bundle.original.js。 - 运行:
mkdir -p out && script -q /dev/null npx humanifyjs gemini -m "gemini-3.8-flash" -o out bundle.js。 - 美化代码:
npx prettier --write out/deobfuscated.js。
**结果:**生成 out/deobfuscated.js,变量已重命名,可以开始阅读。
示例 2:反混淆大型打包文件中的一个模块
用户说:“反混淆 og/bundle.js 中的模块 3658,并将其命名为 treeGenerator”
操作:
- 如果项目已有反混淆辅助脚本(例如
scripts/deobfuscate.js),运行它:node scripts/deobfuscate.js 3658 treeGenerator。 - 否则:通过统计大括号提取模块主体,将其包装为
(e, t, n) => { ... },使用script -q /dev/null运行 humanify,去除包装,将结果保存到modules/module_3658_treeGenerator.js,并为打包文件添加桩代码。
结果:modules/ 中新增一个可读的函数文件;通过添加桩代码缩小 bundle.js。
示例 3:humanify 因 cursorTo is not a function 崩溃
用户说:“humanify 一运行就挂了”
**诊断:**标准输出不是 TTY(这是 Claude Code 的 bash 工具的默认状态)。
**修复:**使用 script -q /dev/null <cmd>(macOS)或 script -qc "<cmd>" /dev/null(Linux)包装命令后重新运行。
故障排除
cursorTo is not a function
**原因:**标准输出不是 TTY。**修复:**使用 script -q /dev/null 包装命令。
未设置 GEMINI_API_KEY
**原因:**humanify 从进程环境变量读取,而不会自动读取项目的 .env。**修复:**在同一个 shell 中运行 set -a; source .env; set +a,或直接传入:GEMINI_API_KEY=... npx humanifyjs gemini ...。
输出与输入完全相同(没有重命名)
**原因:**humanify 只会重命名它认为经过混淆的标识符(较短的单字母标识符)。可读代码会保持原样。**修复:**确认输入确实经过混淆;如果 useState 之类的名称保留了下来,这是预期行为,不是 bug。
Gemini 限流/配额错误
**原因:**文件较大、顶层函数较多 → 需要多次顺序调用大模型。**修复:**切换到 local 后端进行免费的本地重命名,或者提取单个模块(工作流 B),而不是处理整个打包文件。
humanify 需要数小时
**原因:**打包文件较大,且逐个函数顺序调用。**修复:**先用 webcrack 拆分,或者只对实际需要的模块运行 humanify。
输出目录中包含过时的 deobfuscated.js
**原因:**humanify 会写入固定文件名。第二次运行失败时,会拾取上次成功生成的输出。**修复:**每次运行前执行 rm -f $OUT/deobfuscated.js。