JavaScript逆向爬虫(二)

本文涉及的产品
.cn 域名,1个 12个月
简介: JavaScript逆向爬虫(二)

接上文 JavaScript逆向爬虫(一)https://developer.aliyun.com/article/1621750

字符串混淆
字符串混淆,即将一个字符串声明放到一个数组里面,使之无法被直接搜到。这可以通过stringArray参数来控制,默认为true。此外,我们还可以通过rotateStringArray参数来控制数组化后结果的元素顺序,默认为true。还可以通过stringArrayEncoding参数来控制数组的编码形式,默认不开启编码。如果将其设置为true或base64,则会使用Base64编码;如果设置为rc4,则使用RC4编码。另外,可以通过stringArrayThreshold来控制启用编码的概率,其范围为0到1,默认值为0.8。示例如下:

const code = `
var a = 'hello world'   
`
const options = {
   
  stringArray: true,
  rotateStringArray: true,
  stringArrayEncoding: true, // '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 _0x348927=_0x186e;function _0x2437(_0x569362,_0x161a7e){
   var _0x29ec4d=_0x29ec();return _0x2437=function(_0x186ec2,_0x323e01){
   _0x186ec2=_0x186ec2-0xcb;var _0x279a37=_0x29ec4d[_0x186ec2];if(_0x2437['YrNdSE']===undefined){
   var _0x2bcac3=function(_0x3fc915){
   var _0x3fef53='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x20caa2='',_0x6b64eb='';for(var _0x57de42=0x0,_0xa2eb39,_0x291c79,_0xfc26d=0x0;_0x291c79=_0x3fc915['charAt'](_0xfc26d++);~_0x291c79&&(_0xa2eb39=_0x57de42%0x4?_0xa2eb39*0x40+_0x291c79:_0x291c79,_0x57de42++%0x4)?_0x20caa2+=String['fromCharCode'](0xff&_0xa2eb39>>(-0x2*_0x57de42&0x6)):0x0){
   _0x291c79=_0x3fef53['indexOf'](_0x291c79);}for(var _0x1ee15d=0x0,_0x38dec2=_0x20caa2['length'];_0x1ee15d<_0x38dec2;_0x1ee15d++){
   _0x6b64eb+='%'+('00'+_0x20caa2['charCodeAt'](_0x1ee15d)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x6b64eb);};var _0x243785=function(_0x20adaf,_0x376e17){
   var _0x6c264c=[],_0x3434d7=0x0,_0x370984,_0x568422='';_0x20adaf=_0x2bcac3(_0x20adaf);var _0x43cd15;for(_0x43cd15=0x0;_0x43cd15<0x100;_0x43cd15++){
   _0x6c264c[_0x43cd15]=_0x43cd15;}for(_0x43cd15=0x0;_0x43cd15<0x100;_0x43cd15++){
   _0x3434d7=(_0x3434d7+_0x6c264c[_0x43cd15]+_0x376e17['charCodeAt'](_0x43cd15%_0x376e17['length']))%0x100,_0x370984=_0x6c264c[_0x43cd15],_0x6c264c[_0x43cd15]=_0x6c264c[_0x3434d7],_0x6c264c[_0x3434d7]=_0x370984;}_0x43cd15=0x0,_0x3434d7=0x0;for(var _0x1c5b38=0x0;_0x1c5b38<_0x20adaf['length'];_0x1c5b38++){
   _0x43cd15=(_0x43cd15+0x1)%0x100,_0x3434d7=(_0x3434d7+_0x6c264c[_0x43cd15])%0x100,_0x370984=_0x6c264c[_0x43cd15],_0x6c264c[_0x43cd15]=_0x6c264c[_0x3434d7],_0x6c264c[_0x3434d7]=_0x370984,_0x568422+=String['fromCharCode'](_0x20adaf['charCodeAt'](_0x1c5b38)^_0x6c264c[(_0x6c264c[_0x43cd15]+_0x6c264c[_0x3434d7])%0x100]);}return _0x568422;};_0x2437['TYlecL']=_0x243785,_0x569362=arguments,_0x2437['YrNdSE']=!![];}var _0x3af191=_0x29ec4d[0x0],_0x1407eb=_0x186ec2+_0x3af191,_0x4a5e9a=_0x569362[_0x1407eb];return!_0x4a5e9a?(_0x2437['AFUziT']===undefined&&(_0x2437['AFUziT']=!![]),_0x279a37=_0x2437['TYlecL'](_0x279a37,_0x323e01),_0x569362[_0x1407eb]=_0x279a37):_0x279a37=_0x4a5e9a,_0x279a37;},_0x2437(_0x569362,_0x161a7e);}function _0x29ec(){
   var _0x2d1bbe=['oeL1t0Dpsq','ntyWntq0mgjIv2z6BG','W4hdII/cI8ouW5VdR8oFWOJcUSo5WPb8','W4CulavHWOTWWPldGCkRW501W5a','eCoGrmoeW7TnDaJcRmkbW4HLyq','WOpcMmkzWONdNCkMW6u','W4WfW49oW6XHWPtcQSkcu3X9','WQpdUCoEe8ofW4zDuctcSSo0zCkhW6O','wJpdVmkvWRpcJcpcPSkfBSoCexC','WRVcMSoqFuD9W65kfSkivX0U','kmolpftdOdtdVa','ndu4nJG0teHWAKnI','WQNcQSkasmkxWROn','owfVAgfeAa','AgvSBg8GD29YBgq','W7xcTdNdMmkZW43dL0RcRgyDqa','WOZdPmoFW7/cQmorWP5nW6RcJ8o7W5bO','emkevCkwWPVdRZq','otm5nJa1A3P4D0ft','mtKXmtC2ngDIvurxsW','W4FdJI7cJCotW5FdP8knWP3cI8oyWPjLDW'];_0x29ec=function(){
   return _0x2d1bbe;};return _0x29ec();}(function(_0x5d03a7,_0x32a43a){
   var _0x16808a=_0x186e,_0x363257=_0x2437,_0x51d111=_0x5d03a7();while(!![]){
   try{
   var _0xdf507f=parseInt(_0x363257(0xdc,'BKF('))/0x1*(-parseInt(_0x363257(0xd3,'y%JB'))/0x2)+parseInt(_0x16808a(0xd8))/0x3*(-parseInt(_0x16808a(0xd6))/0x4)+parseInt(_0x16808a(0xdd))/0x5+-parseInt(_0x16808a(0xcc))/0x6+-parseInt(_0x363257(0xce,'2AKB'))/0x7*(-parseInt(_0x16808a(0xcb))/0x8)+-parseInt(_0x363257(0xdb,'XaDc'))/0x9+parseInt(_0x363257(0xdf,'AhkU'))/0xa;if(_0xdf507f===_0x32a43a)break;else _0x51d111['push'](_0x51d111['shift']());}catch(_0x4c7127){
   _0x51d111['push'](_0x51d111['shift']());}}}(_0x29ec,0x771f3));function _0x186e(_0x569362,_0x161a7e){
   var _0x29ec4d=_0x29ec();return _0x186e=function(_0x186ec2,_0x323e01){
   _0x186ec2=_0x186ec2-0xcb;var _0x279a37=_0x29ec4d[_0x186ec2];if(_0x186e['cxplLu']===undefined){
   var _0x2bcac3=function(_0x243785){
   var _0x3fc915='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';var _0x3fef53='',_0x20caa2='';for(var _0x6b64eb=0x0,_0x57de42,_0xa2eb39,_0x291c79=0x0;_0xa2eb39=_0x243785['charAt'](_0x291c79++);~_0xa2eb39&&(_0x57de42=_0x6b64eb%0x4?_0x57de42*0x40+_0xa2eb39:_0xa2eb39,_0x6b64eb++%0x4)?_0x3fef53+=String['fromCharCode'](0xff&_0x57de42>>(-0x2*_0x6b64eb&0x6)):0x0){
   _0xa2eb39=_0x3fc915['indexOf'](_0xa2eb39);}for(var _0xfc26d=0x0,_0x1ee15d=_0x3fef53['length'];_0xfc26d<_0x1ee15d;_0xfc26d++){
   _0x20caa2+='%'+('00'+_0x3fef53['charCodeAt'](_0xfc26d)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x20caa2);};_0x186e['xQSoyb']=_0x2bcac3,_0x569362=arguments,_0x186e['cxplLu']=!![];}var _0x3af191=_0x29ec4d[0x0],_0x1407eb=_0x186ec2+_0x3af191,_0x4a5e9a=_0x569362[_0x1407eb];return!_0x4a5e9a?(_0x279a37=_0x186e['xQSoyb'](_0x279a37),_0x569362[_0x1407eb]=_0x279a37):_0x279a37=_0x4a5e9a,_0x279a37;},_0x186e(_0x569362,_0x161a7e);}var a=_0x348927(0xd9);

可以看到,这里就把字符串进行了Base64编码,我们再也无法通过查找的方式找到字符串的位置了。如果将stringArray设置为false的话,输出就是这样:

var a='hello\x20world';

字符串就仍然是明文显示的,没有被编码。另外,我们还可以使用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 (_0x1b6a0d, _0x1c97c8) {
   
    var _0xd2d623 = _0x52e8, _0x217f4a = _0x1b6a0d();
    while (!![]) {
   
        try {
   
            var _0x2408ea = -parseInt(_0xd2d623(0xdf)) / 0x1 + parseInt(_0xd2d623(0xde)) / 0x2 + -parseInt(_0xd2d623(0xe1)) / 0x3 + -parseInt(_0xd2d623(0xe4)) / 0x4 + parseInt(_0xd2d623(0xe0)) / 0x5 + -parseInt(_0xd2d623(0xe3)) / 0x6 * (-parseInt(_0xd2d623(0xe2)) / 0x7) + -parseInt(_0xd2d623(0xdd)) / 0x8;
            if (_0x2408ea === _0x1c97c8)
                break;
            else
                _0x217f4a['push'](_0x217f4a['shift']());
        } catch (_0x7aa649) {
   
            _0x217f4a['push'](_0x217f4a['shift']());
        }
    }
}(_0x1aea, 0xf1bf2));
function _0x52e8(_0x4d9345, _0xe5f9cb) {
   
    var _0x1aeadb = _0x1aea();
    return _0x52e8 = function (_0x52e84a, _0x45526f) {
   
        _0x52e84a = _0x52e84a - 0xdd;
        var _0x253278 = _0x1aeadb[_0x52e84a];
        return _0x253278;
    }, _0x52e8(_0x4d9345, _0xe5f9cb);
}
function _0x1aea() {
   
    var _0x2c665d = [
        '\x31\x32\x39\x39\x32\x31\x34\x37\x69\x4f\x55\x69\x41\x61',
        '\x36\x7a\x57\x4b\x61\x6e\x77',
        '\x34\x32\x36\x39\x39\x34\x34\x75\x79\x75\x79\x69\x79',
        '\x33\x32\x39\x35\x33\x33\x36\x4e\x77\x57\x53\x4a\x42',
        '\x33\x39\x31\x39\x37\x30\x42\x63\x71\x79\x49\x53',
        '\x38\x39\x38\x33\x36\x31\x46\x6c\x50\x68\x44\x57',
        '\x39\x31\x39\x33\x33\x33\x35\x4d\x51\x77\x65\x43\x54',
        '\x31\x35\x36\x38\x31\x34\x35\x65\x70\x74\x6a\x77\x48'
    ];
    _0x1aea = function () {
   
        return _0x2c665d;
    };
    return _0x1aea();
}
var a = '\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64';

