引言
网站的逻辑是通过javascript来实现的,但因为javascript可通过浏览器直接获取源码,通常情况下,网站所有者为保证web安全性,会对网站中的javascript文件经过处理,以防数据泄露和黑客攻击。
- 代码压缩:去除代码中的空格、制表符、换行符等内容,将代码压缩至几行内容,降低代码可读性,同时也提高了网站加载速度,但是可通过一些代码格式化工具提高代码可读性。
- 代码混淆:代码混淆是将代码转换成一种功能上等价,但是难于阅读和理解的形式。代码混淆可通过将代码中的各种元素改写成无意义的名字;重写代码中的部分逻辑,将其变成功能上等价,但是更难理解的形式;打乱代码的格式等方式混淆代码。但是代码混淆并不能真正阻止反向工程,只能增大其难度,因此,对于对安全性要求很高的场合,仅仅使用代码混淆并不能保证代码的安全性。
JavaScript Obfuscator
JavaScript Obfuscator是一个免费且高效的JavaScript混淆器,可将原始的JavaScript代码转换为新的表示形式,该表示形式将更难理解、复制、重用和修改,混淆后的代码将具有原始代码的功能。
使用Node对JavaScript进行压缩与混淆的环境搭建
-
安装好Node.js环境
-
安装JavaScript Obfuscator库
npm install --sace-dev javascript-obfuscator
- 新建文件夹,使用
npm init初始化工作空间 - 新建js文件,使用
require引入JavaScript Obfuscator,定义一个方法用来返回处理后的代码,code为原始代码,options为传入的JavaScript Obfuscator库选项值,最后使用控制台输出处理后的代码
const code = ``
const options = {}
const obfuscator = require('javascript-obfuscator')
function obfuscate(code, options) {
return obfuscator.obfuscate(code, options).getObfuscatedCode()
}
console.log(obfuscate(code, options))
Javascript压缩
JavaScript Obfuscator提供了参数compact来完成对代码的压缩,默认为true
const code = `
let x = 'JavaScript Obfuscator'
console.log(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 _0x2381=['JavaScript\x20Obfuscator'];(function(_0x395104,_0x23815b){const _0x1d3967=function(_0xceb63e){while(--_0xceb63e){_0x395104['push'](_0x395104['shift']());}};_0x1d3967(++_0x23815b);}(_0x2381,0x154));const _0x1d39=function(_0x395104,_0x23815b){_0x395104=_0x395104-0x0;let _0x1d3967=_0x2381[_0x395104];return _0x1d3967;};let x=_0x1d39('0x0');console['log'](x);
Javascript混淆
- 域名锁定
通过设置domainLock参数为相关的域名来让代码只能在特定的域名下运行
const options = {
domainLock: ['https://www.baidu.com/']
}
- 调试保护
通过设置debugProtection来禁用调试模式,进入无限Debug模式,使代码在调试过程中不断进入断点模式,无法顺畅运行
const options = {
debugProtection: true
}
另外可通过设置debugProtectionInterval来指定Debug的间隔
- 禁用控制台输出
通过设置diableConsoleOutput参数可禁用console.log输出功能,加大调试难度
const options = {
diableConsoleOutput: true
}
- 代码自我保护
通过设置selfDefending参数可开启代码的自我保护功能,混淆后的代码强制为一行显示,并且格式化和重命名将无法执行
const options = {
selfDefending: true
}
输出结果如下:
const _0x3d23=['log','JavaScript\x20Obfuscator','compile','^([^\x20]+(\x20+[^\x20]+)+)+[^\x20]}','test','constructor','return\x20/\x22\x20+\x20this\x20+\x20\x22/'];(function(_0x1c63ad,_0x3d23a7){const _0x206b13=function(_0x38a220){while(--_0x38a220){_0x1c63ad['push'](_0x1c63ad['shift']());}};const _0xbcdb3d=function(){const _0x2cb417={'data':{'key':'cookie','value':'timeout'},'setCookie':function(_0x1e6bac,_0x233f4c,_0x7f906c,_0x9eb8af){_0x9eb8af=_0x9eb8af||{};let _0x7bddbf=_0x233f4c+'='+_0x7f906c;let _0x3ffd79=0x0;for(let _0x47ef4f=0x0,_0xab38c2=_0x1e6bac['length'];_0x47ef4f<_0xab38c2;_0x47ef4f++){const _0x1d36e2=_0x1e6bac[_0x47ef4f];_0x7bddbf+=';\x20'+_0x1d36e2;const _0x790442=_0x1e6bac[_0x1d36e2];_0x1e6bac['push'](_0x790442);_0xab38c2=_0x1e6bac['length'];if(_0x790442!==!![]){_0x7bddbf+='='+_0x790442;}}_0x9eb8af['cookie']=_0x7bddbf;},'removeCookie':function(){return'dev';},'getCookie':function(_0x5844b1,_0x473d52){_0x5844b1=_0x5844b1||function(_0x1d62ab){return _0x1d62ab;};const _0x491e1c=_0x5844b1(new RegExp('(?:^|;\x20)'+_0x473d52['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));const _0x51f804=function(_0x153efb,_0x31e24c){_0x153efb(++_0x31e24c);};_0x51f804(_0x206b13,_0x3d23a7);return _0x491e1c?decodeURIComponent(_0x491e1c[0x1]):undefined;}};const _0x5c80f0=function(){const _0x1e4668=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return _0x1e4668['test'](_0x2cb417['removeCookie']['toString']());};_0x2cb417['updateCookie']=_0x5c80f0;let _0x3e6a1e='';const _0x138000=_0x2cb417['updateCookie']();if(!_0x138000){_0x2cb417['setCookie'](['*'],'counter',0x1);}else if(_0x138000){_0x3e6a1e=_0x2cb417['getCookie'](null,'counter');}else{_0x2cb417['removeCookie']();}};_0xbcdb3d();}(_0x3d23,0xdb));const _0x206b=function(_0x1c63ad,_0x3d23a7){_0x1c63ad=_0x1c63ad-0x0;let _0x206b13=_0x3d23[_0x1c63ad];return _0x206b13;};const _0x2cb417=function(){let _0x3ffd79=!![];return function(_0x47ef4f,_0xab38c2){const _0x1d36e2=_0x3ffd79?function(){if(_0xab38c2){const _0x790442=_0xab38c2['apply'](_0x47ef4f,arguments);_0xab38c2=null;return _0x790442;}}:function(){};_0x3ffd79=![];return _0x1d36e2;};}();const _0x38a220=_0x2cb417(this,function(){const _0x5844b1=function(){const _0x473d52=_0x5844b1[_0x206b('0x3')](_0x206b('0x4'))()[_0x206b('0x0')](_0x206b('0x1'));return!_0x473d52[_0x206b('0x2')](_0x38a220);};return _0x5844b1();});_0x38a220();let x=_0x206b('0x6');console[_0x206b('0x5')](x);
如果将结果进行格式化,会变成如下内容:
const _0x3d23 = ['log', 'JavaScript\x20Obfuscator', 'compile', '^([^\x20]+(\x20+[^\x20]+)+)+[^\x20]}', 'test', 'constructor', 'return\x20/\x22\x20+\x20this\x20+\x20\x22/']; (function(_0x1c63ad, _0x3d23a7) {
const _0x206b13 = function(_0x38a220) {
while (--_0x38a220) {
_0x1c63ad['push'](_0x1c63ad['shift']());
}
};
const _0xbcdb3d = function() {
const _0x2cb417 = {
'data': {
'key': 'cookie',
'value': 'timeout'
},
'setCookie': function(_0x1e6bac, _0x233f4c, _0x7f906c, _0x9eb8af) {
_0x9eb8af = _0x9eb8af || {};
let _0x7bddbf = _0x233f4c + '=' + _0x7f906c;
let _0x3ffd79 = 0x0;
for (let _0x47ef4f = 0x0, _0xab38c2 = _0x1e6bac['length']; _0x47ef4f < _0xab38c2; _0x47ef4f++) {
const _0x1d36e2 = _0x1e6bac[_0x47ef4f];
_0x7bddbf += ';\x20' + _0x1d36e2;
const _0x790442 = _0x1e6bac[_0x1d36e2];
_0x1e6bac['push'](_0x790442);
_0xab38c2 = _0x1e6bac['length'];
if (_0x790442 !== !![]) {
_0x7bddbf += '=' + _0x790442;
}
}
_0x9eb8af['cookie'] = _0x7bddbf;
},
'removeCookie': function() {
return 'dev';
},
'getCookie': function(_0x5844b1, _0x473d52) {
_0x5844b1 = _0x5844b1 ||
function(_0x1d62ab) {
return _0x1d62ab;
};
const _0x491e1c = _0x5844b1(new RegExp('(?:^|;\x20)' + _0x473d52['replace'](/([.$?*|{}()[]\/+^])/g, '$1') + '=([^;]*)'));
const _0x51f804 = function(_0x153efb, _0x31e24c) {
_0x153efb(++_0x31e24c);
};
_0x51f804(_0x206b13, _0x3d23a7);
return _0x491e1c ? decodeURIComponent(_0x491e1c[0x1]) : undefined;
}
};
const _0x5c80f0 = function() {
const _0x1e4668 = new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');
return _0x1e4668['test'](_0x2cb417['removeCookie']['toString']());
};
_0x2cb417['updateCookie'] = _0x5c80f0;
let _0x3e6a1e = '';
const _0x138000 = _0x2cb417['updateCookie']();
if (!_0x138000) {
_0x2cb417['setCookie'](['*'], 'counter', 0x1);
} else if (_0x138000) {
_0x3e6a1e = _0x2cb417['getCookie'](null, 'counter');
} else {
_0x2cb417['removeCookie']();
}
};
_0xbcdb3d();
} (_0x3d23, 0xdb));
const _0x206b = function(_0x1c63ad, _0x3d23a7) {
_0x1c63ad = _0x1c63ad - 0x0;
let _0x206b13 = _0x3d23[_0x1c63ad];
return _0x206b13;
};
const _0x2cb417 = function() {
let _0x3ffd79 = !![];
return function(_0x47ef4f, _0xab38c2) {
const _0x1d36e2 = _0x3ffd79 ?
function() {
if (_0xab38c2) {
const _0x790442 = _0xab38c2['apply'](_0x47ef4f, arguments);
_0xab38c2 = null;
return _0x790442;
}
}: function() {};
_0x3ffd79 = ![];
return _0x1d36e2;
};
} ();
const _0x38a220 = _0x2cb417(this,
function() {
const _0x5844b1 = function() {
const _0x473d52 = _0x5844b1[_0x206b('0x3')](_0x206b('0x4'))()[_0x206b('0x0')](_0x206b('0x1'));
return ! _0x473d52[_0x206b('0x2')](_0x38a220);
};
return _0x5844b1();
});
_0x38a220();
let x = _0x206b('0x6');
console[_0x206b('0x5')](x);
如果将其放到浏览器里,浏览器将无法运行,也无法调试,从而起到了自我保护的作用
- 字符串混淆
字符串混淆是指将一个字符串声明放到一个数组里,使之无法被搜索到,可通过设置stringArray参数来控制。还可通过设置rotateStringArray参数来控制字符串数组化后的元素顺序,默认为true,通过设置stringArrayEncoding参数来设置数组的编码形式,默认不开启编码,如果设置为true,默认使用base64编码,还可设置为RC4编码,stringArrayThreshold可控制编码的范围(0~1),默认为0.8。
如不进行字符串混淆
const options = {
stringArray: false
}
原字符串为:
let x='JavaScript\x20Obfuscator';console['log'](x);
如果进行字符串混淆:
const options = {
stringArray: true,
rotateStringArray: true,
stringArrayEncoding: true,
stringArrayThreshold: 1
}
输出结果为:
const _0x17c8=['bG9n','SmF2YVNjcmlwdCBPYmZ1c2NhdG9y'];(function(_0x47d261,_0x17c874){const _0xa93bc5=function(_0x2afddb){while(--_0x2afddb){_0x47d261['push'](_0x47d261['shift']());}};_0xa93bc5(++_0x17c874);}(_0x17c8,0x191));const _0xa93b=function(_0x47d261,_0x17c874){_0x47d261=_0x47d261-0x0;let _0xa93bc5=_0x17c8[_0x47d261];if(_0xa93b['tzeCDL']===undefined){(function(){const _0x53868a=function(){let _0x4d371d;try{_0x4d371d=Function('return\x20(function()\x20'+'{}.constructor(\x22return\x20this\x22)(\x20)'+');')();}catch(_0x230efd){_0x4d371d=window;}return _0x4d371d;};const _0x5b0ee0=_0x53868a();const _0x15514a='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';_0x5b0ee0['atob']||(_0x5b0ee0['atob']=function(_0x2f7e17){const _0x89e3ab=String(_0x2f7e17)['replace'](/=+$/,'');let _0x34b50b='';for(let _0x3636d9=0x0,_0x454555,_0x565c62,_0x22e527=0x0;_0x565c62=_0x89e3ab['charAt'](_0x22e527++);~_0x565c62&&(_0x454555=_0x3636d9%0x4?_0x454555*0x40+_0x565c62:_0x565c62,_0x3636d9++%0x4)?_0x34b50b+=String['fromCharCode'](0xff&_0x454555>>(-0x2*_0x3636d9&0x6)):0x0){_0x565c62=_0x15514a['indexOf'](_0x565c62);}return _0x34b50b;});}());_0xa93b['VtyEej']=function(_0x2d9dd8){const _0x372ed4=atob(_0x2d9dd8);let _0x91b44a=[];for(let _0x10ebae=0x0,_0x6b690e=_0x372ed4['length'];_0x10ebae<_0x6b690e;_0x10ebae++){_0x91b44a+='%'+('00'+_0x372ed4['charCodeAt'](_0x10ebae)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x91b44a);};_0xa93b['ddUCUb']={};_0xa93b['tzeCDL']=!![];}const _0x2afddb=_0xa93b['ddUCUb'][_0x47d261];if(_0x2afddb===undefined){_0xa93bc5=_0xa93b['VtyEej'](_0xa93bc5);_0xa93b['ddUCUb'][_0x47d261]=_0xa93bc5;}else{_0xa93bc5=_0x2afddb;}return _0xa93bc5;};let x=_0xa93b('0x0');console[_0xa93b('0x1')](x);
可见字符串进行了Base64编码,无法通过查找的方式查找到
另外可通过设置unicodeEscapeSequence参数来对字符串进行Unicode编码,使之更难辨认
const options = {
compact: false,
unicodeEscapeSequence: true
}
输出结果为:
const _0x2fdc = ['\x6c\x6f\x67'];
(function (_0x1a1c60, _0x2fdca5) {
const _0x42a72d = function (_0x4e9a85) {
while (--_0x4e9a85) {
_0x1a1c60['push'](_0x1a1c60['shift']());
}
};
_0x42a72d(++_0x2fdca5);
}(_0x2fdc, 0xd1));
const _0x42a7 = function (_0x1a1c60, _0x2fdca5) {
_0x1a1c60 = _0x1a1c60 - 0x0;
let _0x42a72d = _0x2fdc[_0x1a1c60];
return _0x42a72d;
};
let x = '\x4a\x61\x76\x61\x53\x63\x72\x69\x70\x74\x20\x4f\x62\x66\x75\x73\x63\x61\x74\x6f\x72';
console[_0x42a7('\x30\x78\x30')](x);
- 变量名混淆
JavaScript Obfuscator提供了参数identifierNamesGenerator来完成对代码的变量名混淆,identifierNamesGenerator有三个参数,分别为mangled(简写字符,如a、b、c、d等)和默认的hexadecimal(十六进制),以mangled为例
const options = {
compact: false,
identifierNamesGenerator: 'mangled'
}
输出结果如下,可见变量名都变为了'b'、'e'等形式
const a = [
'log',
'JavaScript\x20Obfuscator'
];
(function (b, e) {
const f = function (g) {
while (--g) {
b['push'](b['shift']());
}
};
f(++e);
}(a, 0x6d));
const b = function (c, d) {
c = c - 0x0;
let e = a[c];
return e;
};
let x = b('0x0');
console[b('0x1')](x);
还可用identifiersPrefix参数为变量名加上前缀
const options = {
compact: false,
identifiersPrefix: 'Obfuscator'
}
输出结果如下,可见所有变量之前都加上了字符串'Obfuscator'
const Obfuscator_0x445b = ['log'];
(function (_0x24701e, _0x445b58) {
const _0x4506e2 = function (_0x5207fb) {
while (--_0x5207fb) {
_0x24701e['push'](_0x24701e['shift']());
}
};
_0x4506e2(++_0x445b58);
}(Obfuscator_0x445b, 0xfd));
const Obfuscator_0x4506 = function (_0x24701e, _0x445b58) {
_0x24701e = _0x24701e - 0x0;
let _0x4506e2 = Obfuscator_0x445b[_0x24701e];
return _0x4506e2;
};
let x = 'JavaScript\x20Obfuscator';
console[Obfuscator_0x4506('0x0')](x);
另外可通过设置renameGlobals参数来混淆全局变量和函数名称
const code = `
let add = function(a, b) {
return a + b
}
`
const options = {
compact: false,
renameGlobals: true
}
输出结果如下,可见函数名add被替换,如果后面要用到add函数,就会出现找不到定义的错误
let _0x561d32 = function (_0x40eef8, _0x3d7bba) {
return _0x40eef8 + _0x3d7bba;
};
如过我们不设置renameGlobals参数,或者设置其为false,则函数名和全局变量不会被替换
let add = function (_0x33194d, _0x13078b) {
return _0x33194d + _0x13078b;
};
- 控制流平坦化
控制流平坦化是指混淆代码的执行逻辑,使其变得复杂难懂。原理是将一些逻辑处理块都统一加上一个前驱逻辑块,每个逻辑块由前驱逻辑块进行条件判断和分发,构成闭环逻辑,使整个代码执行逻辑复杂化。可通过设置controlFlowFlattening参数启用该功能,但开启该功能后代码的执行时间将变长。
如果不设置controlFlowFlattening
const options = {
compact: false
}
输出结果为:
var _0x212c = ['log'];
(function (_0x371da5, _0x212c64) {
var _0x52e022 = function (_0x391324) {
while (--_0x391324) {
_0x371da5['push'](_0x371da5['shift']());
}
};
_0x52e022(++_0x212c64);
}(_0x212c, 0x14b));
var _0x52e0 = function (_0x371da5, _0x212c64) {
_0x371da5 = _0x371da5 - 0x0;
var _0x52e022 = _0x212c[_0x371da5];
return _0x52e022;
};
(function () {
function _0x567b71() {
return function () {
var _0x5c194e = 0x1 + 0x2;
console[_0x52e0('0x0')](0x1);
console[_0x52e0('0x0')](0x2);
console[_0x52e0('0x0')](0x3);
console['log'](0x4);
console[_0x52e0('0x0')](0x5);
console[_0x52e0('0x0')](0x6);
};
}
_0x567b71()();
}());
如果设置controlFlowFlattening
const options = {
compact: false,
controlFlowFlattening: true
}
输出结果为:
var _0x4688 = [
'HXCUG',
'RIkMw',
'KpXTT',
'log',
'split'
];
(function (_0x2ed73d, _0x4688b7) {
var _0x341376 = function (_0x6b95ff) {
while (--_0x6b95ff) {
_0x2ed73d['push'](_0x2ed73d['shift']());
}
};
_0x341376(++_0x4688b7);
}(_0x4688, 0x128));
var _0x3413 = function (_0x2ed73d, _0x4688b7) {
_0x2ed73d = _0x2ed73d - 0x0;
var _0x341376 = _0x4688[_0x2ed73d];
return _0x341376;
};
(function () {
var _0x19ffb0 = {
'KpXTT': '2|5|6|4|3|0|1',
'HXCUG': function (_0x35a6fe, _0x387809) {
return _0x35a6fe + _0x387809;
},
'RIkMw': function (_0x5ed2bc) {
return _0x5ed2bc();
}
};
function _0x2fff4b() {
return function () {
var _0x54c687 = _0x19ffb0[_0x3413('0x1')][_0x3413('0x3')]('|');
var _0x4320fa = 0x0;
while (!![]) {
switch (_0x54c687[_0x4320fa++]) {
case '0':
console[_0x3413('0x2')](0x5);
continue;
case '1':
console[_0x3413('0x2')](0x6);
continue;
case '2':
var _0x22e948 = _0x19ffb0[_0x3413('0x4')](0x1, 0x2);
continue;
case '3':
console[_0x3413('0x2')](0x4);
continue;
case '4':
console[_0x3413('0x2')](0x3);
continue;
case '5':
console[_0x3413('0x2')](0x1);
continue;
case '6':
console['log'](0x2);
continue;
}
break;
}
};
}
_0x19ffb0[_0x3413('0x0')](_0x2fff4b)();
}());
可以看到一些连续的逻辑被被修改为一个switch语句
另外可通过设置controlFlowFlatteningThreshold参数来控制平坦化的比例,范围为0~1,默认0.75
- 僵尸代码注入
僵尸代码为不会执行的代码或对上下文没有影响的代码,可通过在原始代码中加入该代码来降低代码的可读性。设置deadCodeInjection参数可开启该功能,且可通过设置deadCodeInjectionThreshold参数来控制注入比例,范围为0~1,默认0.4
const options = {
deadCodeInjection: true
}
- 对象名替换
通过设置transformObjectKeys参数可将对象的键值进行替换
const options = {
compact: false,
transformObjectKeys: true
}
输出结果为
const _0x14bc = [
'woman',
'sam',
'sex',
'name'
];
(function (_0x39c5c1, _0x14bcd9) {
const _0x5bd8a9 = function (_0x4abe71) {
while (--_0x4abe71) {
_0x39c5c1['push'](_0x39c5c1['shift']());
}
};
_0x5bd8a9(++_0x14bcd9);
}(_0x14bc, 0x156));
const _0x5bd8 = function (_0x39c5c1, _0x14bcd9) {
_0x39c5c1 = _0x39c5c1 - 0x0;
let _0x5bd8a9 = _0x14bc[_0x39c5c1];
return _0x5bd8a9;
};
const _0x4abe71 = {};
_0x4abe71[_0x5bd8('0x1')] = _0x5bd8('0x3');
_0x4abe71[_0x5bd8('0x0')] = _0x5bd8('0x2');
_0x4abe71['id'] = 0x5be3;
let object = _0x4abe71;
此时object的变量名已被替换
END
以上介绍了在node环境下通过JavaScript Obfuscator库来对JavaScript代码进行压缩和混淆的技术。