一文详解常见的JavaScript混淆类型
作者:shenyan~
一、变量/函数名混淆
原理:将有意义的变量名替换为无意义的短名(如 _0xabc12
, a
, b1
),让代码失去可读性。
示例
function _0x1f2a(_0x1234) { console['log'](_0x1234); } _0x1f2a('你好');
原始逻辑是:
function sayHello(name) { console.log(name); } sayHello('你好');
目的:隐藏函数功能,增加逆向难度。
解决方法
使用
js-beautify
美化代码使用 Babel AST 或正则替换还原变量名
人工或自动语义命名(如 第一层, 参数1, 输出)
二、字符串混淆
原理:将字符串转为 Hex、Unicode、Base64 等格式,运行时再解密。
示例
var a = '\x63\x6f\x6e\x73\x6f\x6c\x65'; var b = a + '\x2e\x6c\x6f\x67'; eval(b + '("hello")');
等价于:
console.log("hello");
目的:隐藏字符串内容,如 API、函数名、密钥。
解决方法
用
console.log
打印eval
参数写脚本解码 hex/base64/unicode
Babel AST 静态替换解码函数
三、控制流扁平化
原理:将原本顺序执行的代码拆成块,用 switch/while/jump 执行,打乱执行顺序。
示例
(function(){ var _idx = 0; var _arr = ['a', 'b', 'c']; while(true){ switch(_arr[_idx++]) { case 'a': console.log("1"); break; case 'b': console.log("2"); break; case 'c': return; } } })();
目的:打乱逻辑结构,阻止静态分析。
解决方法
静态分析 switch case 执行顺序
用 Babel 拆成顺序逻辑
人工逐步调试每一步
四、数组映射字符串
原理:把关键字符串存在数组中,用数组下标访问调用。
示例
var arr = ['log', 'hello']; console[arr[0]](arr[1]);
等价于:
console.log("hello");
目的:隐藏调用结构。
解决方法
使用脚本将数组反查原值并替换所有引用
Babel traverse 实现自动替换
五、多层 eval
原理:用多层嵌套的 eval
执行,防止静态阅读和调试。
示例
eval(eval("String.fromCharCode(99,111,110,115,111,108,101)+'.log(\"hi\")'"));
最终等价于:
console.log("hi");
解决方法
Hook
eval
函数打印执行参数用
console.log
替代eval
查看内部结构把 eval 参数提取出来还原
六、new Function 动态执行
原理:用 new Function()
构造运行时代码。
示例
var fn = new Function('console.log("hi")'); fn();
目的:等价于 eval,隐藏逻辑。
解决方法
替换为
console.log
输出代码内容在浏览器中注入 Hook Function 构造器
七、动态网络加载代码
原理:运行时从网络下载 JS 脚本,使用 eval
或 script
标签执行。
示例
fetch('/api/js').then(r => r.text()).then(eval);
目的:绕过静态分析,延迟投毒。
解决方法
抓包获取真实脚本内容
屏蔽动态加载,分析本地脚本
Hook fetch / xhr 返回体
八、JS VM 虚拟机混淆
原理:把 JS 编译成字节码,运行一个自定义解释器执行。
示例
var code = [0x12, 0x34, 0x78]; MyVM.run(code);
目的:完全隐藏真实逻辑,甚至加壳。
解决方法
分析 VM 实现,还原 opcode 与行为映射
手动构建 opcode -> JS 代码的转换脚本
跟踪运行行为,dump 中间变量
九、JSFuck 编码混淆
原理:用仅 [ ] ( ) + !
六个字符实现 JS 代码。
示例
[][(![]+[])[+[]]+([][[]]+[])[+!+[]]+...][([]+[])[+[]]+...][([]+[])[+[]]+...]('alert(1)')();
解决方法
使用 https://enkhee-osiris.github.io/Decoder-JSFuck/ 解码
手写 JSFuck 解码器或 AST 解析器还原
十、反调试/反控制台
原理:检测是否打开了 devtools 调试器,阻止控制台输出。
示例
setInterval(function(){ if(window.outerHeight - window.innerHeight > 100) { alert("调试器检测"); } }, 1000);
目的:防止调试器断点分析。
解决方法
注释相关检测代码
重写相关函数如
console.log
,window.outerHeight
使用无头浏览器配合 bypass 技术
十一、实战举例
11.1 混淆代码
(function (_0x3f12d1, _0x1b23f4) { var _0xabc123 = function (_0x5a72c1) { while (--_0x5a72c1) { _0x3f12d1['push'](_0x3f12d1['shift']()); } }; _0xabc123(++_0x1b23f4); })(['log', 'Hello\x20World'], 0x1); var _0x55ee = function (_0x1d3f84, _0x2f7e3b) { _0x1d3f84 = _0x1d3f84 - 0x0; var _0x4328cb = ['log', 'Hello World']; return _0x4328cb[_0x1d3f84]; }; console[_0x55ee(0x0)](_0x55ee(0x1));
11.2 初步分析
混淆点:
立即执行函数 IIFE 中对数组进行了 shift/rotate 操作
_0x55ee
是一个偏移映射函数(类数组解码)控制台调用变成了动态索引访问
目标是还原出最原始的代码。
11.3 逐步还原
步骤 1:恢复 IIFE 之后的数组内容
(function (_0x3f12d1, _0x1b23f4) { var _0xabc123 = function (_0x5a72c1) { while (--_0x5a72c1) { //arr.shift():移除并返回数组开头的元素。 //arr.push(value):将一个元素添加到数组末尾。 _0x3f12d1['push'](_0x3f12d1['shift']()); } }; _0xabc123(++_0x1b23f4); })(['log', 'Hello World'], 0x1);
执行逻辑等价于:
let arr = ['log', 'Hello World']; arr.push(arr.shift()); // 'log' 被移到最后 // => ['Hello World', 'log']
所以 _0x55ee
实际访问的是:
['Hello World', 'log']
但是注意:混淆里写的是 ['log', 'Hello World']
,所以 IIFE 执行后变成 ['Hello World', 'log']
。
步骤 2:解读 _0x55ee 作用
var _0x55ee = function (_0x1d3f84, _0x2f7e3b) { _0x1d3f84 = _0x1d3f84 - 0x0; var _0x4328cb = ['log', 'Hello World']; return _0x4328cb[_0x1d3f84]; };
但注意:这个数组 ['log', 'Hello World']
是原始的,没有做 shift。
所以这里 _0x55ee(0x0)
实际返回的是 'log'
,_0x55ee(0x1)
返回 'Hello World'
。
步骤 3:替换 console 调用部分
console[_0x55ee(0x0)](_0x55ee(0x1));
等价于:
console['log']('Hello World');
11.4 还原代码
完全还原结果如下:
console.log('Hello World');
11.5 总结分析
混淆点 | 意图 | 解决方式 |
---|---|---|
数组移位 | 打乱数组索引逻辑 | 运行一次 IIFE 获取结果 |
索引函数 _0x55ee | 模拟字符串映射 | 提前计算函数返回值并替换 |
动态调用 console[...] | 避免搜索关键字 | 静态替换成 console.log |
总结
到此这篇关于JavaScript混淆类型的文章就介绍到这了,更多相关JS混淆类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!