混淆还原

目前最常见的商业混淆方案。 特征识别: 1. 文件顶部有一个超大数组(几十至几百个 Base64 字符串) 2. 紧随其后有一个数组位移函数(自执行,通过对数组进行旋转还原顺序) 3. 所有字符串通过 _0xXXXX(index) 形式的函数取值 还原方法: 还原方法:将 eval 替换为 console.log,直接打印出解密后的代码。 JJEncode 将代码转换为全由 $ 和 + 等符号组成的字符串;AAEncode 转换为表情符号。 还原:直接在 Console 中粘贴执行,或使用在线工具解码。 仅使用 , !, (, ), + 6 个字符编写任

分享

官方文档:https://babeljs.io/docs/
适用场景:识别并还原 JS 代码混淆(OB混淆、JsJiami、压缩混淆等),提取可读逻辑


混淆类型

OB 混淆(obfuscator.io)

目前最常见的商业混淆方案。

特征识别:

  1. 文件顶部有一个超大数组(几十至几百个 Base64 字符串)
  2. 紧随其后有一个数组位移函数(自执行,通过对数组进行旋转还原顺序)
  3. 所有字符串通过 _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 生成代码

常见还原操作:

  1. 字符串还原:找到字符串解密函数,在 AST 遍历时调用它,将 CallExpression 替换为字面量字符串
  2. 变量名还原:将单字母或 _0x 开头的变量名替换为语义化名称
  3. 控制流还原:将 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 分析

分析建议

  1. 先格式化,再找入口(搜索加密关键字)
  2. OB 混淆不必完全还原,找到字符串解密函数后可以直接在 Console 调用它查看任意位置的明文
  3. 遇到多层套娃(eval 里再 eval)时,逐层剥离,每层单独处理
  4. 保留一份原始混淆文件备份,避免修改出错后无法恢复

最佳实践

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(windowdocumentfetch),Node.js 中这些不存在。
解决: 参考补环境方案,在 Node.js 中模拟缺失的浏览器 API;或用 vm.runInNewContext 提供最小化环境。


参见

Babel AST入门
js逆向调试技巧
补环境
Webpack逆向还原

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog