JavaScript 混淆程度与类型 (Obfuscation Levels and Techniques)
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)
- 动态构造
debugger字符串 + 定时器/循环,开着 DevTools 就反复触发暂停。见 防盗链_防克隆跳转机制 (preventCheat)。
域名锁 (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.domaingetter 守卫绕过,debugProtection 注释调用点禁掉,页面照常跑。见 防盗链_防克隆跳转机制 (preventCheat) 的"万能零改动方案"。
拿到混淆 JS 的快速判断法
看 3 处特征,决定用什么手段:
- 有
var _0x[hex]=['str',...]或function _0x[hex](){var _0x...=['..']}→ 字符串混淆(3 级),上 webcrack - 有
while(!![])+switch+ 海量0x→ 控制流扁平化(4 级),别指望全自动 - 有
constructor("return this")/return this包装 → 自防御,反混淆后要验会不会自毁
先按特征定档,再选工具;上来就正则的路线基本必翻车。
相关
- JavaScript 混淆逆向与反混淆 (JavaScript Obfuscation Reverse Engineering)
- 正则反混淆破坏旋转器 (Regex Deobfuscation Breaks the Rotator)
- 防盗链_防克隆跳转机制 (preventCheat)
本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/Web