最初只是加载一个谷歌插件试试别人的产品,随着深入的了解,
发现其在temp缓存目录生成了一些html+css+js等文件。
插件加载使用的时候,调用了debugger,阻止用户调试。
禁止断点:
而对应的操作是在Chrome控制台
的Source Tab页
点击Deactivate breakpoints
按钮或者按下Ctrl + f8
代码加密过了,读不懂,试着找找有没有解密方法。
根据代码关键字,找打到了一些相关资料:
1.某JS最牛加密脱壳解密破解去混淆工具。
2.记录一次 JS 解密去混淆的经历 -- 如何破解加密的 JS 代码(一)
3.JS 不可逆加密后半部分,去混淆还原代码。
4.https://www.sojson.com/jsobfuscator.html
起初用别人现成的解码代码试了试,简单几句js加密的可以解密,复杂的就不行了。
然后解读了大牛的记录,还是有不少地方值得学习借鉴。
其中有一段代码:
})(qs_base_data, 202);
而大牛后来用的是:})(qs_base_data, 203);加了个一
我手上的代码是:}(qs_base_data , 0x1df));//0x1df=》479,但是这个位置要+1用480才能正确解密
其中一些 qs_base_data["push"]
替换为 qs_base_data.push
:
用正则表达式替换 ([a-zA-z0-9_]+)\[['"]([^"']+)['"]\]
替换为 $1.$2
亦或者使用html:
<textarea id="main" style="width:800px;height:500px;"></textarea>
<button onclick="decode();">decode</button>
<script>
function decode(){
var js_str = document.getElementById("main").value,
js_str = js_str = js_str.replace(/([a-z0-9\-_A-Z)\]]+)\s?\[["']([^"']+)["']\]/g, '$1.$2')
console.log(js_str);
document.getElementById("main").value=js_str;
}
</script>
值得mark的一段文字:
/\w+ *\(\) *{\w+ *['|"].+['|"];? *}/.test('function () {
return "dev";
}');
但是,这里有一个坑,如果熟悉正则表达式的朋友应该会发现,这个正则表达式不匹配换行,
而我们 toString 得到的方法字符串是带有换行的,因为我们格式化过了,
如果不熟的话,也应该有一个警觉,removeCookie 方法本身没有多大实际意义,
然后这里用正则表达式去匹配一个方法是什么目的?
其实,这个代码就是用来防格式化的,因为格式化以后就会返回 false,我们拿未格式化的代码进去就会返回 true。
先不管,我们知道这里应该为 true 就好,继续往下执行,
大牛说:其实这也是他的代码中最核心的一部分代码了,其他的都是围绕这个功能打掩护而已。
这种思路值得借鉴。
跟随大牛的脚本获取rc4解密核心代码,保存到js里面,用html调用解密并替换:
<script src="qs_rc4Bytes解密核心代码.js"></script>
qs_rc4Bytes正则替换代码
<textarea id="main" style="width:800px;height:500px;"></textarea>
<button onclick="decode();">decode</button>
<script>
function decode(){
var js_str = document.getElementById("main").value,
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;
});
console.log(js_str);
document.getElementById("main").value=js_str;
}
</script>
上面两种替换可能会执行多次,之后解读到:定义一个对象,里面定义几个方法,将参数返回出来。
类似代码:
(function() {
var _0x6a407b = {
'aZPsL': "function *\( *\)",
'uHQzr': "\+\+ *(?:_0x(?:[a-f0-9]){4,6}|(?:\b|\d)[a-z0-9]{1,4}(?:\b|\d))",
'xhQPy': function _0x488deb(_0x315164, _0x1f2bdf) {
return _0x315164(_0x1f2bdf);
},
'MDsna': "init",
'xTnfC': function _0x552557(_0x2c63cd, _0x39eb8a) {
return _0x2c63cd + _0x39eb8a;
},
'dLmnR': "chain",
'JSBMG': function _0x244b6e(_0x1b188e, _0x6d42d0) {
return _0x1b188e + _0x6d42d0;
},
'NBAFw': "input",
'EEuId': function _0x159709(_0x76b8f5, _0xe936dc) {
return _0x76b8f5(_0xe936dc);
},
'mzpVU': function _0x485ce7(_0x1aac61) {
return _0x1aac61();
},
'gYpjP': function _0x58cba9(_0xc63c83, _0x377b8c, _0x71fff7) {
return _0xc63c83(_0x377b8c, _0x71fff7);
}
};
_0x6a407b.gYpjP(_0x3d9286, this, function() {
var _0x356543 = new RegExp(_0x6a407b.aZPsL);
var _0xce30c = new RegExp(_0x6a407b.uHQzr, 'i');
var _0x1669a4 = _0x6a407b.xhQPy(_0x251ab7, _0x6a407b.MDsna);
if (!_0x356543.test(_0x6a407b.xTnfC(_0x1669a4, _0x6a407b.dLmnR)) || !_0xce30c.test(_0x6a407b.JSBMG(_0x1669a4, _0x6a407b.NBAFw))) {
_0x6a407b.EEuId(_0x1669a4, '0');
} else {
_0x6a407b.mzpVU(_0x251ab7);
}
})();
}());
这是个大工程,用js没想好怎么替换,写了个小软件来替换大部分,剩余一些传入参数是function的手动替换。
然后参照大牛的方法删了一些没用的和调试中断的代码,
最后得出的代码虽然不理想,但也大概看出他做了一些什么,
例如执行某个post,获取响应的json,并加载响应的js和css文件
chrome.storage.local.get(_0x2b44ac, function(_0x122f2f) {
fetch( _0x5678a9, {
'method': "POST",
'body': JSON.stringify({
'v': "0.0.0"
}),
'headers': new Headers({
'Content-Type': "application/json"
})
}).then(_0x87b5f2 => _0x87b5f2.json()).then(_0x230098 => {
chrome.storage.local.set(_0x230098, function() {
_0x56c2a6(_0x230098.amingtool.options);
});
}).catch(_0x179c96 => console.log(_0x179c96));
});
返回的部分json:
"options": [{
"host": "s.***.com",
"show": true,
"css": {
"remote": [],
"local": ["css/aming.css", "css/buttons.css", "css/alldatatables.min.css"]
},
"js": {
"remote": {
"in": ["https://codecdn.***.com/production/xqygj-in.js?t=2019122413"],
"out": ["https://codecdn.***.com/production/xqygj-out.js?t=2019122413"]
},
"local": {
"in": ["js/jquery.min.js", "js/buttons.js", "layui/layui.all.js", "js/layer.js"],
"out": ["js/jsencrypt.min.js", "js/crypto-js.min.js", "js/echarts.min.js", "js/alldatatables.min.js", "js/jszip.min.js", "js/FileSaver.min.js", "layui/layui.all.js"]
}
}
},...
chrome.storage API是chrome扩展特有的api,它和HTML5 LocalStorage一个显著不同是可以存取object数据。也是以键-值的形式存取。
更多了解:Chrome 存储 API,BOM 操作,js中map()方法和apply()方法的总结
function _0x56c2a6(_0x179c22) {//加载返回的结果
for (let _0x14145a = 0x0; _0x14145a < _0x179c22.length; _0x14145a++) {
if (location.hostname.includes(_0x179c22[_0x14145a].host)) {
//当前页面链接是否包含返回数据中的host
_0xd56f6e(_0x179c22[_0x14145a].css.local.map(_0x19bcc0 => chrome.extension.getURL(_0x19bcc0)));
//加载json中对应的css本地链接
_0xd56f6e(_0x179c22[_0x14145a].css.remote);
//加载json中对应的css远程链接
var _0x167ece = [];
if (_0x179c22[_0x14145a].js.local.in) {
_0x167ece = _0x167ece.concat(_0x179c22[_0x14145a].js.local.in.map(_0x4faecc => chrome.extension.getURL(_0x4faecc)));
//concat() 方法用于连接两个或多个数组。
}
if (_0x179c22[_0x14145a].js.remote.in) {
_0x167ece = _0x167ece.concat(_0x179c22[_0x14145a].js.remote.in);
}
_0x3e4911(_0x167ece);//加载js
var _0x1bdddd = [];
if (_0x179c22[_0x14145a].js.local.out) {
_0x1bdddd = _0x1bdddd.concat(_0x179c22[_0x14145a].js.local.out.map(_0x5158cf => chrome.extension.getURL(_0x5158cf)));
}
if (_0x179c22[_0x14145a].js.remote.out) {
_0x1bdddd = _0x1bdddd.concat(_0x179c22[_0x14145a].js.remote.out);
}
_0x32e45a(_0x1bdddd);
break;
}
}
}
function _0xd56f6e(_0x2eca4b) {//加载css文件,脚本文件也差不多是这样的代码
if (!_0x2eca4b) return;
for (let _0x40f483 = 0x0; _0x40f483< _0x2eca4b.length; _0x40f483++) {
var _0x59e674 = "1|4|3|0|2"["split"]('|'),
_0x3b7c24 = 0x0;
while ( !! []) {
switch (_0x59e674[_0x3b7c24++]) {
case '0':
_0x44bb71.href = _0x2eca4b[_0x40f483];
continue;
case '1':
var _0x44bb71 = document.createElement("link");
continue;
case '2':
document.head.appendChild(_0x44bb71);
continue;
case '3':
_0x44bb71.rel = "stylesheet";
continue;
case '4':
_0x44bb71.type = "text/css";
continue;
}
break;
}
}
}
for中间的代码:
var _0x44bb71 = document.createElement("link");
_0x44bb71.type = "text/css";
_0x44bb71.rel = "stylesheet";
_0x44bb71.href = _0x2eca4b[_0x40f483];
document.head.appendChild(_0x44bb71);
js的加载:
var _0x59f0d6 = _0x494982[_0x281d07];
var _0x1ac512 = document.createElement("script");
_0x1ac512.type = "text/javascript";
_0x1ac512.onload = function() {
_0x281d07++;
if (_0x281d07=== _0x494982.length) {
_0x121b7e();
} else {
_0xd09ce5(_0x281d07);
}
this.parentNode.removeChild(this);
};
_0x1ac512.src = _0x59f0d6;
_0x1ac512.charset = "UTF-8";
document.documentElement.append(_0x1ac512);
function _0x32e45a(_0x277aca) {//遍历用eval使加载的js代码生效
if (!_0x277aca) return;
Promise.all(_0x277aca.map(_0x2b20f4 => fetch(_0x2b20f4).then(_0x18b094 => _0x18b094.text()))).then(_0x2fbd73 => {
for (let _0x506656 = 0x0; _0x506656< _0x2fbd73.length; _0x506656++) {
eval( _0x2fbd73[_0x506656]);
}
});
}
理解和使用Promise.all和Promise.race:
Promse.all在处理多个异步处理时非常有用
Promise.all获得的成功结果的数组里面的数据顺序和Promise.all接收到的数组顺序是一致的,即p1的结果在前,即便p1的结果获取的比p2要晚。这带来了一个绝大的好处:在前端开发请求数据的过程中,偶尔会遇到发送多个请求并根据请求顺序获取和使用数据的场景,使用Promise.all毫无疑问可以解决这个问题。
json返回的某些js文件又是另一种加密方式,
window.tbWebpackJsonp=window.webpackJsonp,window.webpackJsonp=null,function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;"undefined"!=typeof window?e=window:"undefined"!=typeof global?e=global:"undefined"!=typeof self&&(e=self),e.JSZip=t()}}(function(){return function i(a,o,s){function l(n,t){if(!o[n]){if(!a[n]){var e="function"==typeof require&&require;if(!t&&e)return e(n,!0);if(c)return c(n,!0);throw new Error("Cannot find module '"+n+"'")}...
vue的webpack打包,还得继续研究...
有人说:
通过webpack、grunt、gulp 、 etc 的一些ugly 插件压缩打包后,会生成一个 source map的映射文件,将行映射成列的。所以只要有source map 都可以还原,没有的话,就别想了,只能格式化下。