我们在爬取网站的时候,会遇到一些需要分析接口或 URL 信息的情况,这时会有各种各样类似加密的情况
-
某个网站的URL 带有一些看不懂的长串加密字符,要抓取就必须懂的这些参数是怎么构造的,否则我们连完整的 URL 都构造不出来,更不用说爬取了
-
在分析某个网站的 Ajax 接口时,可以看到接口的一些参数也是加密的, Request Headers 里面的也可能带有一些加密参数, 如果不知道这些参数的具体构造逻辑,就没法直接用程序来模拟这些 Ajax 请求
-
翻看网站的 JavaScrapt 源代码,可以发现很多压缩了或者看不太懂字符, 比如 JavaScrapt 文件名被编码, 文件的内容被压缩成几行, 变量被修改成单个字符或者一些十六进制的字符,这些导致我们无法轻易根据 JavaScrapt 源代码找出某些接口的加密逻辑
以上情况基本上是网站为了保护其数据而采取的一些措施,我们可以把它分为两大类
-
URL/API 参数加密
-
JavaScrapt 压缩,混淆和加密
网站数据防护方案
URL/API 参数加密
网站运营者首先想到的防护措施可能是对某些数据接口的参数进行加密,比如给某些 URL 的参数加上校验码, 给一些 ID 信息编码, 给某些 API 请求加上 token/ sign 等签名, 这样这些请求发送到服务器时,服务器会通过客户端发来的一些请求信息以及双方约定好的秘钥等来对当前的的请求进行校验,只有校验通过,才返回对应的数据结果
JavaScript 压缩,混淆 和 加密
接口加密技术看起来确实是一个不错的方案,但是单纯依靠它并不能很好的解决问题。因为:
-
JavaScript 运行于客户端,也就是它必须在用户浏览器端加载并运行
-
Javascript 代码是公开透明的, 也就是说浏览器可以直接获取到正在运行的 JavaScript 的源码
基于这两个原因, JavaScript 代码是不安全的,任何人都可以读,分析,复制,盗用甚至篡改代码
所以说,对于以上情形,客户端 JavaScript 对于某些加密的实现是很容易被找到或模拟的,了解了加密逻辑后,模拟参数的构造和请求也就轻而易举了,所以如果 JavaScript 没有做任何层面的保护的话,接口的加密对数据起不到任何防护作用
如果不想数据被这么轻易获取,那么就要用到 JavaScript 压缩,混淆和加密技术了
代码压缩: 去除 JavaScript 代码中不必要的空格,换行等内容,使源代码压缩为几行内容,降低代码的可读性,当然同时也提高了网站的加载速度
代码混淆: 使用变量替换,字符串阵列化,控制流平坦化,多态变异,僵尸函数,调试保护等手段,是代码变得难以阅读和分析,达到最终保护的目的。但这并不影响代码原有的功能,使理想实用的 JavaScript 保护方案
代码加密:可以通过某种手段将 JavaScript 代码进行加密, 转成人无法阅读或者解析的代码,如借用 WebAssembly 技术,可以直接将 JavaScript 代码用 C/C++ 实现, JavaScript 调用其编译后形成的文件执行相应的功能
URL/API 参数加密
现在绝大多数的网站的数据一般都通过服务器提供的API 来获取的,网站或 App 可以请求某个数据 API 获取对应的数据,然后在把数据展示出来。 但有些数据是比较宝贵或私密的,这些数据肯定需要一些层面上的防护,所以不同的 API 的实现也就对应着不同的安全防护级别
为了提升接口的安全性,客户端和服务端约定一种接口校验方式,一般来说会用到各种加密和编码算法, 如 Base64, Hex 编码, MD5 , AES, DES, RSA 等对称或非对称加密
JavaScript 压缩
JavaScript 压缩其实就是去除 JavaScript 代码中不必要的空格,换行等内容或者把一些可能公用的代码进行处理实现共享,最后输出的结果压缩为几行内容,代码的可读性变的很差,同时仅仅去除空格,换行这样的压缩方式,其实几乎是没有任何防护作用的,因为这种压缩方式仅仅是降低了代码的直接可读性。 因为网上有一些常见的工具就可以直接将其格式化
目前主流的前端开发技术大多都会利用 webpack , Rollup 等工具进行打包。 webpack, Rollup 会对源代码进行编译和压缩,输出几个打包好的 JavaScript 文件,其中我们可以看到输出的 JavaScript 文件名带有一些不规则字符串,同时文件内容可能只有几行, 变量名都用一些简单的字母表示。这其中就包含 JavaScript 压缩技术, 比如一些公共的库输出成 bundle 文件,一些调用逻辑压缩和转义成冗长的几行代码,这些都属于 JavaScript 压缩。
JavaScript 混淆
JavaScript 混淆完全是在 JavaScript 上面进行的处理, 它的目的就是使得 JavaScript 变得难以阅读和分析。
变量名混淆: 将带有含义的变量名,方法名,常量名随机变为毫无意义的乱码字符串
字符串混淆: 将字符串阵列化集中放置并进行 MD5 或 Base64 加密存储,使代码不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口
对象键名替换: 针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系
控制流平坦化: 打乱函数原有的代码的执行流程及调用关系,使代码逻辑变得混乱无序
**无用代码注入:**随机在代码中插入不会被执行的无用代码,进一步是代码看起更加混乱
**调试保护:**基于调试器的特性,对当前运行环境进行校验, 加入一些 debugger 语句,使其在调试模式下难以顺利执行 JavaScript 代码
**多态变异:**使JavaScript 每次被调试调用时,将代码自身立刻发生变异,变为与之前完全不同的代码,即功能不变,只是代码形式发生变异,以杜绝代码被动态分析和调试
**域名锁定:**使 JavaScript 代码只能在指定域名下运行
**特殊编码:**将 JavaScript 完全编码为人不可读的代码,比如表情符号,特殊符号等
在前端开发中,现在实现 JavaScript 混淆的主流实现是 javascript-obfscatr 和 terser 这两个库
以 javascript-obfuscator 为例, 它是支持 ES8 的免费,高效的 JavaScript 混淆库, 可以使得 JavaScript 代码经过混淆后难以被复用,盗用,混淆后的代码具有和原来的代码一模一样的功能
怎么使用能? 首先 要装好 Node.js 12.x 及以上的版本, 确保可以正常使用 npm 命令
首先创建一个项目:
-
创建一个文件夹 例如: js-obfuscate
-
使用工具打开文件夹, 例如 vscode cmd 都行
-
输入 npm init (前面装了 Node.js ,并可以使用 npm 命令)
-
会出现一系列的提示
package name: 你的项目名字叫啥
version: 版本号 (默认 1.0.0)(可选)
description: 对项目的描述(可选)
entry point: 项目的入口文件(默认 index.js)(可选)
test command: 项目启动的时候要用什么命令来执行脚本文件(默认为node app.js)(可选)
git repository: 如果你要将项目上传到git中的话,那么就需要填写git的仓库地址(可选)
keywirds: 项目关键字(我也不知道有啥用,所以我就不写了)
author: 作者的名字(可选)
license: 发行项目需要的证书(可选)
完成之后会出现一个 package.json
然后安装 js-obfuscate 库
npm i -D javascript-obfuscator
安装的时候如果报错,有可能是网络问题,可以多试几次,如果出现了 node_modules 的文件夹依然会报错,可以将命令再执行一遍(不要删之前安装过的),让他继续安装
安装完成之后,node_modules 文件夹下会出现很多的文件夹, 表示安装成功了
创建一个 main.js 文件
const code = `
let x = '1' + 1
console.log('x', x)
`
const options = {
compact: false,
controlFlowFlattening: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
在命令行 : node main.js 运行之后
(function (_0x4d092c, _0x2f5762) {
const _0x281041 = _0x26c1, _0x27e2de = _0x4d092c();
while (!![]) {
try {
const _0x3151b1 = parseInt(_0x281041(0x1f4)) / 0x1 * (parseInt(_0x281041(0x1f3)) / 0x2) + -parseInt(_0x281041(0x1f7)) / 0x3 * (-parseInt(_0x281041(0x1f5)) / 0x4) + parseInt(_0x281041(0x1f8)) / 0x5 + parseInt(_0x281041(0x1f2)) / 0x6 + -parseInt(_0x281041(0x1fc)) / 0x7 * (-parseInt(_0x281041(0x1fb)) / 0x8) + -parseInt(_0x281041(0x1f9)) / 0x9 + parseInt(_0x281041(0x1f6)) / 0xa * (-parseInt(_0x281041(0x1fa)) / 0xb);
if (_0x3151b1 === _0x2f5762)
break;
else
_0x27e2de['push'](_0x27e2de['shift']());
} catch (_0x456666) {
_0x27e2de['push'](_0x27e2de['shift']());
}
}
}(_0x3a8e, 0x236c9));
let x = '1' + 0x1;
function _0x26c1(_0x31b95d, _0x36c78f) {
const _0x3a8e23 = _0x3a8e();
return _0x26c1 = function (_0x26c120, _0x2b8ed3) {
_0x26c120 = _0x26c120 - 0x1f2;
let _0x35ab0b = _0x3a8e23[_0x26c120];
return _0x35ab0b;
}, _0x26c1(_0x31b95d, _0x36c78f);
}
console['log']('x', x);
function _0x3a8e() {
const _0x4ea833 = [
'232AFrliB',
'20oCyCxY',
'13893ZUBQDQ',
'225280tUMyyY',
'1679427ppLFEE',
'2203410wXvAkQ',
'280ZIEIAh',
'41482jszBjV',
'502092KVtJsN',
'127574cmdaFW',
'2OBrzKA'
];
_0x3a8e = function () {
return _0x4ea833;
};
return _0x3a8e();
这里我们定义了两个变量,一个是 code ,即需要被混淆的代码,另一个是混淆选项 options ,是一个 Object ,接下来我们引入了 javascript-obfuscator 这个库, 然后定义了一个方法, 给其传入 code 和 options 来获取混淆后的代码,最后在控制台输出混淆后的代码
代码压缩
javascript-obfuscator 也提供了代码压缩功能, 使用其参数 compact 即可完成 JavaScript 代码的压缩,输出为一行内容。参数 compact 的默认值是 true ,如果定义为 false 那么混淆后的代码会分行展示
const code = `
let x = '1' + 1
console.log('x', x)
`
const options = {
compact: true,
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
可以看到,代码完全挤在一起了
const _0x5c6b41=_0x546d;(function(_0xebf384,_0x54f3a9){const _0x302b00=_0x546d,_0x367638=_0xebf384();while(!![]){try{const _0x249619=parseInt(_0x302b00(0x183))/0x1+-parseInt(_0x302b00(0x185))/0x2*(parseInt(_0x302b00(0x17f))/0x3)+parseInt(_0x302b00(0x182))/0x4+-parseInt(_0x302b00(0x188))/0x5+-parseInt(_0x302b00(0x187))/0x6*(parseInt(_0x302b00(0x186))/0x7)+parseInt(_0x302b00(0x184))/0x8*(-parseInt(_0x302b00(0x180))/0x9)+parseInt(_0x302b00(0x181))/0xa;if(_0x249619===_0x54f3a9)break;else _0x367638['push'](_0x367638['shift']());}catch(_0x9b3150){_0x367638['push'](_0x367638['shift']());}}}(_0x15fd,0xb25e6));function _0x15fd(){const _0x274d2e=['56jlGlMY','4TBRzYu','203VZEcKD','23334gBqhhL','767055ydWwNV','log','2186532ofYyfi','666351DYipZp','3326350TuZLDM','5096152PuNVOt','1366078zwxueh'];_0x15fd=function(){return _0x274d2e;};return _0x15fd();}let x='1'+0x1;function _0x546d(_0x51e0c2,_0x52160b){const _0x15fd52=_0x15fd();return _0x546d=function(_0x546d84,_0x12968b){_0x546d84=_0x546d84-0x17f;let _0x3a2bc9=_0x15fd52[_0x546d84];return _0x3a2bc9;},_0x546d(_0x51e0c2,_0x52160b);}console[_0x5c6b41(0x189)]('x',x);
变量名混淆
变量名混淆可以通过在 javascript-obfuscator 中配置 identifierNamesGenerator 参数来实现。如果将其值设为 hexadecimal ,则会将变量名替换为 十六进制形式的字符串
hexadecimal : 将变量名替换为十六进制形式的字符串 例如 0xabc123
mangled : 将变量名替换为普通的简写字符 例如 a, b , c等
const code = `
let hello = '1' + 1
console.log('hello', hello)
`
const options = {
compact: true,
identifierNamesGenerator: 'mangled'
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
const i=b;(function(c,d){const h=b,e=c();while(!![]){try{const f=parseInt(h(0x10e))/0x1*(-parseInt(h(0x10c))/0x2)+-parseInt(h(0x111))/0x3+parseInt(h(0x115))/0x4*(parseInt(h(0x114))/0x5)+parseInt(h(0x112))/0x6*(-parseInt(h(0x10f))/0x7)+-parseInt(h(0x110))/0x8+-parseInt(h(0x113))/0x9+parseInt(h(0x117))/0xa;if(f===d)break;else e['push'](e['shift']());}catch(g){e['push'](e['shift']());}}}(a,0x6c52f));function a(){const j=['26031360NSHRFA','139570bWOiFJ','log','10vsDbgU','7WyBWmQ','25168GNiOri','1462830btnoTs','5186778KGtaFW','6696261JlGafx','3188285UAKRGV','4oDzIAv','hello'];a=function(){return j;};return a();}function b(c,d){const e=a();return b=function(f,g){f=f-0x10c;let h=e[f];return h;},b(c,d);}let hello='1'+0x1;console[i(0x10d)](i(0x116),hello);
可以看到变量名变成了 a ,b 的形式
也可以设置 identifiersPrefix 参数来控制混淆后的变量前缀
const code = `
let hello = '1' + 1
console.log('hello', hello)
`
const options = {
identifiersPrefix: 'germey'
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
const germey_0x5a7cec=germey_0x3c79;function germey_0x1462(){const _0x256596=['5FxMIWA','14876HiEAWk','4175800QHkiZt','2604196ZCrwwo','95890cnxtCC','20DszwMR','log','30mIbOUa','1720938eHRKlL','137304kEtZRb','2506077rvjWdq'];germey_0x1462=function(){return_0x256596;};return germey_0x1462();}(function(_0x946c93,_0x40e486){const _0x206272=germey_0x3c79,_0x136653=_0x946c93();while(!![]){try{const _0x3771d9=parseInt(_0x206272(0x106))/0x1*(-parseInt(_0x206272(0x105))/0x2)+parseInt(_0x206272(0x10a))/0x3+parseInt(_0x206272(0x10d))/0x4*(-parseInt(_0x206272(0x10c))/0x5)+-parseInt(_0x206272(0x109))/0x6+parseInt(_0x206272(0x104))/0x7+parseInt(_0x206272(0x10e))/0x8+parseInt(_0x206272(0x10b))/0x9*(parseInt(_0x206272(0x108))/0xa);if(_0x3771d9===_0x40e486)break;else _0x136653['push'](_0x136653['shift']());}catch(_0x15bd60){_0x136653['push'](_0x136653['shift']());}}}(germey_0x1462,0x80578));let hello='1'+0x1;function germey_0x3c79(_0x581c10,_0x5dc5b2){const _0x14622a=germey_0x1462();return germey_0x3c79=function(_0x3c79bf,_0xef6b91){_0x3c79bf=_0x3c79bf-0x104;let _0x5e9c40=_0x14622a[_0x3c79bf];return _0x5e9c40;},germey_0x3c79(_0x581c10,_0x5dc5b2);}console[germey_0x5a7cec(0x107)]('hello',hello);
可以看到,混淆后的变量名前缀加上了我们自定义的 germey
另外: renameGlobals 这个参数还可以指定是否混淆全局变量名和函数名称,默认值为 false
const code = `
var $ = function(id) {
return document.getElementById(id);
};
`
const options = {
renameGlobals: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
(function(_0x2126f5,_0x4f498b){var _0x448046=_0x10b3,_0x4912e1=_0x2126f5();while(!![]){try{var _0x5c646b=-parseInt(_0x448046(0x1c3))/0x1*(parseInt(_0x448046(0x1be))/0x2)+parseInt(_0x448046(0x1bf))/0x3*(-parseInt(_0x448046(0x1b9))/0x4)+parseInt(_0x448046(0x1bd))/0x5*(parseInt(_0x448046(0x1bc))/0x6)+-parseInt(_0x448046(0x1c0))/0x7+-parseInt(_0x448046(0x1c2))/0x8+parseInt(_0x448046(0x1bb))/0x9+-parseInt(_0x448046(0x1c4))/0xa*(-parseInt(_0x448046(0x1ba))/0xb);if(_0x5c646b===_0x4f498b)break;else _0x4912e1['push'](_0x4912e1['shift']());}catch(_0x5e6fb7){_0x4912e1['push'](_0x4912e1['shift']());}}}(_0x4ccb,0x6f00e));function _0x10b3(_0x219820,_0x29bda8){var _0x4ccb5c=_0x4ccb();return _0x10b3=function(_0x10b31f,_0x3eb05f){_0x10b31f=_0x10b31f-0x1b9;var _0x1d07f9=_0x4ccb5c[_0x10b31f];return _0x1d07f9;},_0x10b3(_0x219820,_0x29bda8);}var _0x2695a4=function(_0x524c7c){var _0x5dfa95=_0x10b3;returndocument[_0x5dfa95(0x1c1)](_0x524c7c);};function _0x4ccb(){var _0x58c9f1=['getElementById','347616xHAGlf','49906zAzpCm','1090EPDXUe','3556248GSaYKz','229394zZtUgU','700569Wnqqmy','6ofVjXU','1200825mUOHDu','26QTSxHw','3iUmEtj','3885910ffHvVO'];_0x4ccb=function(){return _0x58c9f1;};return _0x4ccb();}
可以看到我们定义的 $ 不见了,如果后文用到了这个变量,可能会出错,因此酌情设置
字符串混淆
字符串混淆,即将一个字符串声明放到一个数组里面, 使之无法被直接搜到。 这可以通过 stringArray 参数控制, 默认是 true 。 此外还可以通过 rotateStringArray 参数来控制数组化后结果的元素顺序, 默认为 true 。 还可以通过 stringArrayEncoding 参数来控制数组的编码形式, 默认不开启编码。 如果将其设置为 true 或 base64 ,则会使用 Base64 另外,还可以通过 stringArrayThreshold 来控制启用编码的概率, 其范围为 0-1 默认为 0.8
const code = `
var a = 'hello world'
`
const options = {
stringArray: true,
rotateStringArray: true,
stringArrayEncoding: ['base64',], // 'base64' or 'rc4' or false
stringArrayThreshold: 1,
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
var _0x35131b=_0x4590;(function(_0x19c057,_0xc27b2d){var _0x15721b=_0x4590,_0xa11bed=_0x19c057();while(!![]){try{var _0x1e0433=parseInt(_0x15721b(0x1f0))/0x1*(-parseInt(_0x15721b(0x1ee))/0x2)+parseInt(_0x15721b(0x1f6))/0x3*(parseInt(_0x15721b(0x1f5))/0x4)+-parseInt(_0x15721b(0x1ef))/0x5+parseInt(_0x15721b(0x1f7))/0x6+-parseInt(_0x15721b(0x1f9))/0x7*(parseInt(_0x15721b(0x1f8))/0x8)+-parseInt(_0x15721b(0x1f3))/0x9*(-parseInt(_0x15721b(0x1f4))/0xa)+-parseInt(_0x15721b(0x1f1))/0xb;if(_0x1e0433===_0xc27b2d)break;else _0xa11bed['push'](_0xa11bed['shift']());}catch(_0x5865c7){_0xa11bed['push'](_0xa11bed['shift']());}}}(_0x2531,0xe366a));function _0x2531(){var _0x4fc026=['otrVC3LczuG','mJi4nZCXne1JrKDPtG','AgvSBg8GD29YBgq','owTLAunqBq','nZqXmtu5mefgtMXYuW','nezctNLXqW','mtGZndyXn0D0y1nxEa','mteWmdC3mJb6v0DvrNi','nJq4ndy0qu5ms2Hx','nZDXD2HLtKi','otC0ogTZD0zLAG','mZq5mdu4merhBgLRBW'];_0x2531=function(){return _0x4fc026;};return _0x2531();}function _0x4590(_0x1d9eee,_0x2ca6fa){var _0x2531df=_0x2531();return _0x4590=function(_0x45909f,_0x694dc4){_0x45909f=_0x45909f-0x1ee;var _0x5b178c=_0x2531df[_0x45909f];if(_0x4590['jbdtDM']===undefined){var _0x283592=function(_0x581a43){var _0x23f657='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x3f5727='',_0x1dd216='';for(var _0x5ea4f8=0x0,_0x18de19,_0x42a870,_0x5edc64=0x0;_0x42a870=_0x581a43['charAt'](_0x5edc64++);~_0x42a870&&(_0x18de19=_0x5ea4f8%0x4?_0x18de19*0x40+_0x42a870:_0x42a870,_0x5ea4f8++%0x4)?_0x3f5727+=String['fromCharCode'](0xff&_0x18de19>>(-0x2*_0x5ea4f8&0x6)):0x0){_0x42a870=_0x23f657['indexOf'](_0x42a870);}for(var _0x427e2f=0x0,_0x3a1436=_0x3f5727['length'];_0x427e2f<_0x3a1436;_0x427e2f++){_0x1dd216+='%'+('00'+_0x3f5727['charCodeAt'](_0x427e2f)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1dd216);};_0x4590['RwPTRB']=_0x283592,_0x1d9eee=arguments,_0x4590['jbdtDM']=!![];}var _0x467615=_0x2531df[0x0],_0x24ad2f=_0x45909f+_0x467615,_0x1db7f0=_0x1d9eee[_0x24ad2f];return!_0x1db7f0?(_0x5b178c=_0x4590['RwPTRB'](_0x5b178c),_0x1d9eee[_0x24ad2f]=_0x5b178c):_0x5b178c=_0x1db7f0,_0x5b178c;},_0x4590(_0x1d9eee,_0x2ca6fa);}var a=_0x35131b(0x1f2);
可以看到 字符串已经找不到了,另外我们还可以用 unicodeEscapeSequence 这个参数对字符串进行 Unicode 转码
const code = `
var a = 'hello world'
`
const options = {
compact: false,
unicodeEscapeSequence: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x363c(_0x228c16, _0x48aa18) {var _0x25c699 = _0x25c6();
return _0x363c = function (_0x363c29, _0x5b2b9c) {
_0x363c29 = _0x363c29 - 0xaa;
var _0x23386b = _0x25c699[_0x363c29];
return _0x23386b;
}, _0x363c(_0x228c16, _0x48aa18);
}
var _0x940dfd = _0x363c;
(function (_0x19f7da, _0xf3603f) {
var _0x4e557e = _0x363c, _0x24e454 = _0x19f7da();
while (!![]) {
try {
var _0x3bd4d1 = -parseInt(_0x4e557e(0xb1)) / 0x1 * (-parseInt(_0x4e557e(0xb0)) / 0x2) + parseInt(_0x4e557e(0xac)) /
0x3 + -parseInt(_0x4e557e(0xaf)) / 0x4 + -parseInt(_0x4e557e(0xab)) / 0x5 * (-parseInt(_0x4e557e(0xad)) / 0x6) + parseInt(_0x4e557e(0xb2)) / 0x7 + -parseInt(_0x4e557e(0xb4)) / 0x8 + -parseInt(_0x4e557e(0xb3)) / 0x9 * (parseInt(_0x4e557e(0xaa)) / 0xa);
if (_0x3bd4d1 === _0xf3603f)
break;
else
_0x24e454['push'](_0x24e454['shift']());
} catch (_0x5cfa3b) {
_0x24e454['push'](_0x24e454['shift']());
}
}
}(_0x25c6, 0x56b69));
function _0x25c6() {
var _0x1fbea8 = [
'\x32\x30\x31\x32\x35\x35\x32\x55\x6c\x4d\x6a\x76\x56',
'\x31\x30\x61\x75\x53\x66\x4d\x48',
'\x33\x35\x30\x37\x38\x33\x30\x49\x53\x72\x64\x61\x64',
'\x31\x33\x31\x35\x35\x30\x33\x71\x55\x66\x63\x6a\x4a',
'\x36\x57\x59\x45\x45\x50\x49',
'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64',
'\x31\x38\x30\x34\x34\x30\x56\x71\x4d\x6b\x7a\x50',
'\x32\x36\x63\x76\x51\x74\x6a\x43',
'\x32\x36\x34\x39\x44\x79\x61\x79\x51\x41',
'\x34\x35\x34\x39\x30\x33\x34\x53\x45\x48\x78\x62\x63',
'\x31\x30\x35\x35\x32\x35\x39\x30\x4c\x50\x62\x67\x78\x51'
];
_0x25c6 = function () {
return _0x1fbea8;
};
return _0x25c6();
}
var a = _0x940dfd(0xae);
代码的自我保护
我们可以通过设置 selfDefending 参数来开启代码的自我保护功能。 开启之后, 混淆后的 JavaScript 会强制以一行形式显示。 如果我们将混淆后的代码进行格式化或者重命名,该段代码将无法执行
const code = `
console.log('hello world')
`
const options = {
selfDefending: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
var _0xd11e3f=_0x439f;(function(_0x1c0305,_0x3a57cf){var _0x4bda0c=_0x439f,_0x404764=_0x1c0305();while(!![]){try{var _0x3613bc=-parseInt(_0x4bda0c(0x87))/0x1*(parseInt(_0x4bda0c(0x8f))/0x2)+parseInt(_0x4bda0c(0x91))/0x3*(parseInt(_0x4bda0c(0x92))/0x4)+-parseInt(_0x4bda0c(0x8c))/0x5*(-parseInt(_0x4bda0c(0x96))/0x6)+-parseInt(_0x4bda0c(0x94))/0x7+-parseInt(_0x4bda0c(0x95))/0x8*(-parseInt(_0x4bda0c(0x93))/0x9)+parseInt(_0x4bda0c(0x8e))/0xa+-parseInt(_0x4bda0c(0x90))/0xb*(parseInt(_0x4bda0c(0x8b))/0xc);if(_0x3613bc===_0x3a57cf)break;else _0x404764['push'](_0x404764['shift']());}catch(_0x2b7399){_0x404764['push'](_0x404764['shift']());}}}(_0x4dd7,0xb197e));var _0x3a491c=(function(){var _0x1820bc=!![];return function(_0x3fdfea,_0x148e5d){var _0x56780d=_0x1820bc?function(){var _0xead5db=_0x439f;if(_0x148e5d){var _0xe980a0=_0x148e5d[_0xead5db(0x8d)](_0x3fdfea,arguments);return _0x148e5d=null,_0xe980a0;}}:function(){};return _0x1820bc=![],_0x56780d;};}()),_0x5d3317=_0x3a491c(this,function(){var _0x4d62e8=_0x439f;return _0x5d3317['toString']()[_0x4d62e8(0x88)](_0x4d62e8(0x89))['toString']()[_0x4d62e8(0x8a)](_0x5d3317)['search'](_0x4d62e8(0x89));});function _0x439f(_0x254843,_0x1192ee){var _0x28eb15=_0x4dd7();return _0x439f=function(_0x5d3317,_0x3a491c){_0x5d3317=_0x5d3317-0x87;var _0x4dd7dc=_0x28eb15[_0x5d3317];return _0x4dd7dc;},_0x439f(_0x254843,_0x1192ee);}function _0x4dd7(){var _0x36da29=['constructor','12LIMnts','3410545JdsbGk','apply','5684260usRpJN','2086ZOaUxx','16507139lursEK','1448274nlOFbv','4VBSaXz','2297367hVvMsn','4346405zFrRkF','40iaruMA','6lVbCUP','log','154hbVypx','search','(((.+)+)+)+$'];_0x4dd7=function(){return _0x36da29;};return _0x4dd7();}_0x5d3317(),console[_0xd11e3f(0x97)]('hello\x20world');
如果我们将混淆后的代码运行是可以正常使用的, 如果格式化之后再运行则会报错
控制流平坦化
控制流平坦化就是将代码的执行逻辑混淆, 使其变得复杂难读。其基本思想是将一些逻辑处理快都统一加上一个前驱逻辑块, 每个逻辑块都有前驱逻辑块进行条件判断和分发, 构成一个个闭环逻辑,这导致整个逻辑十分复杂
在 javascript-obfuscator 中我们可以使用 controlFlowFlattening 变量控制是否开启控制流平坦化
const code = `
(function(){
function foo () {
return function () {
var sum = 1 + 2;
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
}
}
foo()();
})();
`
const options = {
compact: false,
controlFlowFlattening: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x58e5(_0x27b691, _0x5104fa) {var _0x1e139d = _0x1e13();
return _0x58e5 = function (_0x58e5c3, _0x23afd1) {
_0x58e5c3 = _0x58e5c3 - 0x126;
var _0x417bcb = _0x1e139d[_0x58e5c3];
return _0x417bcb;
}, _0x58e5(_0x27b691, _0x5104fa);
}
function _0x1e13() {
var _0xe5c863 = [
'10721970nxDJls',
'log',
'150712pXmEVE',
'182CzrguC',
'7428828IljWVM',
'1623ImHQdj',
'524130sdQmZL',
'166202xBaANC',
'5NZTnsL',
'split',
'16520290nSzLpD',
'448PoSvMN'
];
_0x1e13 = function () {
return _0xe5c863;
};
return _0x1e13();
}
(function (_0x41ed0b, _0x39aa00) {
var _0x2b900e = _0x58e5, _0x3f3043 = _0x41ed0b();
while (!![]) {
try {
var _0x44b040 = -parseInt(_0x2b900e(0x12a)) / 0x1 * (parseInt(_0x2b900e(0x129)) / 0x2) + parseInt(_0x2b900e(0x127))
/ 0x3 * (parseInt(_0x2b900e(0x12d)) / 0x4) + -parseInt(_0x2b900e(0x128)) / 0x5 + -parseInt(_0x2b900e(0x126)) / 0x6 + -parseInt(_0x2b900e(0x131)) / 0x7 * (parseInt(_0x2b900e(0x130)) / 0x8) + parseInt(_0x2b900e(0x12e)) / 0x9 + parseInt(_0x2b900e(0x12c)) / 0xa;
if (_0x44b040 === _0x39aa00)
break;
else
_0x3f3043['push'](_0x3f3043['shift']());
} catch (_0xc4f29b) {
_0x3f3043['push'](_0x3f3043['shift']());
}
}
}(_0x1e13, 0xa0134), (function () {
var _0x5e08d1 = {
'Bsdmq': function (_0x2eda91) {
return _0x2eda91();
}
};
function _0x54c5b1() {
return function () {
var _0x5ce4e9 = _0x58e5, _0x4507e7 = '1|3|4|5|0|2|6'[_0x5ce4e9(0x12b)]('|'), _0x3531ae = 0x0;
while (!![]) {
switch (_0x4507e7[_0x3531ae++]) {
case '0':
console[_0x5ce4e9(0x12f)](0x4);
continue;
case '1':
var _0x39a77b = 0x1 + 0x2;
continue;
case '2':
console['log'](0x5);
continue;
case '3':
console['log'](0x1);
continue;
case '4':
console[_0x5ce4e9(0x12f)](0x2);
continue;
case '5':
console['log'](0x3);
continue;
case '6':
console[_0x5ce4e9(0x12f)](0x6);
continue;
}
break;
}
};
}
_0x5e08d1['Bsdmq'](_0x54c5b1)();
}()));
另外我们还可以使用 controlFlowFlatteningThreshold 这个参数来控制比例,取值范围是 0 到 1,默认值为 0.75, 如果为 0 则是不设置
无用代码注入
无用代码注入即不会被执行的代码或对上下文没有任何影响的代码, 注入之后可以对现有的 JavaScript 代码阅读形成干扰。 我们可以使用 deadCodeInjection 参数开启这个选项,默认为 false
const code = `
console.log('abc');
console.log('cde');
console.log('efg');
console.log('hij');
`
const options = {
compact: false,
deadCodeInjection: true,
deadCodeInjectionThreshold: 1
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x488e() {var _0x3c144e = [
'abc',
'log',
'1401706bMgzVB',
'3053718NwChnb',
'15781493nKgPHo',
'hij',
'2606500HDxpBP',
'16320UHJLfq',
'2861355LWkVsM',
'1142771eYCHKY',
'efg'
];
_0x488e = function () {
return _0x3c144e;
};
return _0x488e();
}
function _0x49e4(_0x57d459, _0x5e4fbf) {
var _0x488e97 = _0x488e();
return _0x49e4 = function (_0x49e47c, _0x6c16a7) {
_0x49e47c = _0x49e47c - 0x1dd;
var _0x2d6b25 = _0x488e97[_0x49e47c];
return _0x2d6b25;
}, _0x49e4(_0x57d459, _0x5e4fbf);
}
var _0x585613 = _0x49e4;
(function (_0x248c50, _0x13dd30) {
var _0x39d544 = _0x49e4, _0xf5f349 = _0x248c50();
while (!![]) {
try {
var _0x2cb33b = parseInt(_0x39d544(0x1e7)) / 0x1 + -parseInt(_0x39d544(0x1e0)) / 0x2 + -parseInt(_0x39d544(0x1e6)) / 0x3 + -parseInt(_0x39d544(0x1e4)) / 0x4 + parseInt(_0x39d544(0x1e5)) / 0x5 + -parseInt(_0x39d544(0x1e1)) / 0x6 + parseInt(_0x39d544(0x1e2)) / 0x7;
if (_0x2cb33b === _0x13dd30)
break;
else
_0xf5f349['push'](_0xf5f349['shift']());
} catch (_0x51933b) {
_0xf5f349['push'](_0xf5f349['shift']());
}
}
}(_0x488e, 0x8ee66), console[_0x585613(0x1df)](_0x585613(0x1de)), console[_0x585613(0x1df)]('cde'), console[_0x585613(0x1df)](_0x585613(0x1dd)), console[_0x585613(0x1df)](_0x585613(0x1e3)));
这里可以看到,在每个方法内部额外增加了一些 if....else 语句,这些代码有的会被执行,但不影响结果,有的压根不会执行。
在 javascript-obfuscator 中, 我们可以通过 deadCodeInjection 参数控制代码的注入
还可以通过 deadCodeInjectionThreshold 参数控制无用代码注入的比例 取值范围 0-1 默认 0.4
对象键名替换
如果是一个对象,可以使用 transformObjectkeys 来对对象的键值进行替换
const code = `
(function(){
var object = {
foo: 'test1',
bar: {
baz: 'test2'
}
};
})();
`
const options = {
compact: false,
transformObjectKeys: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x5980(_0x29a057, _0x2d593f) {var _0x470dfa = _0x470d();
return _0x5980 = function (_0x598085, _0x47a11e) {
_0x598085 = _0x598085 - 0x15f;
var _0x52f61c = _0x470dfa[_0x598085];
return _0x52f61c;
}, _0x5980(_0x29a057, _0x2d593f);
}
(function (_0x184600, _0x14a2e4) {
var _0x626b65 = _0x5980, _0x57441a = _0x184600();
while (!![]) {
try {
var _0x511c72 = -parseInt(_0x626b65(0x16c)) / 0x1 * (parseInt(_0x626b65(0x169)) / 0x2) + parseInt(_0x626b65(0x162)) / 0x3 * (-parseInt(_0x626b65(0x160)) / 0x4) + parseInt(_0x626b65(0x15f)) / 0x5 * (parseInt(_0x626b65(0x16a)) / 0x6) + parseInt(_0x626b65(0x16e)) / 0x7 + parseInt(_0x626b65(0x16d)) / 0x8 * (-parseInt(_0x626b65(0x164)) / 0x9) + -parseInt(_0x626b65(0x161)) / 0xa * (-parseInt(_0x626b65(0x16b)) / 0xb) + parseInt(_0x626b65(0x168)) / 0xc * (parseInt(_0x626b65(0x166)) / 0xd);
if (_0x511c72 === _0x14a2e4)
break;
else
_0x57441a['push'](_0x57441a['shift']());
} catch (_0x34fab0) {
_0x57441a['push'](_0x57441a['shift']());
}
}
}(_0x470d, 0x1c3c5), (function () {
var _0x230e06 = _0x5980, _0x3791d7 = {};
_0x3791d7[_0x230e06(0x16f)] = _0x230e06(0x165);
var _0x59e14a = {};
_0x59e14a[_0x230e06(0x167)] = 'test1', _0x59e14a[_0x230e06(0x163)] = _0x3791d7;
var _0x3f3925 = _0x59e14a;
}()));
function _0x470d() {
var _0x3d92c8 = [
'4CIKdSY',
'2980asQFWz',
'639417qrHvJJ',
'bar',
'306azjkbB',
'test2',
'91sGTAtg',
'foo',
'200892zgruwY',
'2QIzZua',
'564cFfEZN',
'2519gCnZUy',
'155317nKchsb',
'4168doMXgy',
'677670NkEqkC',
'baz',
'11680tPdurf'
];
_0x470d = function () {
return _0x3d92c8;
};
return _0x470d();
}
可以看到 Object 的变量名被替换为了特殊变量,代码可读性变差
禁用控制台输出
我们可以用 disableConsoleOutput 来禁用掉 console.log 输出功能,加大调试难度
const code = `
console.log('hello world')
`
const options = {
disableConsoleOutput: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x4c23(_0x529d5e,_0x3f3a42){var _0x6403d0=_0x54cf();return _0x4c23=function(_0x4cafa9,_0x47fa2b){_0x4cafa9=_0x4cafa9-0x169;var _0x1416bb=_0x6403d0[_0x4cafa9];return _0x1416bb;},_0x4c23(_0x529d5e,_0x3f3a42);}var _0x4855c1=_0x4c23;function _0x54cf(){var _0x2d65b1=['161640PiLPKs','exception','73OSEmJG','prototype','info','apply','4462FhELTj','981680fHVvFG','608922acGYGj','error','2230MVSzAU','toString','log','28DIFbWN','hello\x20world','constructor','433748yAzFLk','length','bind','13707SZdGMu','console','61735TddpUM','return\x20(function()\x20','table'];_0x54cf=function(){return _0x2d65b1;};return _0x54cf();}(function(_0x3b62e9,_0x25ec1e){var _0x1bc584=_0x4c23,_0x49701b=_0x3b62e9();while(!![]){try{var _0x5964d5=parseInt(_0x1bc584(0x17d))/0x1*(parseInt(_0x1bc584(0x169))/0x2)+parseInt(_0x1bc584(0x17b))/0x3+parseInt(_0x1bc584(0x170))/0x4*(parseInt(_0x1bc584(0x178))/0x5)+parseInt(_0x1bc584(0x16b))/0x6+-parseInt(_0x1bc584(0x173))/0x7+parseInt(_0x1bc584(0x16a))/0x8+-parseInt(_0x1bc584(0x176))/0x9*(parseInt(_0x1bc584(0x16d))/0xa);if(_0x5964d5===_0x25ec1e)break;else _0x49701b['push'](_0x49701b['shift']());}catch(_0x3bb303){_0x49701b['push'](_0x49701b['shift']());}}}(_0x54cf,0x1eb50));var _0x47fa2b=(function(){var _0x525183=!![];return function(_0x52bd98,_0x4fa7d4){var _0x207e5f=_0x525183?function(){var _0x345c2f=_0x4c23;if(_0x4fa7d4){var _0x151451=_0x4fa7d4[_0x345c2f(0x180)](_0x52bd98,arguments);return _0x4fa7d4=null,_0x151451;}}:function(){};return _0x525183=![],_0x207e5f;};}()),_0x4cafa9=_0x47fa2b(this,function(){var _0x1394fc=_0x4c23,_0x151177;try{var _0x25a489=Function(_0x1394fc(0x179)+'{}.constructor(\x22return\x20this\x22)(\x20)'+');');_0x151177=_0x25a489();}catch(_0x41a93c){_0x151177=window;}var _0x8df322=_0x151177[_0x1394fc(0x177)]=_0x151177[_0x1394fc(0x177)]||{},_0x281853=[_0x1394fc(0x16f),'warn',_0x1394fc(0x17f),_0x1394fc(0x16c),_0x1394fc(0x17c),_0x1394fc(0x17a),'trace'];for(var _0x1092fa=0x0;_0x1092fa<_0x281853[_0x1394fc(0x174)];_0x1092fa++){var _0x387edc=_0x47fa2b[_0x1394fc(0x172)][_0x1394fc(0x17e)]['bind'](_0x47fa2b),_0x323379=_0x281853[_0x1092fa],_0x228196=_0x8df322[_0x323379]||_0x387edc;_0x387edc['proto']=_0x47fa2b[_0x1394fc(0x175)](_0x47fa2b),_0x387edc[_0x1394fc(0x16e)]=_0x228196[_0x1394fc(0x16e)]['bind'](_0x228196),_0x8df322[_0x323379]=_0x387edc;}});_0x4cafa9(),console[_0x4855c1(0x16f)](_0x4855c1(0x171));
此时运行这段代码发现不会有输出
调试保护
我们知道在 JavaScript 代码中加入 debugger 关键字,那么执行到该位置的时候,就会进入断点调试模式。 如果在代码多个位置都加入 debugger 关键字, 或者定义某个逻辑来反复执行 debugger, 就会反复不断执行断点调试模式,原本的代码就无法顺利执行了。这个过程可以被称为调试保护
const code = `
for (let i = 0; i < 5; i ++) {
console.log('i', i)
}
`
const options = {
debugProtection: true
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
const _0x5074be=_0x5732;(function(_0x1f8fda,_0xd0b477){const _0x54b142=_0x5732,_0x5e27f1=_0x1f8fda();while(!![]){try{const _0x5b6004=parseInt(_0x54b142(0xa5))/0x1*(parseInt(_0x54b142(0x90))/0x2)+parseInt(_0x54b142(0xa4))/0x3*(parseInt(_0x54b142(0xa3))/0x4)+parseInt(_0x54b142(0x93))/0x5*(-parseInt(_0x54b142(0x99))/0x6)+-parseInt(_0x54b142(0x9b))/0x7*(-parseInt(_0x54b142(0x8d))/0x8)+parseInt(_0x54b142(0x8a))/0x9*(-parseInt(_0x54b142(0x8e))/0xa)+parseInt(_0x54b142(0x9c))/0xb*(parseInt(_0x54b142(0x98))/0xc)+-parseInt(_0x54b142(0x89))/0xd*(parseInt(_0x54b142(0xa1))/0xe);if(_0x5b6004===_0xd0b477)break;else _0x5e27f1['push'](_0x5e27f1['shift']());}catch(_0x325412){_0x5e27f1['push'](_0x5e27f1['shift']());}}}(_0x4ed7,0x42b62));const _0x172872=(function(){let _0x1c32a0=!![];return function(_0xab5ad5,_0x372292){const _0x454208=_0x1c32a0?function(){const _0x56fbd1=_0x5732;if(_0x372292){const _0x2ac506=_0x372292[_0x56fbd1(0x8f)](_0xab5ad5,arguments);return _0x372292=null,_0x2ac506;}}:function(){};return _0x1c32a0=![],_0x454208;};}());(function(){_0x172872(this,function(){const _0x195a67=_0x5732,_0xb2e1e7=new RegExp(_0x195a67(0x94)),0x5215ee=new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z\]\[0-9a-zA-Z_]*)','i'),_0x3ecded=_0x361448(_0x195a67(0x9e));!_0xb2e1e7[_0x195a67(0x8b)](_0x3ecded+_0x195a67(0x9a))||!_0x5215ee['test'](_0x3ecded+_0x195a67(0x95))?_0x3ecded('0'):_0x361448();})();}());function _0x5732(_0x27968c,_0x1f308b){const _0x35e508=_0x4ed7();return _0x5732=function(_0x361448,_0x172872){_0x361448=_0x361448-0x89;let _0x4ed76c=_0x35e508[_0x361448];return _0x4ed76c;},_0x5732(_0x27968c,_0x1f308b);}for(let i=0x0;i<0x5;i++){console[_0x5074be(0x96)]('i',i);}function _0x4ed7(){const _0x507280=['352dZVXHu','call','init','while\x20(true)\x20{}','length','1618064KkIAqb','stateObject','781772JsYWOq','6dRWoSu','1KgjMCA','constructor','91LRDVyE','788463aoMygo','test','debu','14536trNqnc','60EkmNSX','apply','946754hoNyrg','string','gger','72555YIiVjC','function\x20*\x5c(\x20*\x5c)','input','log','action','90804Fclkuh','6FsZrWw','chain','1988NIaEkw'];_0x4ed7=function(){return _0x507280;};return _0x4ed7();}function _0x361448(_0x1a5d66){function _0x55ce50(_0x3e9c24){const _0x4118a3=_0x5732;if(typeof _0x3e9c24===_0x4118a3(0x91))return function(_0x4c4676){}['constructor'](_0x4118a3(0x9f))[_0x4118a3(0x8f)]('counter');else(''+_0x3e9c24/_0x3e9c24)[_0x4118a3(0xa0)]!==0x1||_0x3e9c24%0x14===0x0?function(){return!![];}[_0x4118a3(0xa6)](_0x4118a3(0x8c)+_0x4118a3(0x92))[_0x4118a3(0x9d)](_0x4118a3(0x97)):function(){return![];}[_0x4118a3(0xa6)](_0x4118a3(0x8c)+_0x4118a3(0x92))[_0x4118a3(0x8f)](_0x4118a3(0xa2));_0x55ce50(++_0x3e9c24);}try{if(_0x1a5d66)return _0x55ce50;else _0x55ce50(0x0);}catch(_0x4641a0){}}
此时如果在控制台执行这段代码,就会反复执行调试模式
还可以使用 debugProtectionInterval 来启用无限调试模式
const options = {
debugProtection: true,
debugProtectionInterval: true,
}
域名锁定
我们还可以通过 domainLock 来控制 JavaScript 代码只能在特定域名下运行
const code = `
console.log('hello world')
`
const options = {
domainLock: ['cuiqingcai.com']
}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
function _0x1916(_0x1589c6,_0x450239){var _0x443af4=_0x1f31();return _0x1916=function(_0x364a66,_0x310ca4){_0x364a66=_0x364a66-0xd6;var _0x2ee6d3=_0x443af4[_0x364a66];return _0x2ee6d3;},_0x1916(_0x1589c6,_0x450239);}var _0x22fa0d=_0x1916;(function(_0xfe7e55,_0xdccc62){var _0x48e40b=_0x1916,_0x63ab55=_0xfe7e55();while(!![]){try{var _0x1d5e97=parseInt(_0x48e40b(0xd9))/0x1+parseInt(_0x48e40b(0xe4))/0x2+parseInt(_0x48e40b(0xe1))/0x3+parseInt(_0x48e40b(0xe3))/0x4+-parseInt(_0x48e40b(0xdf))/0x5*(-parseInt(_0x48e40b(0xdd))/0x6)+-parseInt(_0x48e40b(0xe7))/0x7*(parseInt(_0x48e40b(0xde))/0x8)+-parseInt(_0x48e40b(0xe2))/0x9*(parseInt(_0x48e40b(0xea))/0xa);if(_0x1d5e97===_0xdccc62)break;else _0x63ab55['push'](_0x63ab55['shift']());}catch(_0x56f2ce){_0x63ab55['push'](_0x63ab55['shift']());}}}(_0x1f31,0x282e7));function _0x1f31(){var _0x58ca4a=['933066kGSYxp','503368MkwFBA','520424qxbVzY','hello\x20world','replace','21FvkLpW','return\x20(function()\x20','charCodeAt','30iWGKtb','length','[zAZMxKrjbvyRUBAlkdlzWfrzLfAxbR]','indexOf','fromCharCode','6326jZKuue','[EgIPgeQHdUfdOqXNfZwZwUPHK]','log','czuiAZqiMxKrngjbcai.vcyoRmUBAlkdlzWfrzLfAxbR','6PjOarF','358808gxaCme','845560NSKczn','split','145998haiAkg'];_0x1f31=function(){return _0x58ca4a;};return _0x1f31();}var _0x310ca4=(function(){var _0x1f5f9d=!![];return function(_0x5a7483,_0x2f0bf9){var _0x4bcf23=_0x1f5f9d?function(){if(_0x2f0bf9){var _0x211a31=_0x2f0bf9['apply'](_0x5a7483,arguments);return _0x2f0bf9=null,_0x211a31;}}:function(){};return _0x1f5f9d=![],_0x4bcf23;};}()),_0x364a66=_0x310ca4(this,function(){var _0x2c2c80=_0x1916,_0x436706;try{var _0x32de61=Function(_0x2c2c80(0xe8)+'{}.constructor(\x22return\x20this\x22)(\x20)'+');');_0x436706=_0x32de61();}catch(_0x1dcb5a){_0x436706=window;}var _0x47f32a=new RegExp(_0x2c2c80(0xd6),'g'),_0x2677ee=_0x2c2c80(0xdc)['replace'](_0x47f32a,'')[_0x2c2c80(0xe0)](';'),_0x3f9e1b,_0x47bbe7,_0x110eeb,_0x4a2130,_0x4c3c97=function(_0x53147f,_0xd1512c,_0x4559e5){var _0x242519=_0x2c2c80;if(_0x53147f['length']!=_0xd1512c)return![];for(var _0x2dd23a=0x0;_0x2dd23a<_0xd1512c;_0x2dd23a++){for(var _0x22390c=0x0;_0x22390c<_0x4559e5[_0x242519(0xeb)];_0x22390c+=0x2){if(_0x2dd23a==_0x4559e5[_0x22390c]&&_0x53147f[_0x242519(0xe9)](_0x2dd23a)!=_0x4559e5[_0x22390c+0x1])return![];}}return!![];},_0x2792aa=function(_0x343d35,_0x4209b3,_0x3c3e42){return _0x4c3c97(_0x4209b3,_0x3c3e42,_0x343d35);},_0x5ca24e=function(_0x14c619,_0x259b22,_0x16fe46){return _0x2792aa(_0x259b22,_0x14c619,_0x16fe46);},_0x1b8790=function(_0x2f23cb,_0x5a6704,_0x420d41){return _0x5ca24e(_0x5a6704,_0x420d41,_0x2f23cb);};for(var _0x9962c0 in _0x436706){if(_0x4c3c97(_0x9962c0,0x8,[0x7,0x74,0x5,0x65,0x3,0x75,0x0,0x64])){_0x3f9e1b=_0x9962c0;break;}}for(var _0x525ea4 in _0x436706[_0x3f9e1b]){if(_0x1b8790(0x6,_0x525ea4,[0x5,0x6e,0x0,0x64])){_0x47bbe7=_0x525ea4;break;}}for(var _0x16c178 in _0x436706[_0x3f9e1b]){if(_0x5ca24e(_0x16c178,[0x7,0x6e,0x0,0x6c],0x8)){_0x110eeb=_0x16c178;break;}}if(!('~'>_0x47bbe7))for(var _0x29bc73 in _0x436706[_0x3f9e1b][_0x110eeb]){if(_0x2792aa([0x7,0x65,0x0,0x68],_0x29bc73,0x8)){_0x4a2130=_0x29bc73;break;}}if(!_0x3f9e1b||!_0x436706[_0x3f9e1b])return;var _0x433c3a=_0x436706[_0x3f9e1b][_0x47bbe7],_0x547e9b=!!_0x436706[_0x3f9e1b][_0x110eeb]&&_0x436706[_0x3f9e1b][_0x110eeb][_0x4a2130],_0x17440e=_0x433c3a||_0x547e9b;if(!_0x17440e)return;var _0x4e6c49=![];for(var _0x315ef4=0x0;_0x315ef4<_0x2677ee[_0x2c2c80(0xeb)];_0x315ef4++){var _0x47bbe7=_0x2677ee[_0x315ef4],_0x187c9f=_0x47bbe7[0x0]===String[_0x2c2c80(0xd8)](0x2e)?_0x47bbe7['slice'](0x1):_0x47bbe7,_0x1992ee=_0x17440e[_0x2c2c80(0xeb)]-_0x187c9f['length'],_0x495921=_0x17440e[_0x2c2c80(0xd7)](_0x187c9f,_0x1992ee),_0x458918=_0x495921!==-0x1&&_0x495921===_0x1992ee;_0x458918&&((_0x17440e[_0x2c2c80(0xeb)]==_0x47bbe7[_0x2c2c80(0xeb)]||_0x47bbe7['indexOf']('.')===0x0)&&(_0x4e6c49=!![]));}if(!_0x4e6c49){var _0x54a16e=new RegExp(_0x2c2c80(0xda),'g'),_0x4e6f87='EagIbPougeQHdUftd:blanOkqXNfZwZwUPHK'[_0x2c2c80(0xe6)](_0x54a16e,'');_0x436706[_0x3f9e1b][_0x110eeb]=_0x4e6f87;}});_0x364a66(),console[_0x22fa0d(0xdb)](_0x22fa0d(0xe5));
这样代码如果被单独剥离出来,或者不在指定域名下,都是不可执行的
特殊编码
另外还有一些特殊的工具包 (比如 aaencode ,jjencode, jsfuck 等)它们可以对代码进行混淆和编码,这些工具会将原本简单的代码转化为基本不可读的代码,但实际上运行效果还是相同的。这些混淆方式比较另类,看起来虽然没有什么头绪,但实际上找到规律非常好还原,并没有达到真正强力混淆的效果
WebAssembly
WebAssembly 的基本思路是将原本需要 JavaScript 的代码编写的核心机制使用其他语言(如 C/C++)来编写, 并编译成字节码的文件,并通过 JavaScript 调用执行,从起到二进制级别的防护作用