可以看到,这里字符串被数字化和Unicode化,非常难以辨认。

在很多JavaScript逆向的过程中,一些关键的字符串可能会作为切入点来查找加密入口。用了这种混淆之后,如果有人想通过全局搜索的方式搜索hello这样的字符串找加密入口,也没法搜到了。

代码自我保护
我们可以通过设置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 _0x1b56f8=_0x3c50;function _0x44c6(){
   var _0x57fffd=['90xSYcHj','1466352DFEbcP','2556301FzbUEv','385059xBOYtr','255308fIGnns','256488kiENBq','10JJXZMY','10zxBYXG','hello\x20world','log','(((.+)+)+)+$','toString','48TIjOQD','34aBNcqb','constructor','search','313586SXQUdp','74373Weqtoq'];_0x44c6=function(){
   return _0x57fffd;};return _0x44c6();}function _0x3c50(_0x1b980a,_0x26ed0b){
   var _0x27acc5=_0x44c6();return _0x3c50=function(_0x279fc4,_0x55725f){
   _0x279fc4=_0x279fc4-0x138;var _0x44c649=_0x27acc5[_0x279fc4];return _0x44c649;},_0x3c50(_0x1b980a,_0x26ed0b);}(function(_0x3f4390,_0x55f028){
   var _0x54030a=_0x3c50,_0xe6d746=_0x3f4390();while(!![]){
   try{
   var _0x9b2181=-parseInt(_0x54030a(0x13b))/0x1+parseInt(_0x54030a(0x145))/0x2*(parseInt(_0x54030a(0x149))/0x3)+parseInt(_0x54030a(0x13c))/0x4*(-parseInt(_0x54030a(0x13e))/0x5)+parseInt(_0x54030a(0x144))/0x6*(-parseInt(_0x54030a(0x148))/0x7)+parseInt(_0x54030a(0x13d))/0x8*(parseInt(_0x54030a(0x138))/0x9)+parseInt(_0x54030a(0x13f))/0xa*(parseInt(_0x54030a(0x13a))/0xb)+parseInt(_0x54030a(0x139))/0xc;if(_0x9b2181===_0x55f028)break;else _0xe6d746['push'](_0xe6d746['shift']());}catch(_0x6dab8c){
   _0xe6d746['push'](_0xe6d746['shift']());}}}(_0x44c6,0x3710b));var _0x55725f=(function(){
   var _0x3b535c=!![];return function(_0x2040d0,_0x1e024e){
   var _0xa27340=_0x3b535c?function(){
   if(_0x1e024e){
   var _0xe5a99f=_0x1e024e['apply'](_0x2040d0,arguments);return _0x1e024e=null,_0xe5a99f;}}:function(){
   };return _0x3b535c=![],_0xa27340;};}()),_0x279fc4=_0x55725f(this,function(){
   var _0x201807=_0x3c50;return _0x279fc4[_0x201807(0x143)]()[_0x201807(0x147)](_0x201807(0x142))[_0x201807(0x143)]()[_0x201807(0x146)](_0x279fc4)[_0x201807(0x147)](_0x201807(0x142));});_0x279fc4(),console[_0x1b56f8(0x141)](_0x1b56f8(0x140));

