js混淆

68 阅读15分钟

代码混淆与加密

为什么加密或混淆

  • JavaScript 代码运行于客户端
  • JavaScript 代码是公开透明的

如何对 JavaScript 进行保护

  • 代码压缩:去除空格、换行等
  • 代码加密:eval、emscripten、WebAssembly 等
  • 代码混淆:变量混淆、常量混淆、控制流扁平化、调试保护等

JavaScript 加密实现

eval 加密

利用 eval 函数将 JavaScrpt 代码变成参数, eval 方法就是 JavaScript 代码的一个执行器,它可以把其中的参数按照 JavaScript 语法进行解析并执行。

这种加密方式其实就是把 JavaScript 代码变成 eval 方法的字符串参数,其中的一些字符都会被按照特定的格式编码

image.png 基于 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

  • 将代码转换成 $+: 的形式 上述这几种方式放到浏览器是可以直接运行的,也可以通过一些简单的方式对代码进行还原处理,看起来很复杂,但是非常容易破解。