反编译js的收获

最初只是加载一个谷歌插件试试别人的产品,随着深入的了解,

发现其在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 存储 APIBOM 操作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 都可以还原,没有的话,就别想了,只能格式化下。

发布了69 篇原创文章 · 获赞 31 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/yyws2039725/article/details/103753986