如果我们将上述代码放到控制台,执行结果和之前一模一样的,没有任何问题。

控制流平坦化
控制流平坦化其实就是将代码的执行逻辑混淆,使其变得复杂、难读。其基本思想是将一些逻辑处理块都统一加上一个前驱逻辑块,每个逻辑块都由前驱逻辑块进行条件判断和分发,构成一个个闭环逻辑,这导致整个执行逻辑十分复杂、难读。

这里有段示例代码:

console.log(c);
console.log(a);
console.log(b);

代码逻辑一目了然,依次在控制台输出了c、a、b三个变量值。但如果把这段代码进行控制流平坦化处理,代码就会变成这样:

const s = "3|1|2".split("|");
let x = 0;
while (true){
   
    switch (s[x++]){
   
        case "1":
            console.log(a);
            continue;
        case "2":
            console.log(b);
            continue;
        case "3":
            console.log(c);
            continue;

    }
    break;
}

可以看到,混淆后的代码首先声明了一个变量s,它的结果是一个列表,其实是[”3”, ”1”, ”2”],然后下面通过switch语句对s中的元素进行了判断,每个case都加上了各自的代码逻辑。通过这样的处理,一些连续的执行逻辑就被打破了,代码被修改为一个switch语句,原本我们可以一眼看出的逻辑是控制台先输出c,然后才是a、b,但是现在我们必须结合switch的判断条件和对应case的内容进行判断,我们很难再一眼看出每条语句的执行顺序,这大大降低了代码的可读性。

