混淆还原
目前最常见的商业混淆方案。 特征识别: 1. 文件顶部有一个超大数组(几十至几百个 Base64 字符串) 2. 紧随其后有一个数组位移函数(自执行,通过对数组进行旋转还原顺序) 3. 所有字符串通过 _0xXXXX(index) 形式的函数取值 还原方法: 还原方法:将 eval 替换为 console.log,直接打印出解密后的代码。 JJEncode 将代码转换为全由 $ 和 + 等符号组成的字符串;AAEncode 转换为表情符号。 还原:直接在 Console 中粘贴执行,或使用在线工具解码。 仅使用 , !, (, ), + 6 个字符编写任
官方文档:https://babeljs.io/docs/
适用场景:识别并还原 JS 代码混淆(OB混淆、JsJiami、压缩混淆等),提取可读逻辑
混淆类型
OB 混淆(obfuscator.io)
目前最常见的商业混淆方案。
特征识别:
- 文件顶部有一个超大数组(几十至几百个 Base64 字符串)
- 紧随其后有一个数组位移函数(自执行,通过对数组进行旋转还原顺序)
- 所有字符串通过
_0xXXXX(index)形式的函数取值
// 典型 OB 混淆结构
var _0x1a2b = ['aGVsbG8=', 'd29ybGQ=', ...];
(function(_0x1a2b, _0x3c4d) {
// 数组位移还原逻辑
})(_0x1a2b, 0x1234);
function _0x5e6f(_0x7g8h) {
return _0x1a2b[_0x7g8h];
}
还原方法:
- 工具:ob-decrypt(AST 分析还原)
- 手动:在 Console 中调用
_0xXXXX(index)逐个查看字符串内容 - 本地调试:将整个 JS 文件下载,使用 Node.js 运行后打印字符串数组
eval 混淆
eval(function(p,a,c,k,e,r){...}('...', 62, 100, '...'.split('|'), 0, {}))
还原方法:将 eval 替换为 console.log,直接打印出解密后的代码。
// 在 Console 中执行
(function(p,a,c,k,e,r){console.log(...)}('...'))
JJEncode / AAEncode
JJEncode 将代码转换为全由 $ 和 + 等符号组成的字符串;AAEncode 转换为表情符号。
还原:直接在 Console 中粘贴执行,或使用在线工具解码。
JSFuck
仅使用 [], !, (, ), + 6 个字符编写任意 JS 代码。
还原:Console 直接执行,或使用 JSFuck 解码器。
代码格式化
| 工具 | 使用方式 |
|---|---|
| DevTools Pretty Print | Sources 面板 {} 按钮 |
| Prettier | npx prettier --write file.js |
| js-beautify | npx js-beautify -o out.js in.js |
| 在线工具 | beautifier.io |
AST 分析还原
对于复杂的变量名混淆(变量名改为 _0x1a2b 等),手动分析效率低,可借助 AST 工具批量还原。
工具链:
@babel/parser → 解析 JS 为 AST
@babel/traverse → 遍历和修改 AST 节点
@babel/generator → 将修改后的 AST 生成代码
常见还原操作:
- 字符串还原:找到字符串解密函数,在 AST 遍历时调用它,将
CallExpression替换为字面量字符串 - 变量名还原:将单字母或
_0x开头的变量名替换为语义化名称 - 控制流还原:将
switch状态机结构还原为顺序代码
// 基础 AST 处理模板
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generator = require('@babel/generator').default;
const fs = require('fs');
const code = fs.readFileSync('obfuscated.js', 'utf-8');
const ast = parser.parse(code);
traverse(ast, {
// 替换字符串调用为字面量
CallExpression(path) {
const { node } = path;
// 判断是否为字符串解密函数调用
// 调用解密函数,替换为结果字面量
}
});
const result = generator(ast).code;
fs.writeFileSync('restored.js', result);
常用在线工具
| 工具 | 地址 | 功能 |
|---|---|---|
| JS Nice | http://www.jsnice.org | 变量重命名 + 类型推断 |
| Beautifier | https://beautifier.io | 代码格式化 |
| de4js | https://de4js.kshift.me | 多种混淆还原 |
| BFW工具箱 | https://tool.bfw.wiki | 混淆/反混淆/加解密 |
| AST Explorer | https://astexplorer.net | 可视化 AST 分析 |
分析建议
- 先格式化,再找入口(搜索加密关键字)
- OB 混淆不必完全还原,找到字符串解密函数后可以直接在 Console 调用它查看任意位置的明文
- 遇到多层套娃(eval 里再 eval)时,逐层剥离,每层单独处理
- 保留一份原始混淆文件备份,避免修改出错后无法恢复
最佳实践
OB 混淆优先找字符串解密函数:OB(Obfuscator.io)混淆后所有字符串都通过一个解密函数(_0x1234)取出,在 DevTools Console 中调用这个函数可以直接看到所有明文字符串,无需完整还原代码。
压缩混淆用工具自动格式化:prettier --parser babel 或 Chrome DevTools 的"Pretty Print"({}按钮)可快速格式化压缩代码,格式化后再手动阅读,效率远高于阅读压缩代码。
变量名混淆用 Babel 重命名:用 AST 工具(@babel/traverse)遍历变量声明,按词法作用域分析后重命名为有意义的名称;对于 _0xabc 这类短名,可以用 Chrome DevTools Snippets 批量重命名。
eval 混淆的代码拦截执行:Hook eval 函数打印被执行的代码:const _eval = eval; eval = (code) => { console.log(code); return _eval(code) },每层 eval 都会被记录,逐层查看解混淆后的代码。
利用断点查看中间状态而非完整还原:在关键操作前打断点(如 XHR send、加密函数入口),直接从 Scope 面板读取明文数据,比把整段代码还原为可读代码效率高,也更可靠。
常见陷阱
陷阱:格式化工具破坏了代码逻辑
现象: 用 Prettier 格式化后代码报语法错误或行为改变。
原因: 部分混淆代码利用了 JS 边缘语法(逗号运算符、连续赋值、条件表达式链),Prettier 在不理解语义的情况下格式化可能改变运算优先级。
解决: 先用 Chrome DevTools 内置的 Pretty Print(只做缩进,不改语法),确认逻辑正常后再考虑更激进的格式化;格式化后务必在 Console 测试一个已知输入的输出结果。
陷阱:字符串解密函数有反调试检测
现象: 在 Console 调用字符串解密函数时,页面触发 debugger 或函数返回错误结果。
原因: OB 混淆有时在字符串解密函数中加入调用次数检测或时间检测,直接调用会触发反调试分支。
解决: 先找到反调试逻辑(通常是 setInterval 循环 debugger),用 "Never pause here" 跳过;或直接把整个混淆文件里的 debugger 语句全部替换为空,再重新执行。
陷阱:还原后的代码在 Node.js 中运行不了
现象: 在 Console 测试正常的代码复制到 Node.js 后报错。
原因: 代码依赖浏览器 API(window、document、fetch),Node.js 中这些不存在。
解决: 参考补环境方案,在 Node.js 中模拟缺失的浏览器 API;或用 vm.runInNewContext 提供最小化环境。