本教程仅限于学术探讨,也没有专门针对某个网站而编写,禁止用于非法用途、商业活动、恶意滥用技术等,否则后果自负。观看则同意此约定。如有侵权,请告知删除,谢谢!
目录
概要
本期逆向网站:aHR0cHM6Ly9zcG9ydHMuc3BvcnRzLTE4OC5jb20vZXJyb3IvZm9yYmlkZGVuP3M9QUVFQUFH
Incapsula 是 Imperva 的反机器人。它有两种模式,即验证码非侵入模式和验证码模式。前者最容易通过,而后者则不怕发出更多禁令。
Incapsula 提供的文件本质上是动态的,部分原因是它们改变了编码函数,使其与前一个函数略有不同,因此每次重新加载都会产生不同的编码函数。这在用于reese84
有效负载的编码函数和用于对 cookie 进行编码的编码函数中很明显___utmvc。
reese84此 cookie 尤其包含使用简单xorshift128算法编码的指纹。除此之外,reese84
有效负载中的每个键值对都使用一系列For和While循环进行编码,这些循环以动态顺序对字节进行打乱、复制、克隆或重新排列。
本次逆向网站是没有 ___utmvc 的,所有这个文件省掉了,这个文件的加密也不是很难可以看看其它博主的文章。
逆向分析流程
1.分析下逆向的接口
第一个请求接口不难看出是一个加密用的JS文件代码
第二个请求呢就是加密的表单了蛤
2.开始分析第一个接口
1.JS代码拿到node中,第一自执行呢就是加密函数了,我这里就不AST反混淆了,因为文件都是动态的,反混淆亦或者不反混淆都大差不差了
2.代码直接搜索 aih 这个就是他的加密表单其中一部分内容了
3.然后我们的加密入口代码可以写成这样的,剩下的慢慢补环境了
const copyPaste = require('copy-paste');
const CryptoJS = require('crypto-js');
(async () => {
try {
function calculateSha1(word) {
return CryptoJS.SHA1(word).toString();
}
new Promise((resolve, reject) => {
let date = new Date().getTime();
let data = {
"aih": "5lgxcYvyAkiyhyh/pg/bwqpv4eD+Np4gRja9b6L/lps=",
"t": {
"marks": {
"total": date,
"interrogation": date + parseInt(Math.random() * 100000)
},
"measures": {
"interrogation": 162
},
start: () => {
// console.log("start: ", arguments);
}
,
startInternal: () => {
// console.log("startInternal: ", arguments);
}
,
stop: () => {
// console.log("stop: ", arguments);
}
,
stopInternal: () => {
// console.log("stopInternal: ", arguments);
}
,
summary: () => {
// console.log("summary: ", arguments);
}
},
"at": 1,
"sic": 1,
"slc": 4,
"s": calculateSha1,
"gcs": [],
"slt": date
};
new window.reese84interrogator(data).interrogate(resolve, reject);
}
).then(data => {
data = JSON.stringify(data);
copyPaste.copy(data, function () {
console.log('加密表单已复制到粘贴板:', data);
});
}
).catch(e => {
console.log("出现异常 --> ", e);
})
} catch (err) {
console.error(err);
}
})();
4.然后就用我们经典的补环境代理了 Proxy与Reflect 然后怎么办?当然由你们慢慢补咯!
function detectionEnv(proxy_name_array, out_call = true, out_fault = true) {
require('colors');
const beautify = require("js-beautify");
const createHandler = ({obj_name, env_path = undefined, env_value = undefined}) => {
const handler = {
get: function (target, property, receiver) {
const value = Reflect.get(target, property, receiver);
// 如果是symbol就直接返回
try {
if (typeof property === 'symbol') {
return value
}
} catch (e) {
console.log(`1.${e}`, property);
}
// 拼接路径
let envPath = "";
try {
envPath = `${env_path || obj_name}.${property}`;
} catch (e) {
console.log(`2.${e}`, property, property === Symbol.toStringTag);
}
// 递归判断
try {
if (typeof value === 'object' && value !== null && value !== undefined) {
const env_path_tag = envPath.split(".").pop();
if (out_call && propertyPath.exists(envPath) && env_value && env_path_tag === property) {
console.log(`方法:get | 1.调用环境方法:${envPath} | 属性:${property} | 属性类型:${typeof property} | 属性值类型:${typeof value}`.green);
}
envPath !== "" && propertyPath.set("get", envPath)
// 递归调用
return createHandler({obj_name: value, env_path: envPath, env_value: obj_name});
}
} catch (e) {
console.log(`3.${e}`, property);
}
// 日志输出
try {
if (propertyPath.exists("get", envPath)) {
if (out_fault && value === undefined) {
console.log(`方法:get | 1.缺失环境方法:${envPath} | 属性:${property} | 属性类型:${typeof property} | 属性值类型:${typeof value}`.red);
} else {
out_call && console.log(`方法:get | 2.调用环境方法:${envPath} | 属性:${property} | 属性类型:${typeof property} | 属性值类型:${typeof value}`.cyan);
}
}
} catch (e) {
console.log(`3.${e}`, property);
}
envPath !== "" && propertyPath.set("get", envPath)
return value;
},
set: function (target, property, value, receiver) {
console.log(`方法:set 对象:${obj_name} 属性:${property} 属性类型:${typeof property} 属性值类型:${typeof value}`.blue);
return Reflect.set(target, property, value, receiver);
},
deleteProperty: function (target, propKey) {
console.log(`方法:deleteProperty | 删除属性:${propKey}`.yellow);
return Reflect.deleteProperty(target, propKey);
},
apply: function (target, thisArg, argumentsList) {
console.log(`方法:apply | 调用函数:${target.name || 'anonymous'}`.yellow);
return Reflect.apply(target, thisArg, argumentsList);
},
construct: function (target, argumentsList, newTarget) {
console.log(`方法:construct | 构造函数:${target.name || 'anonymous'}`.yellow);
return Reflect.construct(target, argumentsList, newTarget);
},
defineProperty: function (target, property, descriptor) {
console.log(`方法:defineProperty | 定义属性:${property}`.yellow);
return Reflect.defineProperty(target, property, descriptor);
},
getOwnPropertyDescriptor: function (target, property) {
console.log(`方法:getOwnPropertyDescriptor | 获取属性描述符:${property}`.yellow);
return Reflect.getOwnPropertyDescriptor(target, property);
},
getPrototypeOf: function (target) {
console.log(`方法:getPrototypeOf | 获取原型`.yellow);
return Reflect.getPrototypeOf(target);
},
setPrototypeOf: function (target, prototype) {
console.log(`方法:setPrototypeOf | 设置原型${prototype}`.yellow);
return Reflect.setPrototypeOf(target, prototype);
},
isExtensible: function (target) {
console.log(`方法:isExtensible | 检查是否可扩展`.yellow);
return Reflect.isExtensible(target);
},
preventExtensions: function (target) {
console.log(`方法:preventExtensions | 阻止扩展`.yellow);
return Reflect.preventExtensions(target);
},
has: function (target, property) {
console.log(`方法:has | 检查属性是否存在:${property}`.yellow);
return Reflect.has(target, property);
},
ownKeys: function (target) {
console.log(`方法:ownKeys | 获取自身属性键`.yellow);
return Reflect.ownKeys(target);
}
};
if (env_path === undefined) {
let handlerString = "{" + Object.entries(handler).map(([key, value]) => {
return `${key}: ${value.toString()}`
}).join(",\n") + "}";
handlerString = beautify(handlerString, {indent_size: 2, space_in_empty_paren: true});
const name = obj_name.toUpperCase() === "window".toUpperCase() ? "global" : "Object.create({})"
// 开始代理
return eval(`
try {
${obj_name} = new Proxy(${obj_name}, ${handlerString});
} catch (e) {
console.log(("未定义 " + obj_name + " 默认赋值为: ${name}").red);
${obj_name} = new Proxy(${name}, ${handlerString});
}`);
}
// 递归
return new Proxy(obj_name, handler);
}
const propertyPath = new class {
constructor() {
this.PropertyPath = new Map();
}
exists(key, property_path) {
let property_path_set = this.get(key);
return !property_path_set.has(property_path);
}
get(key) {
let property_path_set = this.PropertyPath.get(key.toLowerCase());
if (!property_path_set) {
property_path_set = new Set();
}
return property_path_set;
}
set(key, property_path) {
let property_path_set = this.get(key);
property_path_set.add(property_path);
this.PropertyPath.set(key.toLowerCase(), property_path_set);
}
}
proxy_name_array.forEach(obj_name => createHandler({obj_name: obj_name}));
}
const proxy_array = [
"baseSelector.hqrW1", "baseSelector.footerText", "baseSelector.xnSV0", "baseSelector.frHelperLoopLink",
"alert", "window", "body", "document", "history", "getContext", "localStorage", "location", "navigator", "sessionStorage", "XMLHttpRequest",
"WebSocket", "requestAnimationFrame", "getElementsByTagName", "getElementsByClassName",
"cancelAnimationFrame", "addEventListener", "removeEventListener", "dispatchEvent", "FileReader", "Worker",
"SharedWorker", "AudioContext", "WebGLRenderingContext", "WebGL2RenderingContext", "Audio", "Video", "Image",
"indexedDB", "canvas", "screen"
];
detectionEnv(proxy_array, true, true);
5.我自己也尝试过还原算法,但是呢,需要正则的东西太多了就放弃了,不信邪的小伙伴可以试试,当然肯定也有大佬还原了纯算法,反正我是不行(狗头保命)
3.分析第二个接口
1.然后分析第二个接口看看,反正我第一眼看到的时候,是两个头都大的
2.然后cookies里面还有一个 ___utmvc 这个网站的是不用的,所以我们就不逆向它了,这个比起interrogation就简单多了,感兴趣的自己研究下吧
3.然后开始用上面的代理,去补环境吧,然后自己慢慢补了两千多行,肝了一周环境,也是累人的一天蛤!
小结
然后我们看下成品效果如何吧!这个网站的reese84,如果你返回的长度是544,那么恭喜你风控绕过了,理论上搞定一个网站都是通用的,这边也是成功爬到数据的,本期逆向结束了,铁子别卷太猛,小心肾透支哦!