在javasript-obfuscator中,我们通过controlFlowFlattening变量可以控制是否开启控制流平坦化,示例如下:

const options = {
   
    compact: false,
    controlFlowFlattening: true
    }

使用控制流平坦化可以使得执行逻辑更加复杂、难读,目前非常多的前端混淆都会加上这个选项。但启用控制流平坦化之后,代码的执行时间会变长,最长大1.5倍之多。

另外,我们还能使用controlFlowFlatteningThreshold这个参数来控制比例,取值范围是0 到1,默认值0.75。如果将该参数设置为0,那相当于将controlFlowFlattening设置为false,即不开启控制流扁平化。

无用代码注入
无用代码即不会被执行的代码或对上下文没有任何影响的代码,注入之后可以对现有的JavaScript代码的阅读形成干扰。我们可以使用deadCodeInjection参数开启这个选项,其默认值为false。

示例代码如下:

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))

这里声明了方法a 和b,然后依次进行调用,分别输出两句话。经过无用代码注入处理之后,代码会变成这样:

(function (_0x5e48ef, _0x428808) {
   
    const _0x3f9afb = _0x5151, _0x16cb33 = _0x5e48ef();
    while (!![]) {
   
        try {
   
            const _0x4621c2 = -parseInt(_0x3f9afb(0x1e9)) / 0x1 * (-parseInt(_0x3f9afb(0x1e5)) / 0x2) + parseInt(_0x3f9afb(0x1e1)) / 0x3 + parseInt(_0x3f9afb(0x1ea)) / 0x4 + -parseInt(_0x3f9afb(0x1e8)) / 0x5 * (-parseInt(_0x3f9afb(0x1e3)) / 0x6) + -parseInt(_0x3f9afb(0x1e6)) / 0x7 + -parseInt(_0x3f9afb(0x1e4)) / 0x8 + -parseInt(_0x3f9afb(0x1e7)) / 0x9;
            if (_0x4621c2 === _0x428808)
                break;
            else
                _0x16cb33['push'](_0x16cb33['shift']());
        } catch (_0xe49682) {
   
            _0x16cb33['push'](_0x16cb33['shift']());
        }
    }
}(_0xf9fd, 0x52ef1));
const a = function () {
   
        const _0x445e1a = _0x5151;
        console[_0x445e1a(0x1e2)]('hello\x20world');
    }, b = function () {
   
        console['log']('nice\x20to\x20meet\x20you');
    };
function _0x5151(_0x1bea8c, _0x4611c5) {
   
    const _0xf9fdc7 = _0xf9fd();
    return _0x5151 = function (_0x515190, _0x28e2ae) {
   
        _0x515190 = _0x515190 - 0x1e1;
        let _0x459069 = _0xf9fdc7[_0x515190];
        return _0x459069;
    }, _0x5151(_0x1bea8c, _0x4611c5);
}
function _0xf9fd() {
   
    const _0x145385 = [
        '102rjAcjB',
        '4037216eCpEFT',
        '300xBNgHs',
        '2112922TJhNbR',
        '1031958MqkGJZ',
        '93895ZomTGw',
        '1300DSmZqn',
        '2621160RhOGdz',
        '273972HsWiAN',
        'log'
    ];
    _0xf9fd = function () {
   
        return _0x145385;
    };
    return _0xf9fd();
}
a(), b();

可以看到,每个方法内部都增加了额外的if…else语句,其中if的判断条件还是一个表达式,其结果是true还是false我们还不能一眼看出来。但是运行结果是完全一致的。如下图所示:

对象键名替换
如果是一个对象,可以使用transformObjectKeys来对对象的键值进行替换,示例如下:

