![](https://img.haomeiwen.com/i2675631/dc652a0970241939.png)
前几天在写种子爬虫,就是将某论坛中大家分享的种子给爬下来,但是想要种子在隐藏内容里,需要回复后或是啥金币才可以看到,但是在分享的帖子内容中基本上都有infohash值信息。
😏嘿嘿!按我平时的做法都是通过一些已有的种子网站通过infohash进行下载就行了,我最常用的是itorrents.org,不过不知道啥时候开始如果第一次进入网站回进入到一个浏览器检查页面。
![](https://img.haomeiwen.com/i2675631/a0aba1d2eb3cd6df.png)
但这也不难,直接通过无头浏览器获取到cookie然后爬就行。
我就是好奇了它提示的浏览器检查,检查哪些东西嘞?
于是就查看了下源码,最开始在看到的是以下js代码,并且每次刷新网页获取的代码都有些区别。
![](https://img.haomeiwen.com/i2675631/0f4ec9e0dc29e25b.gif)
最明显的变化就是+((!+[]+!![]+!![].....
这些看似乱码的东西,将部分代码拷到浏览器控制台中可以看到输出的结果。
![](https://img.haomeiwen.com/i2675631/58a09bf616c43117.png)
其实这些操作符我之前在网上看过一篇关于“如何用JS优雅的骂人”的文章中了解过,大家自行搜索我就不讲了。
> (!(~+[])+{})[--[~+""][+[]]*[~+[]] + ~~!+[]]+({}+[])[[~!+[]]*~+[]]
< "sb"
根据观察可以发现网站中只用到了数字相关的操作符,对应关系如下:
"{
"0": "+[]",
"1": "+!![]",
"2": "!+[]+!![]",
"3": "!+[]+!![]+!![]",
"4": "!+[]+!![]+!![]+!![]",
"5": "!+[]+!![]+!![]+!![]+!![]",
"6": "!+[]+!![]+!![]+!![]+!![]+!![]",
"7": "!+[]+!![]+!![]+!![]+!![]+!![]+!![]",
"8": "!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]",
"9": "!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]+!![]"
}"
我将源代码进行格式化整理,很容易发现原理。
后端生成随机声明变量与其值的代码,再进生成行随机随机运算代码,其中值部分通过特殊的操作符化处理,达到类似混淆的效果,运算结果会提交到后台以便进行对比判断。
![](https://img.haomeiwen.com/i2675631/e2753ae613b106aa.png)
当然变化的不只是js代码,HTML中的表单里面的值也在发生的变化,这里的值的话我猜测是一些时间戳、请求头标识、客户端ip端口之类进行特殊运算生成的用作浏览器标识的东西吧。
![](https://img.haomeiwen.com/i2675631/d6531e76e820c953.gif)
模仿开始
根据上面的探索,我们需要通过生成js相关的随机代码与记录一些浏览器标识信息,通过全局请求拦截再请求判断数据是否允许访问。这里的话我还是使用我最常用的Nodejs来实现吧。
首先处理数字转操作符代码
//GroupingOperator.js
const numMap = {
0: '+[]',
1: '+!![]',
2: '!+[]+!![]',
};
for (let i = 3; i <= 9; i++)numMap[i] = numMap[i - 1] + '+!![]';
/**
*
* @desc 生成一位数字对应运算符
* @param {Number} min
* @param {Number} n
* @param {Boolean} s
* @return {String}
*/
const num = function (n, s) {
let num = numMap[n];
return s == true ? num + '+[]' : num;
};
/**
*
* @desc 生成多位数字对应运算符
* @param {Number} n
* @return {String}
*/
const nums = function (n) {
let chars = n.toString().split("");
return "+(" + chars.map((v, i) => {
let c = num(v);
return `(${i == 0 ? c + num(0) : c})`;
}).join("+") + ")";
};
关于输出随机数随机变量相关可以查看:Random.js
接着就是服务部分了:
具体逻辑看代码的注释吧:index.js
//index.js
const express = require('express'); //express 4
const app = express();
const bodyParser = require('body-parser');
const multer = require('multer');
const upload = multer();
const cookieParser = require('cookie-parser')
const Session = require('express-session');
const Random = require("./Random");
const Goper = require("./GroupingOperator");
const APP_DOMAIN = "127.0.0.1";//<==自行修改
const APP_HOST = APP_DOMAIN + ":8080";
const APP_URL = "http://" + APP_HOST;
//自定义的质数集
const PRIMES = [
[817504243, 817504253, 838041641, 838041647, 858599503, 858599509, 879190747, 879190841, 899809343, 899809363, 920419813, 920419823, 941083981, 941083987, 961748927, 961748941, 982451653, 982451707],
[295075147, 295075153, 314606869, 314606891, 334214459, 334214467, 353868013, 353868019, 373587883, 373587911, 393342739, 393342743, 413158511, 413158523, 433024223, 433024253, 452930459, 452930477, 472882027, 472882049, 492876847, 492876863, 512927357, 512927377, 533000389, 533000401, 553105243, 553105253, 573259391, 573259433, 593441843, 593441861]
];
//算术操作符
const OPRTATORS = {
"*": (a, b) => a * b,
"-": (a, b) => a - b,
"/": (a, b) => a / b,
"+": (a, b) => a + b
};
//设置模板引擎为ejs
app.set('view engine', 'ejs'); //app = express() ;
//设置模板文件位置 => 项目根路径下views目录为模板文件存放目录
app.set('views', __dirname + '/views');
app.use(cookieParser())
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(Session({
name: "sid",
secret: 'key',
resave: true,
rolling: true,
saveUninitialized: false,
cookie: { "maxAge": 60000 }
}));
//随机计算符
function randomOperators() {
return Random.numByArray(Object.keys(OPRTATORS));
}
//随机质数数组对象
function randomNumArr() {
let obj = {
_n: [Random.numByArray(PRIMES[0]), Random.numByArray(PRIMES[1])]
};
obj._r = [Goper.nums(obj._n[0]), Goper.nums(obj._n[1])];
return obj
}
//随机质数数组对象与操作符
function randomNumOper() {
let obj = randomNumArr();
obj._o = randomOperators();
return obj;
}
//全局拦截器
app.all('/*', function (req, res, next) {
//排除
for (let i of ["/505", '/cdn-cgi/l/chk_jschl',"/favicon.ico"]) if (req.url.indexOf(i) == 0) return next();
//检查
if (req.session.cf_clearance && req.session.cf_clearance == req.cookies.cf_clearance) {
next();
} else {
req.session.cf_clearance = "";
const a = randomNumArr();//获取初始化数据
a._u = Random.num(3, 10);//计算次数
a._v = Random.varName(10);//对象变量名
a._t = Random.varName(Random.num(1, 5));//对象键变量名
a._c = [];//计算数据
a._h = a._n[0] / a._n[1];//当前值
a._a = a._h;//计算结果
for (let i = 0; i < a._u; i++) {//循环并计算
let obj = randomNumOper();
obj._h = obj._n[0] / obj._n[1];
a._a = OPRTATORS[obj._o](a._a, obj._h);
a._c.push(obj);
}
a._e = (+a._a + APP_HOST.length).toFixed(10);//用作对比的结果值
a._l = `${a._v}={"${a._t}":${a._r.join("/")}};`;
a._m = ";" + a._c.map(v => `${a._v}.${a._t}${v._o}=${v._r.join("/")};`).join("");
let s = req.headers["user-agent"] + (+new Date()) + req.ip;//自定义的记录请求相关标识信息演示
let s2 = Buffer.from(s).toString("hex");
let data = {//记录相关数据
headers: req.headers,
js: a,
form: {
s: `${Random.md5(s)}-${parseInt((+new Date()) / 100)}-1800-${Buffer.from(s2).toString("base64")}`,
jschl_vc: Random.uuidv4().split("-").join(""),
pass: `${(+new Date()) / 100}-${Random.varName(10)}`,
},
host: APP_HOST,
rayID: Random.uuidv1(s).split("-").join(""),
url: req.url,
cookie: { __cfduid: Random.uuidv4() }
};
req.session.renderData = data;//保存到会话中
res.cookie('__cfduid', data.cookie.__cfduid, { domain: APP_DOMAIN, path: '/' });
res.render('verification', data);//渲染页面
}
});
//浏览器验证请求
app.get('/cdn-cgi/l/chk_jschl', function (req, res) {
let s = req.param("s");
let jschl_vc = req.param("jschl_vc");
let pass = req.param("pass");
let jschl_answer = req.param("jschl_answer");
let __cfduid = req.cookies.__cfduid;
let renderData = req.session.renderData;
if (!renderData) return res.redirect('/');
let url = renderData.url;
if (__cfduid == renderData.cookie.__cfduid && s == renderData.form.s
&& jschl_vc == renderData.form.jschl_vc && pass == renderData.form.pass
&& jschl_answer == renderData.js._e && req.headers["user-agent"] == renderData.headers["user-agent"]) {
//可以加上时间戳判断是否超过5秒
req.session.cf_clearance = Random.uuidv1() + "-" + Random.uuidv4();
res.cookie('cf_clearance', req.session.cf_clearance, { domain: APP_DOMAIN, path: '/' });
delete req.session.renderData;
}
res.send(`<script>location.href="${url}";</script>`);
});
//假设是app的登陆页面
app.get('/', function (req, res) {
let url = req.query.url;
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.write(`<html>
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body> Home Page
</body>
</html>`);
res.send();
});
//505
app.get('/505', function (req, res) {
res.writeHead(505, { "Content-Type": "text/html; charset=utf-8" });
res.write(`<html>
<head>
<meta charset="UTF-8">
<title>505</title>
</head>
<body> 505
</body>
</html>`);
res.send();
});
app.listen(8080);
效果演示
正常情况效果
![](https://img.haomeiwen.com/i2675631/774be110510c9133.gif)
代码改变效果
![](https://img.haomeiwen.com/i2675631/1bc8da39f19e756a.gif)
改变浏览器标识效果
![](https://img.haomeiwen.com/i2675631/a01ab56c4cedb510.gif)
网友评论