HumanifyJS 反混淆实战指南:用 LLM 把压缩 JS 还原成人话
HumanifyJS 反混淆实战指南:用 LLM 把压缩 JS 还原成人话
【免费下载链接】humanifyDeobfuscate Javascript code using ChatGPT项目地址: https://gitcode.com/gh_mirrors/hu/humanify
凌晨两点,你接手了一个老项目。构建产物里躺着一个 200KB 的bundle.min.js,报错堆栈指向第 1 行第 3921 列,打开一看,满屏都是function a(e,t){var n=[]...}。这种场景下,HumanifyJS 这类 AI 辅助的 JavaScript 反混淆工具就是为救你而生的:它调用大语言模型,把压缩代码里那些a、e、t全部重命名为inputString、chunkSize这样的可读名字,让你能在十分钟内读懂原本要花一整天破解的代码。本文不写套话,直接带你从一个真实事故现场出发,把工具的用法、原理和坑位一次讲透。
一段压缩代码引发的"事故现场" 🕳️
先还原一下你的处境。报错堆栈长这样:
at a (bundle.min.js:1:3921) at n.push (bundle.min.js:1:4047)你找到了对应位置,看到的却是:
function a(e,t){var n=[];var r=e.length;var i=0;for(;i<r;i+=t){if(i+t<r){n.push(e.substring(i,i+t))}else{n.push(e.substring(i,r))}}return n}四个字母a、e、t、n,撑起了整个函数。你想加个断点,却不知道该观察哪个变量;你想搜e.length理解业务逻辑,搜出来的全是不相干的匹配。这不是代码写得烂——是压缩工具干的,它天生就该把名字缩短。
你开始手动重命名:e是入参,t也是入参,n是个数组,r是个长度……五分钟过去,你还原了这个函数,但文件里还有另外几百个这样的标识符。手动逆向显然走不通。
这时候你需要的不是"再努力一点",而是一个能把"起名字"这个体力活外包出去的帮手。
在动手之前,先看清它到底能做什么 🔍
HumanifyJS 的官方定位一句话就能说清:用 LLM 为压缩/混淆后的 JavaScript 重新命名标识符。它的适用人群很明确:
- 接手含压缩产物、需要读代码排障的前端开发者
- 分析第三方库、SDK 内部实现的逆向爱好者
- 需要审计线上
dist目录里到底跑了什么逻辑的安全工程师
它有一个很容易被忽略的边界:它只做一件事——改名字。它不会帮你格式化代码、不会拆解 webpack 模块、不会把 UglifyJS 压缩的合并语句还原成原本的写法。格式化你可以交给 Prettier,解包 webpack 产物可以交给 webcrack,而"给几千个标识符起一个贴切的名字"这种需要理解语义的活,恰好是 LLM 的强项,也是 HumanifyJS 专注的领域。
搞清楚边界之后,你会发现"只做一件事"反而是一种美德:流程清晰、输出可预测、出问题也好排查。
3 分钟跑通你的第一条命令 ⚡
v3 版本的 HumanifyJS 是一个 Rust 编写的单文件静态二进制——不需要 Node、不需要 npm、不需要 Python 环境,下载下来就能跑。如果你想从源码构建,一条命令即可:
git clone https://gitcode.com/gh_mirrors/hu/humanify cd humanify cargo build --release编译完成后,把target/release/humanify放到 PATH 里,然后验证:
humanify --help运行逻辑非常 Unix 风格:输入可以是一个文件路径,也可以是-(表示从标准输入读取);输出默认打到标准输出,也可以用-o指定文件。六个子命令对应六种 LLM 来源:
humanify <openai|gemini|anthropic|ollama|openrouter|requesty> [FLAGS] <INPUT>拿最常用的 OpenAI 举例,把环境变量配好之后,你实际敲的命令只有一行:
export OPENAI_API_KEY=你的密钥 humanify openai bundle.min.js -o bundle.readable.js十几秒后,bundle.readable.js里那些a、e、n就都变成了有含义的名字。整套流程从装好工具到拿到结果,三分钟绰绰有余。
打开引擎盖看流程:它怎么处理一个标识符 🏭
加一个--verbose参数重跑一遍,你就能亲眼看到工具的内部工作日志:
humanify openai bundle.min.js -o bundle.readable.js --verbose --progress日志会告诉你:一共找到了多少个标识符,当前在处理第几个,每个标识符原来的名字和建议的新名字分别是什么。这一层"进度可见性"特别适合排查问题——比如某个名字一直没被改,你就能定位到是哪一步出了问题。
而它的处理顺序也藏着设计心思:从最大的作用域开始,逐层向内。先把整个函数/模块级别的标识符定下来,再处理里面的局部变量。这就像先给一栋楼贴好楼层号,再给每扇门装门牌,避免内层名字先定、外层一改名又把语境搅乱。
每个标识符的处理路径大致是:
- 从代码中截取该标识符所在作用域的上下文(默认截取 500 个字符)
- 把"这段代码 + 这个标识符"发给 LLM,要求返回一个 JSON 格式的建议名字
- 收到建议后做合法性检查——LLM 偶尔会返回
foo bar、this.x甚至static这种不能直接用的名字,工具会把它规范化成合法的标识符 - 检查作用域冲突,必要时加数字后缀
- 在符号表里完成改名,输出时自动保证所有引用同步更新
如果 LLM 调用失败或者返回了没法用的名字,工具会保留原名字继续跑,而不是中断整个任务。这个"宁可不动,不可改错"的策略,保证了任何情况下输出都是可运行的有效 JavaScript。
为什么"AI 负责起名,AST 负责动手"最稳妥 🛡️
很多人第一次听到"用 AI 重写代码"会本能地担心:它会不会顺手改了我的逻辑?
HumanifyJS 的分工设计恰好把这种风险压到了最低。整条链路的核心代码在src/rename/目录下:解析用的是 oxc(高性能 JS 解析器),重命名发生在抽象语法树(AST)层,LLM 全程只做一件事——给出一个名字建议。它没有权限改动代码结构,代码生成器会忠实地把 AST 重新打印出来。
这个分工很像"外科医生与助手"的关系:助手(LLM)只负责递上工具、给出建议,下刀的是外科医生(AST 重命名器),每一步都精准、可控、可回退。
具体到"安全重命名",有三个细节值得你注意,它们共同构成了防呆网:
- 上下文窗口:工具不会把整个文件都发给 LLM,而是以每个标识符为圆心,截取其所在作用域的一段代码(默认 500 字符,可用
--context-size调整)。这让 LLM 有足够的"侦查范围"理解语义,又不至于因为文件太大而烧掉太多 token。 - 名称规范化:LLM 可能建议出
user account name、static这类非法标识符。src/rename/safe_name.rs会把它们规范化为userAccountName、_static,保证生成的名字永远是合法的 JavaScript 标识符。 - 作用域感知的冲突处理:两个兄弟函数里的同名局部变量互不干扰,可以都叫
index;但同一作用域里撞名时,会通过数字后缀区分——如果 LLM 建议了item2而该名字已被占用,下一个会变成item3,而不是尴尬的item22。
另外两类名字它坚决不碰:对象属性名和类的方法名/私有字段(如#x)。原因很简单——这些名字可能被外部代码以字符串形式访问,改了就会破坏契约。
一个文件的前后对比:从 a(e,t) 到 splitString 📊
把文章开头那个让你崩溃的函数交给它,处理前的样子:
function a(e,t){var n=[];var r=e.length;var i=0;for(;i<r;i+=t){if(i+t<r){n.push(e.substring(i,i+t))}else{n.push(e.substring(i,r))}}return n}处理之后:
function splitString(inputString, chunkSize) { var chunks = []; var stringLength = inputString.length; var startIndex = 0; for (; startIndex < stringLength; startIndex += chunkSize) { if (startIndex + chunkSize < stringLength) { chunks.push(inputString.substring(startIndex, startIndex + chunkSize)); } else { chunks.push(inputString.substring(startIndex, stringLength)); } } return chunks; }a变成了splitString,e变成了inputString,t变成了chunkSize,n变成了chunks。你没有猜错,这就是一个把字符串按指定长度切片的工具函数——但你不需要靠猜了,名字已经把意图写在了脸上。
注意看:splitString这个函数名本身没有被改动。如果 LLM 判断一个名字已经足够有表达力,它会原样返回,不做无意义的折腾。这也从侧面印证了它改名的依据是语义,而不是"见一个改一个"。
再补一个进阶场景。如果你的目标是 webpack 打包产物,直接处理效果会差很多——因为里面还裹着一层模块加载器。正确的姿势是先拆包、再改名,Unix 管道在这里发挥得淋漓尽致:
npx webcrack < bundle.min.js | humanify openai - -o bundle.jsnpx webcrack把模块结构还原成普通脚本,通过标准输出直接喂给humanify,humanify 读-(标准输入),把结果写到bundle.js。两个工具各司其职,拼成一条完整的"webpack 产物可读化"流水线。
关于费用、速度和精力的三笔账 💰
用 LLM 反混淆不是做慈善,你需要对成本有心理预期。最重要的一条:每遇到一个标识符,工具就会调用一次 LLM。一个中等规模的压缩文件大约有 500 个标识符,也就是说一次完整处理会发起约 500 次请求。
- 费用:500 个标识符的文件,用 OpenAI 的小模型大约花 $0.10~$1.00;用 Gemini 的免费额度通常够用;用本地 Ollama 或 OpenRouter 的免费模型则基本零成本。
- 速度:取决于标识符数量和模型响应速度。本地模型最慢,某些 CPU 环境下单次请求可能要等很久,所以工具给本地模式设置了长达 1800 秒的请求超时,而云端 API 的超时只有 60 秒。
- 精力:处理完别急着删原始文件。把原文件、新文件都纳入版本管理,改完跑一遍测试套件,确认行为一致再谈其他。
想粗略估算一下 token 消耗?工具给的公式很朴素:大概等于文件字符数的两倍。用wc -c量一下你的文件就知道大概量级了:
echo "$((2 * $(wc -c < yourscript.min.js)))"对预算敏感的开发者,推荐从 Gemini 免费层或本地 Ollama 起步,跑通流程后再决定要不要上更强的付费模型。
三个高频坑位与排雷手册 🧯
实战中最容易踩的坑,我帮你提前排掉三个:
坑位一:模型没配好,跑起来全是"原样返回"症状:日志里每个标识符的
->前后名字一模一样。 排查:先--verbose看配置是否解析成功(模型名、API Key、base URL),再确认网络能连通。LLM 调用失败时工具会静默保留原名,所以"名字没变"往往是上游出问题的信号,而不是模型觉得名字已经够好。
坑位二:webpack 产物直接处理,效果一塌糊涂症状:改完的代码里还残留大量
__webpack_require__、__d(function(...){...})之类的结构,局部变量改名了但整体依然读不懂。 排查:记住 HumanifyJS 只重命名标识符。先过一遍 webcrack 把模块结构解开,再交给 humanify 改名,两者通过管道串联。
坑位三:处理超大文件时把预算烧穿症状:跑完一看账单,费用远超预期。 排查:先用
wc -c估算 token 量,再用--context-size把上下文窗口调小(比如 256),牺牲一点命名准确度换成本可控。也可以先用 webcrack 拆包,让每个文件变小后再分别处理。
再提醒一个容易误判的点:你以为"反混淆会改逻辑"?不会。前面说过,所有改动都发生在 AST 层的标识符重命名,程序的行为保持不变。这正是它可以放心接入 CI/CD 流程的原因——跑完对比测试不红,就能合入。
下一步,把它变成你的日常工具 🔧
你不需要等到半夜加班才想起它。几个可以立刻上手的用法:
- 拿到陌生库的压缩版源码,先跑一遍再阅读
- 排查生产环境的报错堆栈,把
bundle.min.js还原成可读版本对照行号 - 配合 webcrack,把历史遗留的 webpack 老产物整理成可维护的源码雏形
最后把命令备忘收藏一下,这一套流程你大概率会反复用到:
# 免费方案:Gemini 免费层 export GEMINI_API_KEY=你的密钥 humanify gemini app.min.js -o app.js # 本地私有方案:Ollama humanify ollama app.min.js -o app.js # 预算充足追求最佳效果:OpenAI humanify openai app.min.js -o app.jsHumanifyJS 是 MIT 许可的开源项目,代码完全开放可审查。想深入了解或参与改进,可以 clone 它的仓库(https://gitcode.com/gh_mirrors/hu/humanify)看看src/rename/下的实现,那里藏着它所有"防呆设计"的细节。
现在,去把那个让你头疼的bundle.min.js拖进来跑一遍吧。十分钟之后你会回来感谢自己——那些a、e、t背后藏着的逻辑,终于不用靠猜了。🚀
【免费下载链接】humanifyDeobfuscate Javascript code using ChatGPT项目地址: https://gitcode.com/gh_mirrors/hu/humanify
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考