(function (_0x4a3c9e, _0x11470d) {
   
    var _0x43ce74 = _0x3495, _0x5eed25 = _0x4a3c9e();
    while (!![]) {
   
        try {
   
            var _0xa49492 = parseInt(_0x43ce74(0x186)) / 0x1 * (-parseInt(_0x43ce74(0x17f)) / 0x2) + -parseInt(_0x43ce74(0x18a)) / 0x3 * (parseInt(_0x43ce74(0x185)) / 0x4) + -parseInt(_0x43ce74(0x184)) / 0x5 + -parseInt(_0x43ce74(0x188)) / 0x6 + -parseInt(_0x43ce74(0x180)) / 0x7 * (-parseInt(_0x43ce74(0x187)) / 0x8) + -parseInt(_0x43ce74(0x18b)) / 0x9 + parseInt(_0x43ce74(0x18e)) / 0xa * (parseInt(_0x43ce74(0x18d)) / 0xb);
            if (_0xa49492 === _0x11470d)
                break;
            else
                _0x5eed25['push'](_0x5eed25['shift']());
        } catch (_0x5e25b4) {
   
            _0x5eed25['push'](_0x5eed25['shift']());
        }
    }
}(_0x5a77, 0x5b892), (function () {
   
    var _0x15c415 = _0x3495, _0x2b820c = {
   };
    _0x2b820c[_0x15c415(0x189)] = _0x15c415(0x182);
    var _0x20ee51 = {
   };
    _0x20ee51[_0x15c415(0x18c)] = _0x15c415(0x183), _0x20ee51[_0x15c415(0x181)] = _0x2b820c;
    var _0x1b69b9 = _0x20ee51;
}()));
function _0x3495(_0x519889, _0x407957) {
   
    var _0x5a7770 = _0x5a77();
    return _0x3495 = function (_0x3495e6, _0xa2fa2c) {
   
        _0x3495e6 = _0x3495e6 - 0x17f;
        var _0x4ce931 = _0x5a7770[_0x3495e6];
        return _0x4ce931;
    }, _0x3495(_0x519889, _0x407957);
}
function _0x5a77() {
   
    var _0x34ee00 = [
        '29168FqvoWB',
        '4274538FlVWrr',
        'baz',
        '102777xzCjUf',
        '6400350VUCeVe',
        'foo',
        '11UVKDWv',
        '19432430SqQONT',
        '2eQHxls',
        '1211yZLccH',
        'bar',
        'test2',
        'test1',
        '2080500Mcwxoy',
        '4CYgQwf',
        '325139uebZtP'
    ];
    _0x5a77 = function () {
   
        return _0x34ee00;
    };
    return _0x5a77();
}

可以看到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))

运行结果如下:

var _0x2102a4=_0x2ee5;(function(_0x2523e4,_0x50adc4){
   var _0x36a6f5=_0x2ee5,_0x122ebb=_0x2523e4();while(!![]){
   try{
   var _0x5502d9=parseInt(_0x36a6f5(0x159))/0x1*(parseInt(_0x36a6f5(0x15b))/0x2)+parseInt(_0x36a6f5(0x168))/0x3*(parseInt(_0x36a6f5(0x167))/0x4)+-parseInt(_0x36a6f5(0x16a))/0x5+-parseInt(_0x36a6f5(0x15d))/0x6*(-parseInt(_0x36a6f5(0x162))/0x7)+parseInt(_0x36a6f5(0x166))/0x8+parseInt(_0x36a6f5(0x164))/0x9+parseInt(_0x36a6f5(0x16b))/0xa*(-parseInt(_0x36a6f5(0x16c))/0xb);if(_0x5502d9===_0x50adc4)break;else _0x122ebb['push'](_0x122ebb['shift']());}catch(_0x85f884){
   _0x122ebb['push'](_0x122ebb['shift']());}}}(_0x5a6e,0x8c312));var _0x3537a0=(function(){
   var _0x4778b4=!![];return function(_0x1b7965,_0x3b16f8){
   var _0x38da34=_0x4778b4?function(){
   var _0x4e48ef=_0x2ee5;if(_0x3b16f8){
   var _0x164ad8=_0x3b16f8[_0x4e48ef(0x161)](_0x1b7965,arguments);return _0x3b16f8=null,_0x164ad8;}}:function(){
   };return _0x4778b4=![],_0x38da34;};}()),_0x1ec86f=_0x3537a0(this,function(){
   var _0x6679e8=_0x2ee5,_0x87c2e0=function(){
   var _0x3dec91=_0x2ee5,_0x5ab99c;try{
   _0x5ab99c=Function(_0x3dec91(0x15f)+_0x3dec91(0x16d)+');')();}catch(_0x240b7f){
   _0x5ab99c=window;}return _0x5ab99c;},_0x4f4cd5=_0x87c2e0(),_0x2400b0=_0x4f4cd5[_0x6679e8(0x169)]=_0x4f4cd5[_0x6679e8(0x169)]||{
   },_0x37f40a=[_0x6679e8(0x165),_0x6679e8(0x160),_0x6679e8(0x15e),_0x6679e8(0x163),'exception',_0x6679e8(0x15c),_0x6679e8(0x158)];for(var _0x56a6e1=0x0;_0x56a6e1<_0x37f40a[_0x6679e8(0x170)];_0x56a6e1++){
   var _0x5edc8b=_0x3537a0['constructor'][_0x6679e8(0x15a)][_0x6679e8(0x16e)](_0x3537a0),_0xfe5de9=_0x37f40a[_0x56a6e1],_0x4505a3=_0x2400b0[_0xfe5de9]||_0x5edc8b;_0x5edc8b[_0x6679e8(0x16f)]=_0x3537a0['bind'](_0x3537a0),_0x5edc8b[_0x6679e8(0x157)]=_0x4505a3[_0x6679e8(0x157)][_0x6679e8(0x16e)](_0x4505a3),_0x2400b0[_0xfe5de9]=_0x5edc8b;}});function _0x2ee5(_0x475530,_0xaf728c){
   var _0x2aa2b1=_0x5a6e();return _0x2ee5=function(_0x1ec86f,_0x3537a0){
   _0x1ec86f=_0x1ec86f-0x157;var _0x3160ba=_0x2aa2b1[_0x1ec86f];return _0x3160ba;},_0x2ee5(_0x475530,_0xaf728c);}_0x1ec86f(),console[_0x2102a4(0x165)]('hello\x20world');function _0x5a6e(){
   var _0x2bd5f5=['console','340725zAoDtK','2340mHCxzs','170049QFdlAw','{}.constructor(\x22return\x20this\x22)(\x20)','bind','__proto__','length','toString','trace','827903DGttIy','prototype','2ZKfJGV','table','6EoTEXw','info','return\x20(function()\x20','warn','apply','7548023dMilus','error','3236121MZEjtb','log','6974296tynSuQ','4488916UjJrGf','3bPnCdf'];_0x5a6e=function(){
   return _0x2bd5f5;};return _0x5a6e();}

