代码混淆与加密
为什么加密或混淆
- JavaScript 代码运行于客户端
- JavaScript 代码是公开透明的
如何对 JavaScript 进行保护
- 代码压缩:去除空格、换行等
- 代码加密:eval、emscripten、WebAssembly 等
- 代码混淆:变量混淆、常量混淆、控制流扁平化、调试保护等
JavaScript 加密实现
eval 加密
利用 eval 函数将 JavaScrpt 代码变成参数, eval 方法就是 JavaScript 代码的一个执行器,它可以把其中的参数按照 JavaScript 语法进行解析并执行。
这种加密方式其实就是把 JavaScript 代码变成 eval 方法的字符串参数,其中的一些字符都会被按照特定的格式编码
基于 javascript-obfuscator 演示、使用多种混淆技术、依赖 Node.js。
npm i javascript-obfuscator --save-dev
基础案例
// demo01.js
const obfuscator = require('javascript-obfuscator')
const code = `
const x = 'l' + 1
console.log('x', x)
`
const options = {
compact: false, // 是否压缩成一行
controlFlowFlattening: true // 控制流平坦化
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// const _0x319a8e = _0x17c8;
// (function (_0x52f8e7, _0x57164d) {
// const _0x46023b = _0x17c8, _0x430115 = _0x52f8e7();
// while (!![]) {
// try {
// const _0x139205 = parseInt(_0x46023b(0x1c2)) / 0x1 + -parseInt(_0x46023b(0x1c0)) / 0x2 + -parseInt(_0x46023b(0x1c4)) / 0x3 + parseInt(_0x46023b(0x1be)) / 0x4 * (-parseInt(_0x46023b(0x1bc)) / 0x5) + -parseInt(_0x46023b(0x1ba)) / 0x6 + -parseInt(_0x46023b(0x1bb)) / 0x7 * (-parseInt(_0x46023b(0x1bd)) / 0x8) + -parseInt(_0x46023b(0x1c1)) / 0x9 * (-parseInt(_0x46023b(0x1c3)) / 0xa);
// if (_0x139205 === _0x57164d)
// break;
// else
// heora@yueluodeMBP obfuscator % node demo01.js
// function _0x37c2(_0x15e667, _0x179462) {
// const _0x3fc284 = _0x3fc2();
// return _0x37c2 = function (_0x37c288, _0x365673) {
// _0x37c288 = _0x37c288 - 0x149;
// let _0x3bb96b = _0x3fc284[_0x37c288];
// return _0x3bb96b;
// }, _0x37c2(_0x15e667, _0x179462);
// }
// const _0x41fee7 = _0x37c2;
// function _0x3fc2() {
// const _0xff6162 = [
// '2180445LYHBNw',
// '6PoXlVC',
// '1062245HPVdof',
// '4962699JsvSZR',
// '2037424UwkIQW',
// '2wCqcbN',
// '22550990HHAjbJ',
// 'log',
// '181064EFBbAW',
// '744311jBDKbx',
// '153fCiFKW'
// ];
// _0x3fc2 = function () {
// return _0xff6162;
// };
// return _0x3fc2();
// }
// (function (_0x44b360, _0x36241e) {
// const _0x352cf1 = _0x37c2, _0x59526 = _0x44b360();
// while (!![]) {
// try {
// const _0x546e33 = -parseInt(_0x352cf1(0x153)) / 0x1 + parseInt(_0x352cf1(0x14f)) / 0x2 * (parseInt(_0x352cf1(0x14a)) / 0x3) + -parseInt(_0x352cf1(0x14e)) / 0x4 + -parseInt(_0x352cf1(0x14c)) / 0x5 * (parseInt(_0x352cf1(0x14b)) / 0x6) + -parseInt(_0x352cf1(0x14d)) / 0x7 + parseInt(_0x352cf1(0x152)) / 0x8 * (-parseInt(_0x352cf1(0x149)) / 0x9) + parseInt(_0x352cf1(0x150)) / 0xa;
// if (_0x546e33 === _0x36241e)
// break;
// else
// _0x59526['push'](_0x59526['shift']());
// } catch (_0x479a3c) {
// _0x59526['push'](_0x59526['shift']());
// }
// }
// }(_0x3fc2, 0x670c0));
// const x = 'l' + 0x1;
// console[_0x41fee7(0x151)]('x', x);
代码压缩
启用 compact 配置可以进行代码压缩,将多行代码压缩为一行。
const obfuscator = require('javascript-obfuscator')
const code = `
const x = 'l' + 1
console.log('x', x)
`
const options = {
compact: true, // 是否压缩成一行
controlFlowFlattening: true // 控制流平坦化
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// function _0x1ed0(){const _0x11ff60=['1696072pCxUdw','9081JMWsXK','58LluwlY','log','2552oFOSUn','1122qjQuAv','25537SpbjXH','711552eXkfAf','4202505CboVKg','8669610XfCMvT','29701fxdBiQ'];_0x1ed0=function(){return _0x11ff60;};return _0x1ed0();}const _0x34263e=_0x2ad4;(function(_0x51afa7,_0x14bd8d){const _0x11aac9=_0x2ad4,_0x4255eb=_0x51afa7();while(!![]){try{const _0x2f7cdd=parseInt(_0x11aac9(0x1a4))/0x1*(parseInt(_0x11aac9(0x1a0))/0x2)+-parseInt(_0x11aac9(0x19a))/0x3+-parseInt(_0x11aac9(0x19e))/0x4+-parseInt(_0x11aac9(0x19b))/0x5+parseInt(_0x11aac9(0x1a3))/0x6*(parseInt(_0x11aac9(0x19d))/0x7)+parseInt(_0x11aac9(0x1a2))/0x8*(-parseInt(_0x11aac9(0x19f))/0x9)+parseInt(_0x11aac9(0x19c))/0xa;if(_0x2f7cdd===_0x14bd8d)break;else _0x4255eb['push'](_0x4255eb['shift']());}catch(_0x12ce85){_0x4255eb['push'](_0x4255eb['shift']());}}}(_0x1ed0,0x8cf79));const x='l'+0x1;function _0x2ad4(_0x3170d9,_0x25c3ba){const _0x1ed02b=_0x1ed0();return _0x2ad4=function(_0x2ad40e,_0x2457e3){_0x2ad40e=_0x2ad40e-0x19a;let _0xf8cd45=_0x1ed02b[_0x2ad40e];return _0xf8cd45;},_0x2ad4(_0x3170d9,_0x25c3ba);}console[_0x34263e(0x1a1)]('x',x);
变量名混淆
controlFlowFlattening 配置为 true,使用的是 16 进制混淆。我们还可以使用 identifierNameGenerator 属性, 设置其值为 mangled,即普通混淆。
const obfuscator = require('javascript-obfuscator')
const code = `
const x = 'l' + 1
console.log('x', x)
`
const options = {
compact: true, // 是否压缩成一行
// controlFlowFlattening: true // 控制流平坦化
identifierNamesGenerator: 'mangled'
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// function a(){const i=['1402511atPvUm','226fjmYht','1084160Vfcblk','6748XotdGE','6ldcOls','374521DOItpE','587862aDEfQq','56gRDPSI','307471KdMVmp','5862OyJEBp','215otlhqL'];a=function(){return i;};return a();}(function(c,d){const h=b,e=c();while(!![]){try{const f=-parseInt(h(0x184))/0x1+parseInt(h(0x188))/0x2*(parseInt(h(0x185))/0x3)+parseInt(h(0x17f))/0x4*(parseInt(h(0x186))/0x5)+-parseInt(h(0x180))/0x6*(-parseInt(h(0x181))/0x7)+parseInt(h(0x183))/0x8*(parseInt(h(0x182))/0x9)+-parseInt(h(0x189))/0xa+-parseInt(h(0x187))/0xb;if(f===d)break;else e['push'](e['shift']());}catch(g){e['push'](e['shift']());}}}(a,0x3fa4c));function b(c,d){const e=a();return b=function(f,g){f=f-0x17f;let h=e[f];return h;},b(c,d);}const x='l'+0x1;console['log']('x',x);
字符串混淆
const options = {
stringArray: true,
rotateStringArray: true,
stringArrayEncoding: ['base64'], // none, base64, rc4
stringArrayThreshold: 1
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// const _0x58c2e4=_0x30f4;(function(_0x3dd5c3,_0x43cf03){const _0x50015b=_0x30f4,_0x175929=_0x3dd5c3();while(!![]){try{const _0x179e3e=parseInt(_0x50015b(0x13b))/0x1+-parseInt(_0x50015b(0x13d))/0x2+parseInt(_0x50015b(0x13e))/0x3+-parseInt(_0x50015b(0x13a))/0x4*(parseInt(_0x50015b(0x13f))/0x5)+-parseInt(_0x50015b(0x143))/0x6+-parseInt(_0x50015b(0x141))/0x7+-parseInt(_0x50015b(0x140))/0x8*(-parseInt(_0x50015b(0x13c))/0x9);if(_0x179e3e===_0x43cf03)break;else _0x175929['push'](_0x175929['shift']());}catch(_0x541ffd){_0x175929['push'](_0x175929['shift']());}}}(_0x1ccc,0xe0350));function _0x30f4(_0x4eb79e,_0x329d01){const _0x1ccc2c=_0x1ccc();return _0x30f4=function(_0x30f48d,_0x5a5d5e){_0x30f48d=_0x30f48d-0x13a;let _0x2d532d=_0x1ccc2c[_0x30f48d];if(_0x30f4['xjhYfs']===undefined){var _0x41f5fe=function(_0x56b76d){const _0x53a9ee='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x1528e0='',_0x57a8e8='';for(let _0x170a79=0x0,_0xdbd185,_0x647290,_0x1847b2=0x0;_0x647290=_0x56b76d['charAt'](_0x1847b2++);~_0x647290&&(_0xdbd185=_0x170a79%0x4?_0xdbd185*0x40+_0x647290:_0x647290,_0x170a79++%0x4)?_0x1528e0+=String['fromCharCode'](0xff&_0xdbd185>>(-0x2*_0x170a79&0x6)):0x0){_0x647290=_0x53a9ee['indexOf'](_0x647290);}for(let _0x7c52f0=0x0,_0x13a433=_0x1528e0['length'];_0x7c52f0<_0x13a433;_0x7c52f0++){_0x57a8e8+='%'+('00'+_0x1528e0['charCodeAt'](_0x7c52f0)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x57a8e8);};_0x30f4['BApANM']=_0x41f5fe,_0x4eb79e=arguments,_0x30f4['xjhYfs']=!![];}const _0x37ec4a=_0x1ccc2c[0x0],_0x52ca06=_0x30f48d+_0x37ec4a,_0x44eed0=_0x4eb79e[_0x52ca06];return!_0x44eed0?(_0x2d532d=_0x30f4['BApANM'](_0x2d532d),_0x4eb79e[_0x52ca06]=_0x2d532d):_0x2d532d=_0x44eed0,_0x2d532d;},_0x30f4(_0x4eb79e,_0x329d01);}function _0x1ccc(){const _0x1e5d0c=['mZzTuuDct0C','mtm4ntG1m1vnshvUAa','mJu4mdq4mdLSuwnuAvq','mJa0mZKWnfbqtgvWBa','ndyXotm2n0jjuNzfCa','nti3ndKWBgX6yxr5','ofHbvfvTua','mteXnJy2mtb6yw9mBhG','Bg9N','nZG0nJK2mLfeu0zSsG'];_0x1ccc=function(){return _0x1e5d0c;};return _0x1ccc();}const x='l'+0x1;console[_0x58c2e4(0x142)]('x',x);
const options = {
compact: false,
unicodeEscaoeSequence: true
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// function _0x26ba(_0x36c780, _0x53d9ad) {
// const _0x395a7c = _0x395a();
// return _0x26ba = function (_0x26ba1b, _0x50e0f8) {
// _0x26ba1b = _0x26ba1b - 0xf6;
// let _0x673a36 = _0x395a7c[_0x26ba1b];
// return _0x673a36;
// }, _0x26ba(_0x36c780, _0x53d9ad);
// }
// const _0x4a8483 = _0x26ba;
// (function (_0x4f6148, _0x62c41e) {
// const _0x12fd5b = _0x26ba, _0x1cdb96 = _0x4f6148();
// while (!![]) {
// try {
// const _0x425692 = -parseInt(_0x12fd5b(0xff)) / 0x1 * (-parseInt(_0x12fd5b(0xfc)) / 0x2) + -parseInt(_0x12fd5b(0xf7)) / 0x3 + parseInt(_0x12fd5b(0xfb)) / 0x4 * (-parseInt(_0x12fd5b(0xfd)) / 0x5) + -parseInt(_0x12fd5b(0xfe)) / 0x6 * (-parseInt(_0x12fd5b(0xf9)) / 0x7) + -parseInt(_0x12fd5b(0xf6)) / 0x8 + -parseInt(_0x12fd5b(0xfa)) / 0x9 + parseInt(_0x12fd5b(0xf8)) / 0xa;
// if (_0x425692 === _0x62c41e)
// break;
// else
// _0x1cdb96['push'](_0x1cdb96['shift']());
// } catch (_0x52d520) {
// _0x1cdb96['push'](_0x1cdb96['shift']());
// }
// }
// }(_0x395a, 0xe5bec));
// function _0x395a() {
// const _0x396de0 = [
// '11wNEArv',
// 'log',
// '3704680AnmDYz',
// '4713714utvsgx',
// '34268930dvIjTj',
// '53137GzFDpL',
// '8750070qAxqEg',
// '260292YHShGR',
// '138466fDpVBL',
// '85BHgFkg',
// '684wMjWNk'
// ];
// _0x395a = function () {
// return _0x396de0;
// };
// return _0x395a();
// }
// const x = 'l' + 0x1;
// console[_0x4a8483(0x100)]('x', x);
自我保护
代码混淆后,如果格式化运行,会将浏览器直接卡死。
原理:申请一些空间、新建一些对象、无限的新建一些 DOM 节点,或者有一些其他操作,它会疯狂占用浏览器和本机内存,将浏览器直接卡死,无法运行。
const options = {
selfDefending: true
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// function _0x6a5a(_0x57ebb1,_0x288f15){const _0x57a170=_0x3833();return _0x6a5a=function(_0x2482c9,_0x25f644){_0x2482c9=_0x2482c9-0x1ef;let _0x3833aa=_0x57a170[_0x2482c9];return _0x3833aa;},_0x6a5a(_0x57ebb1,_0x288f15);}const _0x252268=_0x6a5a;(function(_0x385891,_0x470326){const _0x193ce5=_0x6a5a,_0x3015bf=_0x385891();while(!![]){try{const _0x49f914=-parseInt(_0x193ce5(0x1fb))/0x1+-parseInt(_0x193ce5(0x1fc))/0x2*(parseInt(_0x193ce5(0x1ef))/0x3)+parseInt(_0x193ce5(0x1f3))/0x4+parseInt(_0x193ce5(0x1f2))/0x5*(parseInt(_0x193ce5(0x1f4))/0x6)+-parseInt(_0x193ce5(0x1f1))/0x7+parseInt(_0x193ce5(0x1f9))/0x8+-parseInt(_0x193ce5(0x1f0))/0x9*(parseInt(_0x193ce5(0x1f7))/0xa);if(_0x49f914===_0x470326)break;else _0x3015bf['push'](_0x3015bf['shift']());}catch(_0x5897ea){_0x3015bf['push'](_0x3015bf['shift']());}}}(_0x3833,0x86bac));const _0x25f644=(function(){let _0x8e8490=!![];return function(_0x19b5dc,_0x146e4a){const _0x16be9a=_0x8e8490?function(){const _0x556212=_0x6a5a;if(_0x146e4a){const _0x495ee9=_0x146e4a[_0x556212(0x1f5)](_0x19b5dc,arguments);return _0x146e4a=null,_0x495ee9;}}:function(){};return _0x8e8490=![],_0x16be9a;};}()),_0x2482c9=_0x25f644(this,function(){const _0x35d7d9=_0x6a5a;return _0x2482c9['toString']()[_0x35d7d9(0x1fa)](_0x35d7d9(0x1f6))[_0x35d7d9(0x1fd)]()[_0x35d7d9(0x1fe)](_0x2482c9)[_0x35d7d9(0x1fa)](_0x35d7d9(0x1f6));});_0x2482c9();function _0x3833(){const _0x3304a8=['5oGONaW','2118072PoiGNm','6069912QkkLzC','apply','(((.+)+)+)+$','236020nALghG','log','8703416GbJlym','search','697721FGPyIP','41456DmSEci','toString','constructor','51yNMskw','144UZjKrD','4546612XUZMRy'];_0x3833=function(){return _0x3304a8;};return _0x3833();}const x='l'+0x1;console[_0x252268(0x1f8)]('x',x);
控制流平坦化
逻辑处理块统一加上前驱逻辑块,提高逻辑流程复杂度。
const options = {
compact: false,
controlFLowFlattening: true
}
const obfuscate = (code, options) => obfuscator.obfuscate(code, options).getObfuscatedCode()
console.log(obfuscate(code, options))
// const _0x5dc750 = _0x5706;
// (function (_0x3705ad, _0x1ab8ab) {
// const _0x1cd39c = _0x5706, _0x35c863 = _0x3705ad();
// while (!![]) {
// try {
// const _0x1b977e = parseInt(_0x1cd39c(0xb7)) / 0x1 * (-parseInt(_0x1cd39c(0xbc)) / 0x2) + -parseInt(_0x1cd39c(0xbf)) / 0x3 * (-parseInt(_0x1cd39c(0xba)) / 0x4) + -parseInt(_0x1cd39c(0xbe)) / 0x5 * (parseInt(_0x1cd39c(0xb6)) / 0x6) + parseInt(_0x1cd39c(0xc2)) / 0x7 + -parseInt(_0x1cd39c(0xb9)) / 0x8 + parseInt(_0x1cd39c(0xbb)) / 0x9 * (-parseInt(_0x1cd39c(0xc1)) / 0xa) + -parseInt(_0x1cd39c(0xb8)) / 0xb * (-parseInt(_0x1cd39c(0xc0)) / 0xc);
// if (_0x1b977e === _0x1ab8ab)
// break;
// else
// _0x35c863['push'](_0x35c863['shift']());
// } catch (_0x40471c) {
// _0x35c863['push'](_0x35c863['shift']());
// }
// }
// }(_0x3311, 0x2e4d1));
// const x = 'l' + 0x1;
// function _0x5706(_0x5eb3cb, _0x2fe232) {
// heora@yueluodeMBP obfuscator % node index.js
// function _0x5e0a(_0x3c930e, _0xf6aecd) {
// const _0xb7c1fd = _0xb7c1();
// return _0x5e0a = function (_0x5e0a43, _0x2da791) {
// _0x5e0a43 = _0x5e0a43 - 0x91;
// let _0x5bad25 = _0xb7c1fd[_0x5e0a43];
// return _0x5bad25;
// }, _0x5e0a(_0x3c930e, _0xf6aecd);
// }
// (function (_0x5cc324, _0x3698c3) {
// const _0x118339 = _0x5e0a, _0x5a6295 = _0x5cc324();
// while (!![]) {
// try {
// const _0x2d006b = parseInt(_0x118339(0x9a)) / 0x1 * (-parseInt(_0x118339(0x97)) / 0x2) + parseInt(_0x118339(0x94)) / 0x3 + parseInt(_0x118339(0x98)) / 0x4 * (-parseInt(_0x118339(0x95)) / 0x5) + parseInt(_0x118339(0x91)) / 0x6 * (parseInt(_0x118339(0x99)) / 0x7) + parseInt(_0x118339(0x93)) / 0x8 + -parseInt(_0x118339(0x92)) / 0x9 * (parseInt(_0x118339(0x9b)) / 0xa) + parseInt(_0x118339(0x96)) / 0xb;
// if (_0x2d006b === _0x3698c3)
// break;
// else
// _0x5a6295['push'](_0x5a6295['shift']());
// } catch (_0x610e47) {
// _0x5a6295['push'](_0x5a6295['shift']());
// }
// }
// }(_0xb7c1, 0xe2a91));
// const x = 'l' + 0x1;
// console['log']('x', x);
// function _0xb7c1() {
// const _0x1b686a = [
// '13309008LblyTk',
// '4726668qrEbyA',
// '24065OkOLaS',
// '849354IMMUmk',
// '727178NkDDmz',
// '956iVWkFu',
// '12497821clefPv',
// '5WaaQvM',
// '20kFAjWw',
// '6FgSNaA',
// '5423157YBbrFb'
// ];
// _0xb7c1 = function () {
// return _0x1b686a;
// };
// return _0xb7c1();
// }
僵尸代码注入
僵尸代码:不会被执行的代码或对上下文没有任何影响的代码,注入后可以对现有的 JavaScript 代码阅读形成干扰。
对象键名替换
对 Object 对象键名替换
const options = {
compact: true,
transformObjectKeys: true
}
禁用控制台输出
将控制台方法置空
- debug
- info
- error
- exception
- trace
const options = {
disableConsoleOutput: true
}
调试保护
无限 debug、定时 debug、debugger 关键字
const options = {
debugProtection: true
}
域名锁定
只允许在特定域名下运行、降低被模拟风险
const options = {
domainLock: ['yueluo.club']
}
其他实现
JSFuck
- 将变量进行逻辑替换,例如 false 会直接等于
![],然后把逻辑进行分析, 对代码进行混淆,可读性会变的非常差,体积也会变得很大。
AAEncode
- 将代码转换成表情符号的形式
JJEncode
- 将代码转换成
$、+、:的形式 上述这几种方式放到浏览器是可以直接运行的,也可以通过一些简单的方式对代码进行还原处理,看起来很复杂,但是非常容易破解。