第四段 去混淆(解密后的代码,又一段新的历程)
接下来的代码行数以解密后的
jiemi.js
文件为基准
第一段是一个定时器,定时器以 4000ms 的间隔调用一个 _0x10c488
方法,
里面定义了一个 Object,这个方式在后面会多次出现。
即定义一个对象,里面定义几个方法,将参数返回出来。
比如这个,gHwtC
方法里面就是调用参数一,简化后为:
setInterval(function () {
_0x10c488();
}, 4e3);
往后找一下,定义方法的地方,在第 278 行:
依然定义了一个对象 _0x5d1305
,然后定义了一个 _0x3e0578
方法,先不管,继续往后找。
一个 try 结构,判断 _0x43c6a1
也就是参数一是否有值,这里调用没有传参,直接走 else 流程。
调用了 _0x5d1305.Nktyo
方法,传递 _0x3e0578
进入。
找到 Nktyo
的定义,只是把参数二放入参数一执行,然后回头看下这个方法 _0x3e0578
怎么去混淆
第一个 if 是判断 _0x5d1305.ExxTQ(typeof _0x53a0e4, _0x5d1305.jgffP)
,找到刚才的定义,分别理解。
只是一个判断参数一是否为字符串而已,现在找到调用这个 对象属性的地方,挨个替换回来,其他的也是一样方法替换。
整个大方法处理完以后,可以把最上面的对象删除了,结果如下:
function _0x10c488(_0x43c6a1) {
function _0x3e0578(_0x53a0e4) {
if (typeof _0x53a0e4 === "string") {
var _0x3d9b38 = function () {
while (!![]) { }
};
return _0x3d9b38();
} else {
if ((("" + (_0x53a0e4 / _0x53a0e4))["length"] !== 1) || ((_0x53a0e4 % 20) === 0)) {
debugger;
} else {
debugger;
}
}
_0x3e0578(++_0x53a0e4);
}
try {
if (_0x43c6a1) {
return _0x3e0578;
} else {
_0x3e0578(0);
}
} catch (_0x29e1b1) { }
}
可以看到,这个方法是一个反调试,直接删除即可,开头的定时器也可以一并删除了。
回到开头,看第二段代,一个大的 try
, 里面定义了一个字符串拆散为数组,并死循环 switch
,
最终目的其实就是将 switch
里面的代码按照 _0xd94d8c
定义的顺序执行一遍而已,我们直接提取结果出来看下:
// case "2":
var _0x1d8312 = ["domain", "split", "reverse", "join", "search", "hr" + "ef", "random", !0];
// case "0":
var _0x1dd019 = document[_0x1d8312[0]];
// case "4":
var _0x3f8779 = function (_0x2e5797) {
return _0x2e5797[_0x1d8312[1]]("")[_0x1d8312[2]]()[_0x1d8312[3]]("");
};
// case "3":
var _0x5a0580 = function (_0xc98a64, _0x503b08) {
var _0x1dda52 = {
Scjbh: "function *\( *\)",
odYDy: "\+\+ *(?:_0x(?:[a-f0-9]){4,6}|(?:\b|\d)[a-z0-9]{1,4}(?:\b|\d))",
oKrUQ: function _0x2206d5(_0x42cced, _0x535344) {
return _0x42cced(_0x535344);
},
ZyHof: "init",
hoEKc: function _0x1b7042(_0x219f40, _0x2085aa) {
return _0x219f40 + _0x2085aa;
},
DtRMS: "chain",
ITLzH: "input",
TMYBP: function _0x4c778b(_0x5c060e, _0x4090f3) {
return _0x5c060e(_0x4090f3);
},
arjnp: function _0xa52a54(_0x57076d) {
return _0x57076d();
},
OcNch: function _0x36a26e(_0x1cc753, _0x475023, _0x5f1b96) {
return _0x1cc753(_0x475023, _0x5f1b96);
},
XRkCn: function _0x2ccfd7(_0x13e206, _0x2235db) {
return _0x13e206 === _0x2235db;
},
yWahq: function _0x425b13(_0x12e926, _0x4f260f) {
return _0x12e926(_0x4f260f);
}
};
var _0xceb034 = function () {
var _0x4236ae = !![];
return function (_0x4e3ff6, _0xc225f7) {
var _0x3d1152 = _0x4236ae ? function () {
if (_0xc225f7) {
var _0x23d38f = _0xc225f7.apply(_0x4e3ff6, arguments);
_0xc225f7 = null;
return _0x23d38f;
}
} : function () { };
_0x4236ae = ![];
return _0x3d1152;
};
}();
(function () {
_0x1dda52.OcNch(_0xceb034, this, function () {
var _0x66effa = new RegExp(_0x1dda52.Scjbh);
var _0x5b9f27 = new RegExp(_0x1dda52.odYDy, "i");
var _0x2755c6 = _0x1dda52.oKrUQ(_0x10c488, _0x1dda52.ZyHof);
if (!_0x66effa.test(_0x1dda52.hoEKc(_0x2755c6, _0x1dda52.DtRMS)) || !_0x5b9f27.test(_0x1dda52.hoEKc(_0x2755c6, _0x1dda52.ITLzH))) {
_0x1dda52.TMYBP(_0x2755c6, "0");
} else {
_0x1dda52.arjnp(_0x10c488);
}
})();
})();
return _0x1dda52.XRkCn(_0x1dda52.yWahq(_0x3f8779, _0xc98a64)[_0x1d8312[4]](_0x503b08), 0);
};
// case "1":
if (!(_0x5a0580(_0x1dd019, "moc.udiab.tset") || _0x5a0580(_0x1dd019, "nc.gnatnait"))) {
while (_0x1d8312[7]) {
location[_0x1d8312[5]] = location[_0x1d8312[5]] + "?" + Math[_0x1d8312[6]]();
}
}
依然又看到 case "3":
部分和上面是同样的计俩,替换之,然后 _0x1d8312
相关对应的数组也替换一下,
替换到 _0x5a0580
时,本来想继续这个的,到一半发现调用了刚才的 _0x10c488
反调试的代码,直接跳过,依然是反调试的混淆。
核心代码,最后一行 return qs_reverse_str(_0xc98a64).search(_0x503b08) === 0;
翻转字符串并搜索。
找到调用参数 "moc.udiab.tset"
并尝试翻转 test.baidu.com
, 这是我写的限制域名的,此段代码可以直接删除了。
// case "2":
var _0x1d8312 = ["domain", "split", "reverse", "join", "search", "href", "random", !0];
// case "0":
var qs_domain /*_0x1dd019*/ = document.domain;
// case "4":
var qs_reverse_str /* _0x3f8779 */ = function (qs_str /*_0x2e5797*/) {
return qs_str.split("").reverse().join("");
};
// case "3":
var _0x5a0580 = function (_0xc98a64, _0x503b08) {
var _0xceb034 = function () {
var _0x4236ae = !![];
return function (_0x4e3ff6, _0xc225f7) {
var _0x3d1152 = _0x4236ae ? function () {
if (_0xc225f7) {
var _0x23d38f = _0xc225f7.apply(_0x4e3ff6, arguments);
_0xc225f7 = null;
return _0x23d38f;
}
} : function () { };
_0x4236ae = ![];
return _0x3d1152;
};
}();
(function () {
_0xceb034(this, function () {
var _0x66effa = new RegExp("function *\( *\)");
var _0x5b9f27 = new RegExp("\+\+ *(?:_0x(?:[a-f0-9]){4,6}|(?:\b|\d)[a-z0-9]{1,4}(?:\b|\d))", "i");
var _0x2755c6 = _0x10c488("init");
if (!_0x66effa.test((_0x2755c6 + "chain")) || !_0x5b9f27.test((_0x2755c6 + "input"))) {
_0x2755c6("0");
} else {
_0x10c488();
}
})();
})();
return qs_reverse_str(_0xc98a64).search(_0x503b08) === 0;
};
// case "1":
if (!(_0x5a0580(qs_domain, "moc.udiab.tset") || _0x5a0580(qs_domain, "nc.gnatnait"))) {
while (!0) {
location["href"] = location["href"] + "?" + Math["random"]();
}
}
然后就剩下一大段的匿名函数,其实这个才是我一开始加密的代码。
依然看到开始定义了对象,和刚才的方法一样,替换之,同时看到了熟悉的 "1|2|0|3|4|5|6"
和死循环 switch
老样子,直接处理掉吧。
这里遇到一个新结构,这个结构折腾了很久,返回了两次对象,最终目的就是为了用参数二进行 apply
var _0x4df744 = function () {
var _0x10f531 = !![];
return function (_0x1602b0, _0x1836ce) {
var _0x183ad8 = _0x10f531 ? function () {
if (_0x1836ce) {
var _0x49cae0 = _0x1836ce.apply(_0x1602b0, arguments);
_0x1836ce = null;
return _0x49cae0;
}
} : function () { };
_0x10f531 = ![];
return _0x183ad8;
};
}();
最终结果为
var _0x4df744 = function (_0x1602b0, _0x1836ce) {
_0x1836ce.apply(_0x1602b0, arguments);
}
而定义的 _0x4aa006
方法主要目的为覆盖系统的 console
然后 console 对象所有的方法失效,主要目的依然为了反调试。
整段代码又可以删除了,(case "4"
之前的)
最后结果就只剩下这么几行了
(function (_0x503c1c, _0x5cde2d) {
// case "4":
_0x503c1c.info = "这是一个一系列js操作。";
// case "5":
_0x5cde2d.adinfo = "站长接高级 “JS加密” 和 “JS解密” ,保卫你的 js。";
// case "6":
_0x5cde2d.warning = "如果您的JS里嵌套了PHP,JSP标签,等等其他非JavaScript的代码,请提取出来再加密。这个工具不能加密php、jsp等模版内容";
})(window, document);
后面还有一个 不能删除sojson.v5
我想你懂得,我就不说了~~~
这就是解析的全部了,由于这次写的匆忙,过程中随心所欲的删除,就没有留下过程文件。
大家随意理解下就好了...
本文也是我一边查资料一边写的,又发现写 JS 真的要上 AST 语法树,破解这玩意真的贼容易。
特别是替换对象属性的那段,很适合,有时间学习下。