此时,我们如果执行这段代码,发现没有任何输出,这里实际就是将console的一些功能禁用了。

调试保护
我们知道,如果在JavaScript代码中加入debugger关键字,那么执行到该位置的时候,就会进入断点调试模式。如果在代码多个位置都加入debugger关键字,或者定义某个逻辑来反复执行debugger,就会不断进入断点调试模式,原本的代码就无法顺畅执行了。这个过程可以称为调试保护,即通过反复执行debugger来使得原来的代码无法顺畅执行。

其效果类似于执行了如下代码:

setInterval(() => {
   debugger;}, 3000)

如果我们把这段代码粘贴到控制台,它就会反复执行debugger语句,进入断点调试模式,从而干扰正常的调试流程。

在javascript-obfuscator中,我们可以使用debugProtection来启用调试保护机制,还可以使用debugProtectionInterval来启用无限调试(debug),使得代码在调试过程中不断进入断点模式,无法顺畅执行,配置如下:

const _0x368f32=_0x3817;(function(_0x3427f4,_0xaa7d82){
   const _0x5c7233=_0x3817,_0x282a31=_0x3427f4();while(!![]){
   try{
   const _0x5cef06=-parseInt(_0x5c7233(0x8e))/0x1+parseInt(_0x5c7233(0x86))/0x2*(-parseInt(_0x5c7233(0x82))/0x3)+-parseInt(_0x5c7233(0x79))/0x4+-parseInt(_0x5c7233(0x80))/0x5*(-parseInt(_0x5c7233(0x77))/0x6)+parseInt(_0x5c7233(0x89))/0x7+parseInt(_0x5c7233(0x8a))/0x8+-parseInt(_0x5c7233(0x81))/0x9*(-parseInt(_0x5c7233(0x85))/0xa);if(_0x5cef06===_0xaa7d82)break;else _0x282a31['push'](_0x282a31['shift']());}catch(_0x404c9f){
   _0x282a31['push'](_0x282a31['shift']());}}}(_0x934f,0x36b4b));const _0x3b94e2=(function(){
   let _0x12f64e=!![];return function(_0x57d7a0,_0x483a5e){
   const _0x4e8a30=_0x12f64e?function(){
   const _0x32b0c4=_0x3817;if(_0x483a5e){
   const _0x8198dc=_0x483a5e[_0x32b0c4(0x88)](_0x57d7a0,arguments);return _0x483a5e=null,_0x8198dc;}}:function(){
   };return _0x12f64e=![],_0x4e8a30;};}());function _0x3817(_0x258a52,_0x36b674){
   const _0x1ab7cf=_0x934f();return _0x3817=function(_0x1d4554,_0x3b94e2){
   _0x1d4554=_0x1d4554-0x77;let _0x934ff5=_0x1ab7cf[_0x1d4554];return _0x934ff5;},_0x3817(_0x258a52,_0x36b674);}(function(){
   _0x3b94e2(this,function(){
   const _0x2daf5e=_0x3817,_0x9d16e7=new RegExp(_0x2daf5e(0x8b)),_0x4c39ae=new RegExp(_0x2daf5e(0x78),'i'),_0x29fce1=_0x1d4554(_0x2daf5e(0x8c));!_0x9d16e7['test'](_0x29fce1+_0x2daf5e(0x7f))||!_0x4c39ae['test'](_0x29fce1+_0x2daf5e(0x84))?_0x29fce1('0'):_0x1d4554();})();}());function _0x934f(){
   const _0x21927d=['1208421IXtqKl','action','input','195110ErSnZQ','2EdkfEw','debu','apply','2940679MgffpO','1336888tGNuYo','function\x20*\x5c(\x20*\x5c)','init','call','106462FFldAH','log','6gKHOyW','\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)','1420616mjWpZp','stateObject','while\x20(true)\x20{}','gger','constructor','counter','chain','1823565GBzQuK','63PEYKVn'];_0x934f=function(){
   return _0x21927d;};return _0x934f();}for(let i=0x0;i<0x5;i++){
   console[_0x368f32(0x8f)]('i',i);}function _0x1d4554(_0xad2192){
   function _0x4a8589(_0x26058b){
   const _0x193dae=_0x3817;if(typeof _0x26058b==='string')return function(_0x376fd7){
   }['constructor'](_0x193dae(0x7b))[_0x193dae(0x88)](_0x193dae(0x7e));else(''+_0x26058b/_0x26058b)['length']!==0x1||_0x26058b%0x14===0x0?function(){
   return!![];}[_0x193dae(0x7d)](_0x193dae(0x87)+_0x193dae(0x7c))[_0x193dae(0x8d)](_0x193dae(0x83)):function(){
   return![];}[_0x193dae(0x7d)]('debu'+_0x193dae(0x7c))[_0x193dae(0x88)](_0x193dae(0x7a));_0x4a8589(++_0x26058b);}try{
   if(_0xad2192)return _0x4a8589;else _0x4a8589(0x0);}catch(_0x348a52){
   }}

