JS混淆字符串解密不能靠正则硬匹配,因混淆结构多变且易误伤;稳定方式是基于AST静态分析,精准定位String.fromCharCode、atob等解密模式,忽略无副作用表达式,兼顾安全与兼容性。

JS混淆字符串解密为什么不能靠正则硬匹配
因为混淆后的代码结构千变万化:有的用数组索引拼接,有的套多层 atob + eval + 自调用函数,还有的把字符串拆成 Unicode 码点再动态组装。正则一换就失效,而且容易误伤正常 JS 逻辑。
真正稳定的方式是模拟 JS 执行环境或还原语义——AST 就是后者的关键入口:它不关心变量名、空格、括号风格,只保留语法结构和运算关系,正好绕过混淆的表层干扰。
- 混淆常把字符串藏在
Array.prototype.map或String.fromCharCode调用链里,AST 能准确定位到这些节点 - 某些混淆器会插入无副作用的表达式(如
void 0、!1),AST 可以安全忽略它们,只提取有值返回的子树 - 直接用
execjs或PyExecJS运行可能触发反调试(比如检查window、document),而 AST 解析纯静态,零风险
用 esprima + ast.walk 提取字符串字面量和解密逻辑
Python 本身不原生解析 JS,得靠第三方库。推荐 esprima(通过 esprima-python 绑定)或更轻量的 slimit;注意别用已停更的 pyjsparser,对 ES6+ 支持差,遇到箭头函数或模板字符串会直接报错 ParseError: Unexpected token。
核心思路是:先 parse 成 AST,再遍历所有 Literal 和 CallExpression 节点,识别出常见解密模式:
立即学习“Python免费学习笔记(深入)”;
-
atob("SGVsbG8=")→ 直接 base64 解码 -
String.fromCharCode(72, 101, 108, 108, 111)→ 提取数字参数并转字符 -
["H", "e", "l", "l", "o"].join("")→ 检查ArrayExpression+CallExpression的callee.property.name === "join"
示例片段(简化版):
import esprima from esprima import walk <p>def extract_strings(node): if isinstance(node, esprima.nodes.Literal) and isinstance(node.value, str): return node.value if isinstance(node, esprima.nodes.CallExpression): if (hasattr(node.callee, 'name') and node.callee.name == 'atob' and hasattr(node.arguments[0], 'value')): return base64.b64decode(node.arguments[0].value).decode()</p><h1>其他模式类似处理...</h1>
遇到动态 key 或自执行函数怎么办
有些混淆会把解密函数本身也混淆,比如 (function(a,b){...})("key","data"),或者用对象属性名当密钥:obj[_0x1234[0]]。这时候光看字符串不够,得做简单符号求值。
- 优先提取所有
ArrayExpression字面量(如["hello"]),统一转成明文备用 - 对
MemberExpression(如a[b]),若b是Literal,直接取索引;若是Identifier,需向上找同作用域的赋值语句(比如var b = 0;) - 避免递归求值整个作用域——太重且易出错。只处理一级变量绑定 + 字面量数组,覆盖 90% 常见混淆即可
典型坑:_0x1234 这类变量名常被重复声明(不同函数内),不能全局缓存;每次进入新 FunctionExpression 都要清空当前作用域映射。
混淆升级后 AST 方案还灵不灵
当混淆器开始插入控制流扁平化(如 while(true){switch(_0x1){case 0:...break;case 1:...}})或使用 WebAssembly 加载密钥时,纯 AST 就力不从心了。这时候必须切换策略:
- 先用 AST 定位到可疑的
while/switch块,提取 case 分支里的字符串操作,再用 Python 模拟执行该分支(而非整个函数) - 对 WASM 场景,基本放弃静态分析——改用
playwright启动真实 Chromium,hookWebAssembly.instantiate并 dump 内存中的解密结果 - 记住:AST 不是银弹,它的价值在于「快、稳、可嵌入」,适合做第一层过滤;复杂逻辑该上浏览器环境就得上,别硬扛
最常被忽略的一点:混淆代码里可能混着真实业务逻辑,比如某个 atob 解出来是 URL,但后面还有个 .replace(/\./g, "/") 才是最终请求地址——AST 解析必须连同后续调用链一起捕获,不能只截第一个函数调用。


















