Js 逆向
目标
程序的一般流程分为三块分别是,输入、中间执行过程、输出,而逆向指的是,由输出出发反推中间执行过程和输入,所以逆向的目标
是由输出出发反推出中间执行过程和输入。
爬虫为什么要进行逆向工作
网站、app运营过程中为防止核心数据被爬虫大规模采集而孕生出了反爬了机制,而其中一种就是利用本地浏览器或者app经过若干复
杂过程生成参数,该参数可能是headers中的字段,也可能是cookie,还有可能是请求的参数部分,由于该参数难以被逆向,爬虫程
序无法提供该参数故实现反爬的目标。爬虫为了获取数据故必须逆向出该参数的实现。
js逆向工具
浏览器的开发者工具
自定义的浏览器插件
油猴插件
定位逆向起始点、回溯
逆向的第一步就要确定要逆向的参数的输出位置,这是逆向的起点,我们常用的寻找参数的输出位置有以下几种方法:
1、用浏览器的开发者工具全局搜索参数名,利用读代码、打断点等方法确定是否为参数生成处。
2、对于加密参数为请求参数的情况,查看network窗口下的 initiator面板,该面板会显示发起对应请求的函数调用栈,依次检查
调用栈中的函数,进行静态或者动态分析发现参数的输出位置。
3、对于加密参数为ajax/fetch请求参数的情况,利用source窗口下XHR/fetch Breakpoints子面板的功能,它允许输入一段字
符串,如果ajax/fetch请求的请求参数中包含该字符串,程序会在发出该请求之前中断,中断后一次检查调用栈中的函数。
4、Js 插桩技术
Js 插桩技术
插桩技术:在保证被测程序原有逻辑完整性的基础上在程序中插入一些探针,进行信息的采集。
Js中可用于实现插桩的技术:
- Object.defineProperty 重定义get、set方法
(function() {
'use strict';
var rm_cookies = ["tfstk", "l", 'isg']
var catch_cookie = ""
var cookie_cache = document.cookie;
Object.defineProperty(document, 'cookie', {
get: function() {
console.log(cookie_cache);
return cookie_cache;
},
set: function(val) {
var cookie = val.split(";")[0];
var ncookie = cookie.split("=");
var flag = false;
var cache = cookie_cache.split(";");
if (ncookie[0]==catch_cookie){
debugger;
}
if(rm_cookies.indexOf(ncookie[0]) > 0){
console.log(cookie_cache);
return ""
}
cache = cache.map(function(a){
if (a.split("=")[0] === ncookie[0]){
flag = true;
return cookie;
}
return a;
})
cookie_cache = cache.join(";");
if (!flag){
cookie_cache += cookie + ";";
}
},
});
})();
- 重定义函数
var hook_key = "appKey=12574478"
var _open = window.XMLHttpRequest.prototype.open;
var myopen = function open(method, url, async){
console.log(url)
if (url.indexOf(hook_key)>-1){
debugger;
}
return _open.apply(this, arguments);
};
window.XMLHttpRequest.prototype.open = myopen;
- Proxy 对象
window = new Proxy(window, {
get: function(target, key, receiver) {
if (target[key] instanceof Object) {
console.log("get111", key);
return new Proxy(target[key], {
get: function(_target, _key, _receiver) {
if (_target[_key] instanceof Object){
console.log("get222", key, _key);
}
else{
console.log("get222", key, _key, _target[_key]);
}
return _target[_key];
},
set: function(_target, _key, _value, _receiver){
console.log("set222", key, _key, _value);
target[_key] = _value;
}
})
}
else{
console.log("get111", key, target[key]);
}
return target[key];
},
set: function(target, key, value, receiver) {
if (value instanceof Object){
console.log("set111", key);
}
else{
console.log("set111", key, value);
}
target[key] = value;
}
});
混淆
为增加逆向的难度,重要参数的生成过程都是经过混淆的,常见的混淆策略如下:
1、变量名混淆
2、字符串常量混淆
3、流程混淆:增加冗余逻辑,顺序逻辑变为while if switch 条件表达书 等逻辑,条件表达式还有使代码无法在chrome中调试的作用
反混淆工具:js的抽象语法树
补环境大法
对于参数的中间执行过程及其复杂,代码量多达上万行的情况,人工调试难度非常大而且极其耗时,如瑞数、sojson、阿里的js混淆,
这于这种情况常用的方法是补环境。
该过程一般为找到参数的中间执行过程,复制到node环境,已知该中间过程的正确执行依赖于浏览器环境即为window对象,理论上在
node里构造中间执行过程所依赖的正确的环境,中间执行过程则能够正确执行输出正确参数。
方法1、node里直接执行代码,根据报错信息提供环境信息
方法2、js 插桩技术
常见反爬
检测浏览器信息:
navigator
getBattery:
var promise = getBattery()
promise.then(handler)
canvas
字体大小
performance:
var entries = performance.getEntriesByType('resource')
var nameGetter = Object.getOwnPropertyDescriptor(PerformanceEntry.prototype, 'name').get
var urls = entries.map(nameGetter.call, nameGetter)
函数级别的代码完整性检测:(之后可能有文件级别的完整性检验)
说明:经常会遇到整个js代码美观格式化之后,就无法得到正确结果的情况,这种反爬的原理是调用了函数的toString方法得到自身代码字符串,通过md5或者正则等方式判断
签名是否相似来验证函数完整性。
通过方法:1、修改签名的地方
2、文档不格式化、不用ast反混淆,改需要修改的部分,验证修改的部分没有被签名。
关键参数保留在闭包中,而不是通过传递的方式:
chrome反调试:
说明:例子 chrome浏览器打开淘宝页面出现验证码 只要打开chrome开发者工具,在验证码面板上划一下,验证码验证就通过不了
反外部调用关键函数:通过实例化Error对象获取调用栈,判断函数调用过程是否正确。
参考教程
b站 js逆向编程猫