昨天破解 JS 加(hun)密(xiao)的思路过程整理出来....一部分了... - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
imdong
V2EX    分享创造

昨天破解 JS 加(hun)密(xiao)的思路过程整理出来....一部分了...

  •  
  •   imdong
    imdong 2019-09-01 23:06:35 +08:00 2208 次点击
    这是一个创建于 2235 天前的主题,其中的信息可能已经有所发展或是发生改变。

    昨天发帖之后,有朋友说最好能说一下思路过程,
    今天就抽时间写了一些,不过还没写完,太晚了...

    另外,本人文笔实在有限,可能写的乱七八糟,还请多多指教,
    头一次写这么长的,似乎可能是干货的文章,略(hen)紧张...

    写在前头

    昨天发了一个 《某 JS 最牛加密脱壳解密破解去混淆工具。》
    有朋友说上代码不如讲一下思路,于是今天准备捋一下这个思路,顺便当整理复习了。
    需要直接解密代码的请看上一篇文章,这里只有思路与过程。

    阅读此文默认你有一定的 Javascript 基础,对于一些特性等不做解释,如有阅读困难请及时查看手册。
    另本人也是业余选手,对一些 JS 特性研究不深,仅限会用,如有错误还请多多指教.

    先讲一下我所知道的 JS 加密 /混淆有哪些

    其实我个人并不认为下面讲的这些是加密,顶多是混淆罢了,所以本文后续尽量不用加密来描述。

    eval 加密

    最常见的如 packer 等

    典型特征

    代码内使用 eval 方法,大致原理是对代码进行文本级别的抽取可复用参数进行压缩混淆。
    而使用时将混淆代码还原为字符串,然后使用 eval 运行代码。

    怎么破解

    也许在 eval 之前的代码可能确实是用算法加密了,但毫无意义,因为总是要解密。
    我们跳过他加解密的过程,直接在他最终执行代码的地方获取原文即可。
    比如直接将 eval 改为 console.log 基本可直接输出代码原文,
    如果不行或者找不到,则手动覆盖 eval 方法,然后执行代码,然后真正的代码就会从参数传进来。

    变量混淆

    例如接下来要讲的 Obfuscator(本文针对的 sojson 的混淆即由此衍生,或者是抄袭?)
    还有所谓的二次元加密?

    典型特征

    没有特征,就是一堆看不懂名字的代码,
    原理直白的说,就是把你用的变量名 /方法名替换成无规律的名称实现干扰。
    例如,你可以认为很多代码压缩自带混淆功能,因为压缩以后人肉理解比较困难。

    编译

    还有一些基于编译性质的,了解不多,不做讨论。
    我也是昨天发帖后才知道的。

    正式开始

    首先,我们需要几个工具:

    1. 一个代码编辑器,如:VS Code
    2. Chrome 浏览器,用来调试代码
    3. 需要解密的 JS 文件,我们直接以这个 sojson.v5 为准了
    4. JS 代码格式化工具,例如: https://tool.css-js.com/http://www.bejson.com/jshtml_format/
    5. 一个聪明的脑瓜子,例如:你

    格式化并整理代码

    格式完的代码,我们会看到 Unicode 编码的数据已经自动转换回来了,
    如果没有转换,可以自己考虑写代码转换,例如:

    code_str.replace(/\\x([a-f0-9]{2})/g, function (match_str, group_1) { return String.fromCharCode(parseInt(group_1, 16).toString(10)); }); 

    现在我们先逐段分析每一段代码,有些代码可能太长,会有部分截断。
    我们在翻译代码的时候,会将一些变量给命名,建议使用前缀尽量不要和后面的代码有可能相同的变量名,
    不然后续就很蛋疼了,并且重命名以后不要丢弃原来的变量名,指不定什么时候发现这个变量没替换到,
    而你又不记得是什么地方来的。

    这里,我们把变量名前缀定义为 qs_

    然后我们文章内所有用到行数的,均以格式化后的完整代码为准(jiami_format.js)。

    第一段 (定义了三个变量)

    var encode_version = "sojson.v5", brfui = "", _0x2c67 = ["VHtkTcOA", "DcOPVEB/", "UMK...o9WPA=="]; 

    这里定义了三个变量,第一个是作者的版权,第二个暂时不知道,第三个是一个 base64 编码的数组;
    一般这种数据后面很有可能用得到,变量名改成 qs_base_data 然后全局批量替换这个变量

    结果:

    var encode_version = "sojson.v5", brfui = "", qs_base_data /* _0x2c67 */ = ["VHtkTcOA", "DcOPVEB/", "UMK...o9WPA=="]; 

    然后浏览器打开一个空白页面,手动复制这段代码到控制台进去,执行,为我们接下来的调试做准备。
    或者创建一个空白页面,把这行代码放进去准备执行。

    第二段 (一个匿名函数)

    (function (_0x2ac5c, _0x3068dd) { var _0xe9c838 = function (_0x23ef01) { while (--_0x23ef01) { _0x2ac5c["push"](_0x2ac5c["shift"]()); } }; var _0x3421c5 = function () { var _0x5d77e7 = { data: { key: "cookie", value: "timeout" }, setCookie: function (_0x3c4b91, _0x3bd290, _0x5a5c5a, _0x529726) { _0x529726 = _0x529726 || {}; var _0xe0bed = _0x3bd290 + "=" + _0x5a5c5a; var _0x45867b = 0; for (var _0x45867b = 0, _0x568496 = _0x3c4b91["length"]; _0x45867b < _0x568496; _0x45867b++) { var _0x24edef = _0x3c4b91[_0x45867b]; _0xe0bed += "; " + _0x24edef; var _0x35ae72 = _0x3c4b91[_0x24edef]; _0x3c4b91["push"](_0x35ae72); _0x568496 = _0x3c4b91["length"]; if (_0x35ae72 !== !![]) { _0xe0bed += "=" + _0x35ae72; } } _0x529726["cookie"] = _0xe0bed; }, removeCookie: function () { return "dev"; }, getCookie: function (_0x475fb9, _0x248e39) { _0x475fb9 = _0x475fb9 || function (_0x34f7a4) { return _0x34f7a4; }; var _0x202cdc = _0x475fb9(new RegExp("(?:^|; )" + _0x248e39["replace"](/([.$?*|{}()[]\/+^])/g, "$1") + "=([^;]*)")); var _0x48a974 = function (_0x267c1a, _0x45329b) { _0x267c1a(++_0x45329b); }; _0x48a974(_0xe9c838, _0x3068dd); return _0x202cdc ? decodeURIComponent(_0x202cdc[1]) : undefined; } }; var _0x1dbeed = function () { var _0x4885e3 = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}"); return _0x4885e3["test"](_0x5d77e7["removeCookie"]["toString"]()); }; _0x5d77e7["updateCookie"] = _0x1dbeed; var _0x1cefc6 = ""; var _0x170183 = _0x5d77e7["updateCookie"](); if (!_0x170183) { _0x5d77e7["setCookie"](["*"], "counter", 1); } else if (_0x170183) { _0x1cefc6 = _0x5d77e7["getCookie"](null, "counter"); } else { _0x5d77e7["removeCookie"](); } }; _0x3421c5(); })(qs_base_data, 202); 

    现看这个匿名函数的入口点,传入了两个参数,qs_base_data, 202
    所以参数一的名字也可以替换了,参数二格式化前其实是一个 16 进制 0xca,这里自动转换为 10 进制了。
    我们暂且把参数二称为 qs_arg_num,再次全局替换。

    然后看方法里面第一段,定义了一个方法:

     var _0xe9c838 = function (_0x23ef01) { while (--_0x23ef01) { qs_base_data["push"](qs_base_data["shift"]()); } }; 

    然后我们会看到 qs_base_data["push"](qs_base_data["shift"]()); 这一段,
    我们记得 qs_base_data 是一个数组,["push"] 其实就是调用这个数组的方法。
    我们先全局处理一遍,把 qs_base_data["push"] 替换为 qs_base_data.push 这样更方便我们查看。
    编辑器内用正则表达式替换 ([a-zA-z0-9_]+)\[['"]([^"']+)['"]\] 替换为 $1.$2,记得勾选 .* 表示正则替换。
    替换完以后如果还能找到就继续替换,一直到找不到匹配项为止。

    虽然我们不知道这段代码是什么用途(当然,通过阅读代码,大致可以理解是把数组后面的一定数量移动到数组前面),
    我们可以找哪里调用了这个方法 _0xe9c838,好知道参数一是从哪里来的。

    我们会看到,全局只有一个地方用到了这个方法(名),_0x48a974(_0xe9c838, qs_arg_num);
    如果有多个地方用的,那么就要挨个去测试哪里真正调用的。

    继续找 _0x48a974 发现前一行定义了这个方法,内容是将参数二自增后传递给参数一。

    var _0x48a974 = function (_0x267c1a, _0x45329b) { _0x267c1a(++_0x45329b); }; 

    我们就知道参数一是最开始入口点的 qs_arg_num,给对应的方法起名。
    最终相关代码应该是这样的。

    var qs_arr_move /* _0xe9c838 */ = function (qs_arg_num /* _0x23ef01 */) { while (--qs_arg_num) { qs_base_data.push(qs_base_data.shift()); } }; var call_fun /* _0x48a974 */ = function (callback_fun /* _0x267c1a */, qs_arg_num /* _0x45329b */) { callback_fun(++qs_arg_num); }; call_fun(qs_arr_move, qs_arg_num); 

    到了这一步,我现在接下来的动作有两个不同的方向:

    1. 继续寻找这个方法是从哪里调用的
    2. 回到最开始,按解释器角度继续阅读代码

    我这里选择按解释器来执行代码,我们回到最开始的 qs_arr_move 方法后面。
    后面又定义了一个 _0x3421c5 的方法,并且紧接着执行了这个方法。
    所以,我们可以看下这个方法内部做了什么,由于没有参数,直接进入方法第一行往下看。
    在方法内,又定义了一个 _0x5d77e7 的对象,并且定义了 datasetCookieremoveCookiegetCookie 几个方法,
    我们先给这个对象(_0x5d77e7)起名,就叫他 qs_obj_cookie 吧。
    由于并没有执行,我们先不管,继续往下...

    var _0x1dbeed = function () { var _0x4885e3 = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+[|\"];? *}"); return _0x4885e3.test(qs_obj_cookie.removeCookie.toString()); }; qs_obj_cookie.updateCookie = _0x1dbeed; var _0x170183 = qs_obj_cookie.updateCookie(); 

    后面定义了 _0x1dbeed 这个方法,并赋值给了 qs_obj_cookie.updateCookie,所以我们叫他 qs_updateCookie 吧。
    然后只定义了一个空变量后,就执行了这个方法并保存返回值到 _0x170183
    我们回过头阅读下这个 updateCookie 里面到底做了啥。
    定义一个正则表达式,并把 removeCookie 方法转换为字符串进行匹配。
    代码拿到浏览器控制台执行,

    var removeCookie = function () { return "dev"; } var _0x1dbeed = function () { var _0x4885e3 = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}"); return _0x4885e3.test(removeCookie.toString()); } _0x1dbeed(); 

    我们看到最终执行的是,并返回了 false

    /\w+ *\(\) *{\w+ *['|"].+['|"];? *}/.test('function () { return "dev"; }'); 

    但是,这里有一个坑,如果熟悉正则表达式的朋友应该会发现,这个正则表达式不匹配换行,
    而我们 toString 得到的方法字符串是带有换行的,因为我们格式化过了,
    如果不熟的话,也应该有一个警觉,removeCookie 方法本身没有多大实际意义,
    然后这里用正则表达式去匹配一个方法是什么目的?
    其实,这个代码就是用来防格式化的,因为格式化以后就会返回 false,我们拿未格式化的代码进去就会返回 true
    先不管,我们知道这里应该为 true 就好,继续往下执行,

    下面是一个 if 判断,用来判断刚才返回的是 true 还是 false,我们实际是 true,直接进入对应条件。

    _0x1cefc6 = qs_obj_cookie.getCookie(null, "counter");

    回到 getCookie 的定义,我们看下都干了啥。

    getCookie: function (_0x475fb9, _0x248e39) { _0x475fb9 = _0x475fb9 || function (_0x34f7a4) { return _0x34f7a4; }; var _0x202cdc = _0x475fb9(new RegExp("(?:^|; )" + _0x248e39.replace(/([.$?*|{}()[]\/+^])/g, "$1") + "=([^;]*)")); var call_fun /* _0x48a974 */ = function (callback_fun /* _0x267c1a */, qs_arg_num /* _0x45329b */) { callback_fun(++qs_arg_num); }; call_fun(qs_arr_move, qs_arg_num); return _0x202cdc ? decodeURIComponent(_0x202cdc[1]) : undefined; } 

    先给参数一赋值一个方法,方法直接把参数返回...
    所以,参数一的名字有了,qs_fun_back 真的很随意的起了名字...
    参数二是一个字符串,就先起名叫 qs_name_str 吧...

    紧接着往下面看,定义了一个 _0x202cdc 变量并赋值了一个正则表达式然后在最末尾返回了,
    而最外面调用 getCookie 的地方虽然保存到了 _0x1cefc6,但并没有任何地方使用,暂时先忽略这里的代码,继续往下看。

    这里就会看到我们最开始调用 qs_arr_move 的地方了,然后整个大的方法就执行完了。

    总结一下这个方法做了什么:

    1. 尝试判断代码是否被格式化
    2. 如果没有格式化,就去移动数组的元素。

    我们直接在浏览器控制台执行最终有意义的代码:

    var qs_arr_move /* _0xe9c838 */ = function (qs_arg_num /* _0x23ef01 */) { while (--qs_arg_num) { qs_base_data.push(qs_base_data.shift()); } }; qs_arr_move(qs_base_data, 203 /* ++qs_arg_num */); 

    实际理解代码,就是把数组从某个位置截断,前面的附加在后面。

    arr = [].concat(arr.splice(202 % arr.length, arr.length - (202 % arr.length)) arr); 

    第三段 (又一个匿名函数)

    由于上面已经讲了一些调试的方法,所以这里可能会跳过一些和上面相似但可能重要的流程。

    继续看,先定义了一个方法,叫_0xdf0e ,然后全局搜索这个方法名,发现有很多处调用,这里先找到第一个调用作为理解。
    是在一个定时器里面循环调用的,先不管定时器做了啥,我们先拿着参数回到方法看方法要干啥。

    setInterval(function () { var _0x346bb8 = { gHwtC: function _0x4f689e(_0x2f31c2) { return _0x2f31c2(); } }; _0x346bb8[_0xdf0e("0x0", "*naR")](_0x10c488); }, 4e3); 

    在方法体内寻找这两个参数在什么地方使用,相关关键代码如下:

    _0x532a72 = _0x532a72 - 0; var _0x44d30c = qs_base_data[_0x532a72]; _0x44d30c = _0xdf0e.rc4(_0x44d30c, _0x422b57); 

    方法最开始对参数一 _0x532a72(0x0) -0 操作,实际就是将 16 进制的字符串转为十进制数值。
    然后又从 qs_base_data 取出一个成员,最后放进 rc4 里面,根据理解,应该是取出密文用密钥解密。
    那么,参数分别就起名 qs_arg_idqs_arg_str,相关变量为 qs_data_item
    为了保险起见,我们可以继续看下后面的代码都做了啥(其实上面的代码就是所有需要的了,其他的都是多余的“花指令”)

    首先在对象未初始化时,获取 /创建 windowatob(base64 decode)
    然后创建 _0x481c88 方法赋值给 _0xdf0e.rc4 = _0x481c88;
    就是所谓的 rc4 解密了。
    初始化后,再次获取了 var qs_data_item2 /* _0x5597dd */ = _0xdf0e.data[qs_arg_id];

    然后,关键代码有这样一段(已经整理后的代码)

    var qs_data_item2 /* _0x5597dd */ = _0xdf0e.data[qs_arg_id]; if (qs_data_item2 === undefined) { if (_0xdf0e.Once=== undefined) { var qs_rc4_state /* _0xccf703 */ = function (rc4Bytes /* _0x499f89 */) { this.rc4Bytes = rc4Bytes; this.states = [1, 0, 0]; this.newState = function () { return "newState"; }; this.firstState = "\\w+ *\\(\\) *{\\w+ *"; this.secOndState= "['|\"].+['|\"];? *}"; }; //... new qs_rc4_state(_0xdf0e).checkState(); _0xdf0e.Once= !![]; } qs_data_item = _0xdf0e.rc4(qs_data_item, qs_arg_str); _0xdf0e.data[qs_arg_id] = qs_data_item; } else { qs_data_item = qs_data_item2; } 

    他把至今没有定义名字的 _0xdf0e 传递给 qs_rc4_state 并保存在了 rc4Bytes
    所以我们也可以把这个变量 _0xdf0e 定义为 rc4Bytes 了。

    继续跟代码,和上面 cookies 那一段几乎一样目的的代码,还是判断代码是否格式化。
    如果弄不清楚代码执行结果,就把代码放到浏览器执行一遍就可以了。

    var reg_ex = new RegExp("\\w+ *\\(\\) *{\\w+ *" + "['|\"].+['|\"];? *}"); var newState = function(){return'newState';}; // 这里不要格式化,因为就是测试她是否格式化的 var states = [1, 0, 0]; reg_ex.test(newState.toString()) ? --states[1] : --states[0]; 

    这个代码最后会输出 -1,然后进入 runState 方法

    qs_rc4_state.prototype.runState = function (_0x24db9c) { if (!Boolean(~_0x24db9c)) { return _0x24db9c; } return this.getState(this.rc4Bytes); }; 

    !Boolean(~-1) 返回 true 所以这里就返回了,没有后续。
    而如果代码被格式化了,后续的代码则是进入死循环...

    跳出这个方法,我们看后续又做了什么,

    var qs_data_item2 /* _0x5597dd */ = qs_rc4Bytes.data[qs_arg_id]; if (qs_data_item2 === undefined) { if (qs_rc4Bytes.Once=== undefined) { // 防止代码格式化的代码 } qs_data_item = qs_rc4Bytes.rc4(qs_data_item, qs_arg_str); qs_rc4Bytes.data[qs_arg_id] = qs_data_item; } else { qs_data_item = qs_data_item2; } return qs_data_item; 

    最终执行的意思即是:

    1. qs_base_data 取出密文并根据调用时的密钥使用 rc4 解密,
    2. 然后保存到自身的解密结果数组中
    3. 下次这直接使用解密后的结果
    4. 返回解密的结果

    PS:其实这也是他的代码中最核心的一部分代码了,其他的都是围绕这个功能打掩护而已。

    我们将最终整理出来的核心代码放入浏览器执行,用以后续代码的理解。

    var qs_base_data = ["VHtkTcOA", "DcOPVEB/", "UMKdX8KJ", "SsOnD3DDtQ==", "wolyw6h7QA==", "DlVDwp0=", "w6LCvBIsw6U=", "a8OcBkHDng==", "V8OeWyjDvQ==", "woAMIcKxMg==", "MBVaGsO5", "wr98woZiw6s=", "bho7", "NMOJdivCu1nDmCHCg8KBw4HDqy0=", "YsOKwrvCpk3DqMOgwq1N", "wpVhDsKkw6ws", "JErDp8KOJcKLw7Fh", "DMO/w6dSE0DDmk3CihTCmMKtw7g=", "6L+15pum5LuZ5LuI5Lqg57CV5Ym4B8Oc5pKR5L2h44Cs", "56qx6ZS45o6U6aqB57iwFOKBpmDDo+WLkOWsouKCicOX5ZCYw6nigKIpYeikhOWskOKDvMK7776t5L2T5Y6m5L+t55u3XsOlw67jgbE=", "5aaB5pyP5oKq55uuw6c36Yel5bev5aeL5LugdMK3WO+8pnY1DOahqeesne+9p+ethuetvuWGqeS6pOmciGQ1wrTDoBDCgMKiXMO/I+eYveS7r+egtO+/vOiup+aPl+WNsuWGpeafpeWHleWIreWvleODhOi8n+S7vOW3heWGu+S5tOiCheWIvuWunnDCsQ3jgrhzKMOX56+45quw54uK5Yah5ayU", "U8KCCSTCtw==", "SEB6ccOl", "dsOwwqrCi0c=", "ZcOXwq/ClhA=", "bBtxw4DDjw==", "KcOhwr5Qwps=", "wolww5BvcA==", "w7/Ds0TDhcOd", "Z8Kmw60Rw6Q=", "SsOvwrnCqiQ=", "E8OAdnZ6", "IVDDp8KePsKOw7s=", "wp3DnEzCoS3CrBk=", "JsOYw6vCvxzDvcK4wrQbw6Ekw6JrwpbDhMKdMQ==", "w6AmGMKMwqU=", "JMOGcEZt", "wrfCgHlh", "woHDv8OkQcKFJzcpw70=", "V19x", "S8OnwpFZw4M=", "woUbKcKqJQ==", "acKTw5oH", "w7N/eHbCrQ==", "wqJgXQvCtw==", "wpfDt8OrTcKB", "w6Eqwp8AVU5H", "WMK7wrjCgcOF", "dMOLwrHCsETDrcOr", "w65jf3o=", "O8OQw5LChUvCtcOo", "wOnDklDCvA==", "fcKdw4Yaw5rDtcK2", "w4TCmcOp", "TcO6wo1Fw57CmsOS", "YMONJzLDu1HCgjLDmQ==", "w7EEw4fDmMOh", "OMOuwrhhKitT", "M8OTflp+", "wplxw5lCUg==", "w5c9w5TDhcOp", "dsOTHiDDuA==", "wqloEcKKw6k=", "RcONM8Oow5U=", "S8KBw5gfw7E=", "wqYyH8KyMQ==", "w6vCoycLw78=", "McOgTX5f", "Ul5wdw==", "wqlNw7ZsVg==", "TcKtwrDCjMOGwoE=", "w7DCgcKpRRU=", "wo1iFsKvw6Y2Cg==", "w6HDi0LDqcOY", "wppyw6HDjznDqsKswooL", "w7nCvD0Uw6nCnQEVLw==", "dsOIwrrCsV8=", "5Lma6IGL5Yi86ZmLasKzcGATw7wbwpcp", "K8O6wq1Uwo8t", "wqFjw5RRZ8ON", "a8Otwptiw6A=", "EFdWwo/CtA==", "AAVow4vDiQ==", "T8O2wqZcw4U=", "w51lQVbCvg==", "worDn8O2bMKx", "E8OYwpB+Ew==", "esO7wq16dA==", "c8OEOXzDjw==", "WcKrw5stw5Q=", "ecOVKMOgwoY=", "w7MmAcKCwrQ=", "wrLDo04Nw4M1w7XDt8KG", "CUBcwoDCkA==", "wqBpw5kYZsOBQMK0w5nCoHnCjsOHZw==", "w6luN3LCpsKcwqBBw7E4w4Y=", "e8OpwqPCkxvCiQ==", "cMOjbgPDmQ==", "w7LDuggUwoU6wqM=", "acO8awQ=", "Bi5hw5bDnEw=", "GiZfPcO4WQ==", "wpbCkMOOw63CtcOSKMKzw4TDssOSw7kmDxop", "V3ViwqbCqMKEXzXCrsOXwqbDuMOvQ3VDUn/DtjXDnmN2H8Kawo0Ew5liEMOww63CqcOfL8K6CMObwqzCkcO5w53CiMKoAhzCh8KldD5QYVbDt3pVVQLCuGE0ew==", "R8O7wopC", "YMO7YwPDgw==", "w6PCvScSw7I=", "woAOO8KzOw==", "w5sNOMKVwp8=", "cyY4wqI4", "NMOlwo9WPA=="]; var qs_arr_move = function (qs_arg_num) { while (--qs_arg_num) { qs_base_data.push(qs_base_data.shift()); } }; qs_arr_move(203); var qs_rc4Bytes = function (qs_arg_id, qs_arg_str) { qs_arg_id = qs_arg_id - 0; var qs_data_item = qs_base_data[qs_arg_id]; if (qs_rc4Bytes.initialized === undefined) { qs_rc4Bytes.rc4 = function (_0x8f6596, _0x40168c) { var _0x155004 = [], _0x132490 = 0, _0x55539c, _0x432c06 = "", _0x4c5e8c = ""; _0x8f6596 = atob(_0x8f6596); for (var _0x330500 = 0, _0x16d6ad = _0x8f6596.length; _0x330500 < _0x16d6ad; _0x330500++) { _0x4c5e8c += "%" + ("00" + _0x8f6596.charCodeAt(_0x330500)["toString"](16))["slice"](-2); } _0x8f6596 = decodeURIComponent(_0x4c5e8c); for (var _0x163351 = 0; _0x163351 < 256; _0x163351++) { _0x155004[_0x163351] = _0x163351; } for (_0x163351 = 0; _0x163351 < 256; _0x163351++) { _0x132490 = (_0x132490 + _0x155004[_0x163351] + _0x40168c.charCodeAt(_0x163351 % _0x40168c.length)) % 256; _0x55539c = _0x155004[_0x163351]; _0x155004[_0x163351] = _0x155004[_0x132490]; _0x155004[_0x132490] = _0x55539c; } _0x163351 = 0; _0x132490 = 0; for (var _0x4ebeb6 = 0; _0x4ebeb6 < _0x8f6596.length; _0x4ebeb6++) { _0x163351 = (_0x163351 + 1) % 256; _0x132490 = (_0x132490 + _0x155004[_0x163351]) % 256; _0x55539c = _0x155004[_0x163351]; _0x155004[_0x163351] = _0x155004[_0x132490]; _0x155004[_0x132490] = _0x55539c; _0x432c06 += String.fromCharCode(_0x8f6596.charCodeAt(_0x4ebeb6) ^ _0x155004[(_0x155004[_0x163351] + _0x155004[_0x132490]) % 256]); } return _0x432c06; }; qs_rc4Bytes.data = {}; qs_rc4Bytes.initialized = true; } var qs_data_item2 = qs_rc4Bytes.data[qs_arg_id]; if (qs_data_item2 === undefined) { qs_data_item = qs_rc4Bytes.rc4(qs_data_item, qs_arg_str); qs_rc4Bytes.data[qs_arg_id] = qs_data_item; } else { qs_data_item = qs_data_item2; } return qs_data_item; }; 

    然后调用第一个 qs_rc4Bytes("0x0", "*naR") 进行测试,得到 gHwtC
    定时器的目的大概是定时调用一个方法,对应方法的定义在下面...

    由于后面的代码很多都是被加密的,所以写个 JS 脚本批量替换后面加密的数据为原文

    # 这里的 js_str 就是 160 行 之后的所有代码(字符串) var js_str = $('#compressor-textarea').val(), // 此文代码格式页面的文本框 reg_ex = /qs_rc4Bytes\(['"](0x[0-9a-f]+)['"],\s*['"]([^"']+)['"]\)/g; js_str = js_str.replace(reg_ex, function(match_str, str, key){ return '"' + qs_rc4Bytes(str, key) + '"'; }) // 再次去除数组调用的对象方法 .replace(/([a-zA-z0-9_]+)\[['"]([^"']+)['"]\]/g, function(match_str, str, key) { return str + '.' + key; }); $('#compressor-textarea').val(js_str); 

    替换后的代码存于 jiemi.js

    今天不早了,就先解析到这里,后续的代码,改天抽时间继续分析。

    赶紧洗干净了去玩游戏去~~

    作者原文及相关附件:青石坞- [原创] 记录一次 JS 解密去混淆的经历 -- 如何破解加密的 JS 代码(一)

    10 条回复    2019-09-03 16:16:59 +08:00
    murmur
        1
    murmur  
       2019-09-01 23:11:41 +08:00
    学到了,居然还有利用语句检测格式化的方法
    the7
        2
    the7  
       2019-09-02 00:33:22 +08:00 via iPhone
    楼主厉害,我有几个项目都用 sojson 来混淆的,其实我也一直好奇 sojson,一边说是最牛加密,一边又承接解密单子赚钱,很矛盾啊。
    jason94
        3
    jason94  
       2019-09-02 10:10:06 +08:00
    腻害了
        4
    yuuko  
       2019-09-02 10:11:34 +08:00 via Android
    有意思
    imdong
        5
    imdong  
    OP
       2019-09-02 12:22:26 +08:00
    @the7 就是没底线...
    codehz
        6
    codehz  
       2019-09-02 12:38:23 +08:00
    防破解最佳方法:
    只要你代码写的足够烂,不会有人来破解的!
    * 副作用:也不会有人来用的
    smilev587
        7
    smilev587  
       2019-09-02 17:45:55 +08:00
    最原始的 js 在哪里呢 想跟着你的逻辑走一遍 但是发现不看最原始的 js 跟不上你的思路
    imdong
        8
    imdong  
    OP
       2019-09-02 18:46:37 +08:00
    @smilev587 文章末尾有博客链接,博客末尾有下载地址。
    ragnaroks
        9
    ragnaroks  
       2019-09-02 19:52:53 +08:00
    牛逼
    piloots
        10
    piloots  
       2019-09-03 16:16:59 +08:00
    var _0x57c0=['\x61\x51\x42\x70\x44','\x52\x52\x77\x6d\x45','\x72\x65\x74\x75\x72\x6e\x20\x28\x66\x75\x6e\x63\x74\x69\x6f\x6e\x28\x29\x20','\x78\x47\x4f\x5a\x62','\x6f\x43\x49\x42\x78','\x6c\x4b\x58\x61\x79','\x67\x61\x50\x4b\x62','\x67\x79\x79\x77\x74','\x73\x68\x5a\x54\x4a','\x63\x78\x4e\x6c\x4b','\x30\x7c\x32\x7c\x31\x7c\x34\x7c\x33\x7c\x36\x7c\x35','\x65\x79\x41\x4e\x49','\x7b\x7d\x2e\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72\x28\x22\x72\x65\x74\x75\x72\x6e\x20\x74\x68\x69\x73\x22\x29\x28\x20\x29','\x63\x6f\x6e\x73\x6f\x6c\x65','\x30\x7c\x36\x7c\x37\x7c\x38\x7c\x35\x7c\x33\x7c\x31\x7c\x32\x7c\x34','\x73\x70\x6c\x69\x74','\x65\x78\x63\x65\x70\x74\x69\x6f\x6e','\x65\x72\x72\x6f\x72','\x6c\x6f\x67','\x77\x61\x72\x6e','\x64\x65\x62\x75\x67','\x69\x6e\x66\x6f','\x68\x65\x6c\x6c\x6f','\x5a\x6e\x58\x57\x45','\x5c\x2b\x5c\x2b\x20\x2a\x28\x3f\x3a\x5f\x30\x78\x28\x3f\x3a\x5b\x61\x2d\x66\x30\x2d\x39\x5d\x29\x7b\x34\x2c\x36\x7d\x7c\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x5b\x61\x2d\x7a\x30\x2d\x39\x5d\x7b\x31\x2c\x34\x7d\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x29','\x61\x74\x41\x70\x57','\x75\x74\x4e\x55\x78','\x64\x4d\x55\x61\x6d','\x41\x65\x54\x6a\x49','\x71\x59\x59\x57\x72','\x65\x47\x70\x54\x41','\x77\x72\x62\x78\x4b','\x73\x74\x72\x69\x6e\x67','\x77\x68\x69\x6c\x65\x20\x28\x74\x72\x75\x65\x29\x20\x7b\x7d','\x6a\x4b\x73\x77\x4b','\x76\x70\x4d\x68\x6f','\x45\x44\x55\x42\x51','\x65\x50\x61\x57\x56','\x77\x51\x4f\x7a\x55','\x72\x68\x6d\x47\x67','\x4b\x69\x4c\x46\x54','\x68\x41\x41\x6b\x6c','\x65\x69\x6a\x4a\x66','\x57\x66\x71\x56\x6e','\x56\x54\x6c\x67\x4c','\x46\x41\x73\x57\x6e','\x50\x4e\x66\x52\x72','\x48\x79\x4d\x6d\x44','\x43\x68\x56\x51\x77','\x74\x56\x53\x4b\x77','\x67\x76\x75\x6f\x6f','\x6d\x53\x4f\x76\x57','\x4e\x74\x58\x41\x6e','\x43\x68\x54\x57\x58','\x63\x64\x48\x45\x55','\x63\x4e\x4b\x56\x4e','\x6e\x4f\x49\x4e\x76','\x51\x44\x64\x41\x6c','\x65\x6e\x49\x63\x43','\x63\x61\x6c\x6c','\x4f\x62\x4a\x55\x73','\x74\x4d\x6a\x43\x65','\x6c\x6c\x4e\x6d\x64','\x56\x56\x6b\x4b\x62','\x4a\x77\x7a\x77\x44','\x7a\x66\x45\x66\x49','\x53\x57\x7a\x70\x59','\x72\x68\x79\x59\x65','\x61\x70\x70\x6c\x79','\x67\x67\x65\x72','\x73\x74\x61\x74\x65\x4f\x62\x6a\x65\x63\x74','\x74\x77\x4c\x7a\x45','\x62\x6f\x58\x63\x4d','\x48\x63\x5a\x63\x57','\x69\x6e\x69\x74','\x42\x69\x49\x46\x70','\x69\x6e\x70\x75\x74','\x78\x4f\x45\x54\x4f','\x6d\x54\x4c\x50\x53','\x50\x51\x79\x6b\x66','\x5a\x49\x63\x49\x47','\x64\x65\x62\x75','\x6c\x42\x54\x45\x46','\x47\x48\x76\x45\x56','\x66\x75\x6e\x63\x74\x69\x6f\x6e\x20\x2a\x5c\x28\x20\x2a\x5c\x29','\x74\x65\x73\x74','\x63\x68\x61\x69\x6e','\x46\x6a\x53\x71\x69','\x4a\x43\x69\x47\x77','\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72','\x66\x4a\x4f\x74\x6a','\x74\x55\x6e\x46\x6e'];(function(_0x51d2ca,_0x4993fc){var _0x4a6a10=function(_0xe3b9c2){while(--_0xe3b9c2){_0x51d2ca['push'](_0x51d2ca['shift']());}};var _0x496394=function(){var _0xc1925f={'data':{'key':'cookie','value':'timeout'},'setCookie':function(_0x16ebae,_0x459295,_0x2aff55,_0x1a1a17){_0x1a1a17=_0x1a1a17||{};var _0x341188=_0x459295+'='+_0x2aff55;var _0x140492=0x0;for(var _0x140492=0x0,_0x1ec865=_0x16ebae['length'];_0x140492<_0x1ec865;_0x140492++){var _0x475a45=_0x16ebae[_0x140492];_0x341188+=';\x20'+_0x475a45;var _0x975b0c=_0x16ebae[_0x475a45];_0x16ebae['push'](_0x975b0c);_0x1ec865=_0x16ebae['length'];if(_0x975b0c!==!![]){_0x341188+='='+_0x975b0c;}}_0x1a1a17['cookie']=_0x341188;},'removeCookie':function(){return'dev';},'getCookie':function(_0x227855,_0x1430dd){_0x227855=_0x227855||function(_0x354b4b){return _0x354b4b;};var _0x31a44a=_0x227855(new RegExp('(?:^|;\x20)'+_0x1430dd['replace'](/([.$?*|{}()[]\/+^])/g,'$1')+'=([^;]*)'));var _0x565da5=function(_0x10a81b,_0x3d15c5){_0x10a81b(++_0x3d15c5);};_0x565da5(_0x4a6a10,_0x4993fc);return _0x31a44a?decodeURIComponent(_0x31a44a[0x1]):undefined;}};var _0xaa6804=function(){var _0x4f2983=new RegExp('\x5cw+\x20*\x5c(\x5c)\x20*{\x5cw+\x20*[\x27|\x22].+[\x27|\x22];?\x20*}');return _0x4f2983['test'](_0xc1925f['removeCookie']['toString']());};_0xc1925f['updateCookie']=_0xaa6804;var _0x3a7ecc='';var _0x4369a1=_0xc1925f['updateCookie']();if(!_0x4369a1){_0xc1925f['setCookie'](['*'],'counter',0x1);}else if(_0x4369a1){_0x3a7ecc=_0xc1925f['getCookie'](null,'counter');}else{_0xc1925f['removeCookie']();}};_0x496394();}(_0x57c0,0x151));var _0x60fd=function(_0x2f3e31,_0x3a89c9){_0x2f3e31=_0x2f3e31-0x0;var _0x2c6db2=_0x57c0[_0x2f3e31];return _0x2c6db2;};var _0x39002d=function(){var _0x496858=!![];return function(_0x3e59,_0x3558cd){var _0x40e70c=_0x496858?function(){if(_0x3558cd){var _0x1109a4=_0x3558cd['apply'](_0x3e59,arguments);_0x3558cd=null;return _0x1109a4;}}:function(){};_0x496858=![];return _0x40e70c;};}();var _0x128a5f=_0x39002d(this,function(){var _0xe16871=function(){return'\x64\x65\x76';},_0x287874=function(){return'\x77\x69\x6e\x64\x6f\x77';};var _0x57aa29=function(){var _0x3b2938=new RegExp('\x5c\x77\x2b\x20\x2a\x5c\x28\x5c\x29\x20\x2a\x7b\x5c\x77\x2b\x20\x2a\x5b\x27\x7c\x22\x5d\x2e\x2b\x5b\x27\x7c\x22\x5d\x3b\x3f\x20\x2a\x7d');return!_0x3b2938['\x74\x65\x73\x74'](_0xe16871['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var _0x55927e=function(){var _0x1b8418=new RegExp('\x28\x5c\x5c\x5b\x78\x7c\x75\x5d\x28\x5c\x77\x29\x7b\x32\x2c\x34\x7d\x29\x2b');return _0x1b8418['\x74\x65\x73\x74'](_0x287874['\x74\x6f\x53\x74\x72\x69\x6e\x67']());};var _0x11caa5=function(_0x2ce76b){var _0x7b7a15=~-0x1>>0x1+0xff%0x0;if(_0x2ce76b['\x69\x6e\x64\x65\x78\x4f\x66']('\x69'===_0x7b7a15)){_0x37f731(_0x2ce76b);}};var _0x37f731=function(_0x334972){var _0x4a3c16=~-0x4>>0x1+0xff%0x0;if(_0x334972['\x69\x6e\x64\x65\x78\x4f\x66']((!![]+'')[0x3])!==_0x4a3c16){_0x11caa5(_0x334972);}};if(!_0x57aa29()){if(!_0x55927e()){_0x11caa5('\x69\x6e\x64\u0435\x78\x4f\x66');}else{_0x11caa5('\x69\x6e\x64\x65\x78\x4f\x66');}}else{_0x11caa5('\x69\x6e\x64\u0435\x78\x4f\x66');}});_0x128a5f();var _0x3cb7e7=function(){var _0x2f22dd={};_0x2f22dd[_0x60fd('0x0')]=function(_0x3943cb,_0x3691ea){return _0x3943cb!==_0x3691ea;};_0x2f22dd[_0x60fd('0x1')]=_0x60fd('0x2');_0x2f22dd[_0x60fd('0x3')]=_0x60fd('0x4');var _0x273ea0=!![];return function(_0x3a7e5c,_0x59b5d2){var _0x415570={};_0x415570[_0x60fd('0x5')]=function(_0x3b5d80,_0x18b7ba){return _0x2f22dd.tMjCe(_0x3b5d80,_0x18b7ba);};_0x415570[_0x60fd('0x6')]=_0x2f22dd.llNmd;_0x415570['\x4a\x59\x4f\x50\x47']=_0x2f22dd.JwzwD;var _0x1ff68b=_0x273ea0?function(){if(_0x59b5d2){if(_0x415570[_0x60fd('0x5')](_0x415570[_0x60fd('0x6')],_0x415570['\x4a\x59\x4f\x50\x47'])){var _0x282e81=_0x59b5d2[_0x60fd('0x7')](_0x3a7e5c,arguments);_0x59b5d2=null;return _0x282e81;}else{var _0x495047=_0x59b5d2[_0x60fd('0x7')](_0x3a7e5c,arguments);_0x59b5d2=null;return _0x495047;}}}:function(){};_0x273ea0=![];return _0x1ff68b;};}();(function(){var _0xc492b={};_0xc492b['\x44\x61\x61\x49\x79']=_0x60fd('0x8');_0xc492b['\x58\x71\x48\x6d\x53']=_0x60fd('0x9');_0xc492b[_0x60fd('0xa')]='\x5c\x2b\x5c\x2b\x20\x2a\x28\x3f\x3a\x5f\x30\x78\x28\x3f\x3a\x5b\x61\x2d\x66\x30\x2d\x39\x5d\x29\x7b\x34\x2c\x36\x7d\x7c\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x5b\x61\x2d\x7a\x30\x2d\x39\x5d\x7b\x31\x2c\x34\x7d\x28\x3f\x3a\x5c\x62\x7c\x5c\x64\x29\x29';_0xc492b[_0x60fd('0xb')]=function(_0x526f3e,_0x10f48f){return _0x526f3e(_0x10f48f);};_0xc492b[_0x60fd('0xc')]=_0x60fd('0xd');_0xc492b[_0x60fd('0xe')]=function(_0x951c56,_0x456e6c){return _0x951c56+_0x456e6c;};_0xc492b['\x46\x6a\x53\x71\x69']=_0x60fd('0xf');_0xc492b['\x4a\x43\x69\x47\x77']=_0x60fd('0x10');_0xc492b[_0x60fd('0x11')]=_0x60fd('0x12');_0xc492b[_0x60fd('0x13')]=function(_0x2df886){return _0x2df886();};_0xc492b['\x41\x50\x77\x58\x73']=function(_0x4c884d,_0x368328,_0x57198a){return _0x4c884d(_0x368328,_0x57198a);};_0xc492b['\x41\x50\x77\x58\x73'](_0x3cb7e7,this,function(){var _0x187f7f={};_0x187f7f['\x66\x4a\x4f\x74\x6a']=_0x60fd('0x14');_0x187f7f[_0x60fd('0x15')]=_0xc492b.DaaIy;_0x187f7f[_0x60fd('0x16')]=_0xc492b.XqHmS;var _0x2e8cc1=new RegExp(_0x60fd('0x17'));var _0x433329=new RegExp(_0xc492b['\x74\x77\x4c\x7a\x45'],'\x69');var _0x5ebe82=_0xc492b[_0x60fd('0xb')](_0x8a8378,_0xc492b[_0x60fd('0xc')]);if(!_0x2e8cc1[_0x60fd('0x18')](_0xc492b[_0x60fd('0xe')](_0x5ebe82,_0x60fd('0x19')))||!_0x433329[_0x60fd('0x18')](_0x5ebe82+_0xc492b[_0x60fd('0x1a')])){if(_0xc492b[_0x60fd('0x1b')]===_0xc492b['\x6d\x54\x4c\x50\x53']){(function(){return![];}[_0x60fd('0x1c')](_0x187f7f[_0x60fd('0x1d')]+_0x187f7f[_0x60fd('0x15')])['\x61\x70\x70\x6c\x79'](_0x187f7f[_0x60fd('0x16')]));}else{_0x5ebe82('\x30');}}else{_0xc492b['\x5a\x49\x63\x49\x47'](_0x8a8378);}})();}());var _0x4cc17b=function(){var _0x1e704f=!![];return function(_0x26add2,_0x43ed97){var _0x1736fd=_0x1e704f?function(){if(_0x43ed97){var _0x1424c3=_0x43ed97['\x61\x70\x70\x6c\x79'](_0x26add2,arguments);_0x43ed97=null;return _0x1424c3;}}:function(){};_0x1e704f=![];return _0x1736fd;};}();var _0x3850dc=_0x4cc17b(this,function(){var _0x341c50={};_0x341c50[_0x60fd('0x1e')]=function(_0x116b8c,_0x3d5da0){return _0x116b8c(_0x3d5da0);};_0x341c50['\x65\x79\x41\x4e\x49']=function(_0x42886f,_0x1ae1de){return _0x42886f+_0x1ae1de;};_0x341c50[_0x60fd('0x1f')]=function(_0x2ca9d9,_0x45a895){return _0x2ca9d9+_0x45a895;};_0x341c50[_0x60fd('0x20')]=_0x60fd('0x21');_0x341c50[_0x60fd('0x22')]=function(_0x441f9b){return _0x441f9b();};_0x341c50['\x4a\x6a\x44\x6f\x77']=function(_0x2f4cf6,_0xbe91c1){return _0x2f4cf6!==_0xbe91c1;};_0x341c50[_0x60fd('0x23')]=_0x60fd('0x24');_0x341c50[_0x60fd('0x25')]='\x6e\x59\x73\x4d\x77';_0x341c50[_0x60fd('0x26')]=_0x60fd('0x27');_0x341c50[_0x60fd('0x28')]=_0x60fd('0x29');var _0x53f08a=function(){};var _0x1518ff;try{var _0x179976=_0x341c50['\x74\x55\x6e\x46\x6e'](Function,_0x341c50[_0x60fd('0x2a')](_0x341c50['\x61\x51\x42\x70\x44'](_0x341c50[_0x60fd('0x20')],_0x60fd('0x2b')),'\x29\x3b'));_0x1518ff=_0x341c50[_0x60fd('0x22')](_0x179976);}catch(_0x37a15c){_0x1518ff=window;}if(!_0x1518ff[_0x60fd('0x2c')]){if(_0x341c50['\x4a\x6a\x44\x6f\x77'](_0x341c50[_0x60fd('0x23')],_0x341c50['\x67\x61\x50\x4b\x62'])){_0x1518ff['\x63\x6f\x6e\x73\x6f\x6c\x65']=function(_0x53f08a){var _0x1d854e=_0x60fd('0x2d')[_0x
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4335 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 04:09 PVG 12:09 LAX 21:09 JFK 00:09
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86