域名锁定
还可以通过控制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))

这里我们使用domainLock指定一个域名cuiqingcai.com,也就是设置了一个域名白名单,混淆后的代码结果如下:

var _0x589dd8=_0x2d78;function _0x2d78(_0x47b956,_0x27bf9f){
   var _0x322302=_0x4ea5();return _0x2d78=function(_0x2160d5,_0x3049cc){
   _0x2160d5=_0x2160d5-0x16d;var _0x135bae=_0x322302[_0x2160d5];return _0x135bae;},_0x2d78(_0x47b956,_0x27bf9f);}(function(_0x47a2b8,_0x37e8f9){
   var _0xe5499b=_0x2d78,_0x1cabdc=_0x47a2b8();while(!![]){
   try{
   var _0x1ef79d=parseInt(_0xe5499b(0x173))/0x1+-parseInt(_0xe5499b(0x176))/0x2*(-parseInt(_0xe5499b(0x16d))/0x3)+-parseInt(_0xe5499b(0x180))/0x4+parseInt(_0xe5499b(0x16e))/0x5+parseInt(_0xe5499b(0x17e))/0x6+parseInt(_0xe5499b(0x177))/0x7*(-parseInt(_0xe5499b(0x179))/0x8)+-parseInt(_0xe5499b(0x175))/0x9;if(_0x1ef79d===_0x37e8f9)break;else _0x1cabdc['push'](_0x1cabdc['shift']());}catch(_0x54204c){
   _0x1cabdc['push'](_0x1cabdc['shift']());}}}(_0x4ea5,0x9f9ea));var _0x3049cc=(function(){
   var _0x3f8a2b=!![];return function(_0x186865,_0x1497e8){
   var _0x4fffaf=_0x3f8a2b?function(){
   var _0x231187=_0x2d78;if(_0x1497e8){
   var _0x3bedd8=_0x1497e8[_0x231187(0x174)](_0x186865,arguments);return _0x1497e8=null,_0x3bedd8;}}:function(){
   };return _0x3f8a2b=![],_0x4fffaf;};}()),_0x2160d5=_0x3049cc(this,function(){
   var _0x51cf3e=_0x2d78,_0x2f9734=function(){
   var _0x4dfbff=_0x2d78,_0x1e30e7;try{
   _0x1e30e7=Function(_0x4dfbff(0x17a)+_0x4dfbff(0x17d)+');')();}catch(_0x5a8abd){
   _0x1e30e7=window;}return _0x1e30e7;},_0x2a11e6=_0x2f9734(),_0x597b09=new RegExp(_0x51cf3e(0x183),'g'),_0x232a84=_0x51cf3e(0x17c)[_0x51cf3e(0x178)](_0x597b09,'')[_0x51cf3e(0x17b)](';'),_0x43e98e,_0x559142,_0x50bd03,_0x2e039e,_0x244d9b=function(_0x43517a,_0x4e52f4,_0x1634dc){
   var _0x127a9d=_0x51cf3e;if(_0x43517a[_0x127a9d(0x171)]!=_0x4e52f4)return![];for(var _0x10edb7=0x0;_0x10edb7<_0x4e52f4;_0x10edb7++){
   for(var _0x202b79=0x0;_0x202b79<_0x1634dc[_0x127a9d(0x171)];_0x202b79+=0x2){
   if(_0x10edb7==_0x1634dc[_0x202b79]&&_0x43517a[_0x127a9d(0x181)](_0x10edb7)!=_0x1634dc[_0x202b79+0x1])return![];}}return!![];},_0xf4a5ae=function(_0x3e5617,_0x53d5db,_0x58ab24){
   return _0x244d9b(_0x53d5db,_0x58ab24,_0x3e5617);},_0x2fc234=function(_0x2d27b6,_0x2a93f3,_0xa5fb12){
   return _0xf4a5ae(_0x2a93f3,_0x2d27b6,_0xa5fb12);},_0x4d5190=function(_0x112bb6,_0x2fb813,_0x3ae06d){
   return _0x2fc234(_0x2fb813,_0x3ae06d,_0x112bb6);};for(var _0x4aedca in _0x2a11e6){
   if(_0x244d9b(_0x4aedca,0x8,[0x7,0x74,0x5,0x65,0x3,0x75,0x0,0x64])){
   _0x43e98e=_0x4aedca;break;}}for(var _0xcbf864 in _0x2a11e6[_0x43e98e]){
   if(_0x4d5190(0x6,_0xcbf864,[0x5,0x6e,0x0,0x64])){
   _0x559142=_0xcbf864;break;}}for(var _0x392075 in _0x2a11e6[_0x43e98e]){
   if(_0x2fc234(_0x392075,[0x7,0x6e,0x0,0x6c],0x8)){
   _0x50bd03=_0x392075;break;}}if(!('~'>_0x559142))for(var _0x382f12 in _0x2a11e6[_0x43e98e][_0x50bd03]){
   if(_0xf4a5ae([0x7,0x65,0x0,0x68],_0x382f12,0x8)){
   _0x2e039e=_0x382f12;break;}}if(!_0x43e98e||!_0x2a11e6[_0x43e98e])return;var _0xa19a9b=_0x2a11e6[_0x43e98e][_0x559142],_0x119b47=!!_0x2a11e6[_0x43e98e][_0x50bd03]&&_0x2a11e6[_0x43e98e][_0x50bd03][_0x2e039e],_0x15b5d4=_0xa19a9b||_0x119b47;if(!_0x15b5d4)return;var _0x154fe5=![];for(var _0x27c485=0x0;_0x27c485<_0x232a84[_0x51cf3e(0x171)];_0x27c485++){
   var _0x559142=_0x232a84[_0x27c485],_0x31b39f=_0x559142[0x0]===String['fromCharCode'](0x2e)?_0x559142[_0x51cf3e(0x184)](0x1):_0x559142,_0x4e0de7=_0x15b5d4[_0x51cf3e(0x171)]-_0x31b39f[_0x51cf3e(0x171)],_0x121706=_0x15b5d4[_0x51cf3e(0x182)](_0x31b39f,_0x4e0de7),_0x60443c=_0x121706!==-0x1&&_0x121706===_0x4e0de7;_0x60443c&&((_0x15b5d4[_0x51cf3e(0x171)]==_0x559142[_0x51cf3e(0x171)]||_0x559142[_0x51cf3e(0x182)]('.')===0x0)&&(_0x154fe5=!![]));}if(!_0x154fe5){
   var _0x5e1b5f=new RegExp(_0x51cf3e(0x16f),'g'),_0x23031c=_0x51cf3e(0x17f)[_0x51cf3e(0x178)](_0x5e1b5f,'');_0x2a11e6[_0x43e98e][_0x50bd03]=_0x23031c;}});function _0x4ea5(){
   var _0x50c27c=['13119lwLMyn','6278280HEebXS','[dQTZKevHZpcChQTvAQYpScST]','log','length','hello\x20world','942985MpbBIJ','apply','20740851Upfdlp','526lrYFza','14OdaaIU','replace','1532632YoqplN','return\x20(function()\x20','split','kvcuAtFiqreinCwgVycai.czoAmXyetftvQFeYkzYbzwOehtfT','{}.constructor(\x22return\x20this\x22)(\x20)','3591678pQUkUO','adQTbZouKetvHZpcC:bhQlaTvAQnkYpScST','2423416EImzoL','charCodeAt','indexOf','[kvAtFreCwVyzAXyetftvQFeYkzYbzwOehtfT]','slice'];_0x4ea5=function(){
   return _0x50c27c;};return _0x4ea5();}_0x2160d5(),console[_0x589dd8(0x170)](_0x589dd8(0x172));

