JS 混淆 (JavaScript Obfuscation) 不是"有/无"的二分,而是一条强度谱系。不同站点混淆的目标不同(防抄源码、防调试、防自部署),选择的"档位"也不同。理解档位,才知道该上什么解混淆手段、以及哪些根本不用解(运行时自己能解码,原样能跑)。

核心认知

混淆的前提是 运行时必须能自解码——字符串加密了最终要还原、控制流打平了最终要能执行。所以"解混淆"本质是模拟运行时的解码过程,这决定了它永远有工具可依,但也决定了"解不开 ≠ 不能跑"。

混淆强度分级(0 → 4)

级名称做了什么识别特征还原难度
0原始源码无正常标识符、字符串明文无需
1压缩 (Minification)去空格/换行/注释、局部变量缩写一行超长、无注释、短变量名美化器即可 (prettier)
2轻混淆全局变量/属性重命名 + 字符串编码 (base64/unicode/hex)标识符全变 _0x[hex] 或 a/b/c;字符串是 \x../\u.. 或 base64正则/字符串解码,基本无损
3中混淆字符串数组 + 旋转器 + 解码函数 + 十六进制标识符 (obfuscator.io 默认档)var _0xabcd=['str1','str2',...];while(!![]) 旋转器;_0x(0x..) 解码调用webcrack 一次解出
4重混淆控制流扁平化 + 死代码注入 + 自防御 + 多参解码器 + 字符类二次加密整段 switch+while(!![]) 分发;解码函数多参数;大量无调用函数;检测到被格式化就崩无全自动工具,需专门/手动

混淆技术类型(按技术维度)

标识符重命名 (Identifier Mangling)

  • 变量/函数/参数改成 _0x[hex] 或单字母。防"人读懂",不防运行。
  • 解:webcrack 不做标识符美化——它解完字符串数组后,变量名仍是 _0x。需要可读标识符得再上重命名工具。

字符串混淆 (String Obfuscation)

  • 字符串数组 (String Array):全部字符串字面量收进一个数组,运行时代码用 _0x(索引) 查表。
  • 旋转器 (Rotator):数组声明后跟一个 while(!![]){try{校验和}catch{...}} IIFE,每轮 push(shift()),直到校验和匹配——把数组次序打乱,阻止按索引猜字符串。
  • 字符类加密 (Character-Class Filter):关键串(域名/URL/debugger)再套 str.replace(/[字符类]/g,''),返回的仍是密文,如 'htSvTtIps:B//A...' 过滤后才是 https://toolonline.net。grep 永远搜不到,必须先解码。

数字字面量编码:0x1da 十六进制 + 算术表达式,破坏数字可读性。

控制流扁平化 (Control Flow Flattening, CFF)

  • 把 if/else、循环拆成"状态机":一个 while(!![]) + switch 分发,加大量 0x 常量当状态值。是重混淆里最影响可读性的一招,也是全自动解混淆最难啃的。

不透明谓词 (Opaque Predicates)

  • 插入恒真/恒假的复杂表达式(if(1+1==2) 的伪装版),把真逻辑淹没,诱导人读错分支。死代码注入 (Dead Code Injection) 常与之配套:塞几十个没人调用的假函数。

自防御 (Self-Defending)

  • 用 constructor("return this") 拿全局,检测代码是否被格式化/改写,一旦检测到就抛异常或死循环,让"解完跑不了"。识别特征:return this、constructor("return"。

反调试 (Debug Protection)

域名锁 (Domain Lock)

  • 检测 document.domain || location.hostname 是否后缀匹配白名单,不匹配就 location 跳回原站。是"防自部署"核心。

解混淆工具与能力边界

目标手段边界
美化prettier / js-beautify只对 minify/丑化;对字符串数组无效
字符串数组+旋转器webcrack中混淆一次解;对含 import 的 ES Module 语法失败
运行时映射自写脚本:抽出"数组函数+旋转器+解码函数"真实运行,建 index→字符串 表批量替换最稳,不赌旋转器逻辑
控制流扁平化webcrack、JStillery高配变体经常解不动或输出仍难读
自防御/反调试webcrack transforms:['debug-protection']同 ES Module 会失败
高配变体(4 级)无全自动常需放弃源码级还原,改走"运行时照样能跑"路线
正则 DIY字符串数组替换会把数组值内联进旋转器校验和,parseInt('createApp')→NaN→死循环,见 正则反混淆破坏旋转器 (Regex Deobfuscation Breaks the Rotator)

实战观测(toolonline.net,127 个 JS)

  • 89/127 是 obfuscator.io 默认档(3 级),webcrack 的 debug-protection transform 一次解出:字符串全明文、控制流完整,但变量名仍是 _0x。→ 3 级是"全自动可解"与"需要手动"的分水岭。
  • 22 个 brain 益智游戏是 javascript-obfuscator 高配变体(4 级):扁平化+自防御+多参解码器+字符类加密,webcrack 解不动。
  • 8 个 image 工具是 ES Module(import{...}),webcrack 语法失败 → 只能定位 debugProtection 调用点并注释掉,源码保持混淆但能跑。
  • 8 个散落小工具仍混淆。
  • 结论:4 级文件放弃源码级还原,走运行时兼容——域名锁用 Document.prototype.domain getter 守卫绕过,debugProtection 注释调用点禁掉,页面照常跑。见 防盗链_防克隆跳转机制 (preventCheat) 的"万能零改动方案"。

拿到混淆 JS 的快速判断法

看 3 处特征,决定用什么手段:

  1. 有 var _0x[hex]=['str',...] 或 function _0x[hex](){var _0x...=['..']} → 字符串混淆(3 级),上 webcrack
  2. 有 while(!![]) + switch + 海量 0x → 控制流扁平化(4 级),别指望全自动
  3. 有 constructor("return this") / return this 包装 → 自防御,反混淆后要验会不会自毁

先按特征定档,再选工具;上来就正则的路线基本必翻车。

相关


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web