首页 > 文章阅读 > 【原创】 JS 不可逆加密后半部分,去混淆还原代码。

【原创】 JS 不可逆加密后半部分,去混淆还原代码。

第二篇

111

第四段 去混淆(解密后的代码,又一段新的历程)

接下来的代码行数以解密后的 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 语法树,破解这玩意真的贼容易。

特别是替换对象属性的那段,很适合,有时间学习下。

上一篇: 【原创】记录一次 JS 解密去混淆的经历 -- 如何破解加密的 JS 代码(一)

下一篇: 一种CDK/授权码的生成算法,可离线生成,带日期信息。

最近回复

标签