这段代码就只能在指定域名cuiqingcai.com下运行,不能在其他网站运行。这样的话,如果一些相关JavaScript代码被单独剥离出来,想在其他网站运行或者使用程序模拟运行的话,运行结果只有失败,这样就可以有效降低代码被模拟或盗用的风险。

特殊编码

var a = 1

jsfuck在线工具的网站如下图所示:

使用jsfuck工具的结果:

[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+
...
[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]()[+!+[]+[+!+[]]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+[+!+[]]+(![]+[])[+[]]+([][[]]+[])[!+[]+!+[]]+(![]+[])[+[]]+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(![]+[])[+[]]+([][[]]+[])[!+[]+!+[]]+(![]+[])[+!+[]])

使用aaencode工具的结果:

在线网址: https://utf-8.jp/public/aaencode.html

使用jjencode工具的结果:

在线网址: https://utf-8.jp/public/jjencode.html

通过这些工具进行混淆,虽然看起来没有什么头绪,但实际上找到规律是非常好还原的,并没有真正达到强力混淆的效果。

相关文章
|
6月前
|
数据采集 Web App开发 JavaScript
JavaScript爬虫进阶攻略:从网页采集到数据可视化
JavaScript爬虫进阶攻略:从网页采集到数据可视化
|
27天前
|
数据采集 JSON 前端开发
JavaScript逆向爬虫实战分析
JavaScript逆向爬虫实战分析
|
27天前
|
数据采集 JavaScript 前端开发
JavaScript逆向爬虫——使用Python模拟执行JavaScript
JavaScript逆向爬虫——使用Python模拟执行JavaScript
|
27天前
|
数据采集 JavaScript 前端开发
JavaScript逆向爬虫——无限debugger的原理与绕过
JavaScript逆向爬虫——无限debugger的原理与绕过
|
5天前
|
数据采集 JavaScript 前端开发
JavaScript重定向对网络爬虫的影响及处理
JavaScript重定向对网络爬虫的影响及处理
|
27天前
|
数据采集 前端开发 JavaScript
JavaScript逆向爬虫(一)
JavaScript逆向爬虫(一)
|
3月前
|
数据采集 资源调度 JavaScript
Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
【8月更文挑战第4天】Node.js 适合做高并发、I/O密集型项目、轻量级实时应用、前端构建工具、命令行工具以及网络爬虫和数据处理等项目
55 5
|
4月前
|
数据采集 JavaScript Python
【JS逆向课件:第十三课:异步爬虫】
回调函数就是回头调用的函数
|
5月前
|
数据采集 Web App开发 XML
详尽分享用Node.js写爬虫,撸羞羞的图片
详尽分享用Node.js写爬虫,撸羞羞的图片
34 0
|
5月前
|
数据采集 前端开发 JavaScript
Python爬虫技术:动态JavaScript加载音频的解析
Python爬虫技术:动态JavaScript加载音频的解析