反爬(十)

反爬十

之前搞的有反爬的网站基本都是采用debug,然后扣代码的方式,扣出来核心的代码,自己通过node运行或者使用python运行,但是有些情况下难以扣出来核心的代码,或者说这样做的效率非常低,所以还有另一种方式就是拿出全部js,然后直接运行我们需要的方法,如果报错再去定位问题,具体解决。下面列举两个网站的例子。

信用邯郸

网址:https://credit.hd.gov.cn/xyxxgs/

请求列表页时,需要以上参数,其中nonceStr、queryContent、sign都是通过js代码通过一定的逻辑生成的。

首先看nonceStr参数:

我们定位到H.uuid()之后,里面还调用了其他方法,按照以往的做法,就是把所有被调用的方法都扣出来,但是考虑到一种情况就是层层调用的方法特别多(懂的都懂,不细说了)

那这里可以采用的另一种方法就是拿出全部的js,不过注意这个网站的js是一个自执行的function,调用时传入的参数包含this,所以可以改写为普通function,然后再调用,传入参数为空(测试一下大概率是没问题的);然后在文件开头定义个全局的window对象,window = global;,然后在H方法定义的地方,将其保存到window对象中,之后在最后直接调用即可。

1
2
3
4
5
6
7
8
9
10
function get_uuid(){
try {
return window.H.uuid();
} catch (err) {
console.error(err.message)
return ''
}
}

get_uuid();

queryContent参数,注意看是图中的i,而i主要是ls.sm4.encrypt生成的

同样的,在适当的地方将ls方法保存到window对象window.ls = ls,之后直接调用

1
2
3
4
5
6
7
8
9
10
function get_query_content(i){
try {
return window.ls.sm4.encrypt(i, "dbb78b8b64d640bb130255c69e959973");
} catch (err) {
console.error(err.message)
return ''
}
}

get_query_content("param=&page=7&size=10");

这里面参数值的问题是一些细节问题,就不详细展开了,调试的时候都能看得到。

sign参数,是图中的o,生成的代码是o = Zi(o = ls.sm2.signature(a, e.appSignPrivateKey, e.appSignPublicKey, e.appId))

同样的,在适当的地方将Zi方法保存到window对象window.Zi = Zi,之后直接调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function get_sign(a){
var private_key = "7faa61bb9051707ad9d9d2c417d61e038a3af871a61c8da534a9061ac1e51c32";
var public_key = "040f5940c99c46ee9e438487c6a41d880b93f0804ea0e5ef53a062bb08203fc2a675b3d2b7a9aeb1862bb1b8fa5d17a40e300cbbe9a470ee3bf89b4ccb1c899719";
var app_id = "27IGtFrNFDc";
try {
return window.Zi(window.ls.sm2.signature(a, private_key, public_key, app_id));
} catch (err) {
console.error(err.message)
return ''
}
}

var a = "appId=27IGtFrNFDc&encryptType=SM4&nonceStr=610170e754c74812a4fdb8434cc299af&queryContent=1b55869215635c155ac1af8e9b77ee2e31244ee277cc8ef5d7df2e4ad5afd7b9&signType=SM2&timestamp=1713250498944&version=1.0";
get_sign(a);

同样的针对返回的数据解密方法也是利用这样的套路

1
2
3
4
5
6
7
8
function sm4_decrypt(text){
try {
return window.kr.sm4.decrypt(text, "dbb78b8b64d640bb130255c69e959973", {output: "string"});
} catch (err) {
console.error(err.message)
return ''
}
}

信用安徽

地址:
https://credit.ah.gov.cn/ms/credit/temp/selectQueryTableTmp.do?queryId=54474&activeTitle=%E8%A1%8C%E6%94%BF%E7%AE%A1%E7%90%86%E4%BF%A1%E6%81%AF
网址可能打不开,需要从首页点进去。

这个详情页请求时,sign参数是通过加密的js生成的。

定位到sign生成的地方

这里去一段段的扣Js比较麻烦,我们也是采用直接拿全部js的方法,拿出来之后,我们本身是知道直接传入参数,调用radms方法就能获取需要的参数的

1
2
res = radms("65885665-a675-494f-97ce-15a28d86ef7f", "78890", 1716446852342)
console.log(res);

直接调用测试,会报一些错误,根据报错提示和代码行数,定位到问题进行修复

报错:Uncaught ReferenceError ReferenceError: window is not defined
解决办法:在文件开头定义window全局对象,window = global;

报错:Uncaught ReferenceError ReferenceError: CryptoJS is not defined
解决办法:导入包,CryptoJS = require('crypto-js')

报错:Uncaught ReferenceError ReferenceError: navigator is not defined
解决办法:在开头定义var navigator = {};

报错:Cannot read properties of undefined (reading 'webdriver')
报错行数 3943, var _uu = window['navigator'][_0x2246a3(0x176)], _aa = '0';
这里 _0x2246a3(0x176) 就是 webdriver
这一步骤就是为了给_uu赋值,我们回到浏览器中,运行一下window['navigator'][_0x2246a3(0x176)]拿到值,直接在这里写死就好了

以上问题解决完之后,基本就可以运行得到sign的值了。

node express包

node 的express包可以把node代码部署为服务,只要通过下述代码即可快速运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const express = require("express");  // 先安装一下express包
const bodyParser = require("body-parser");
const funcs = require("./all"); // 导入自定义的js文件
const { time } = require("console");
const app = express();


app.use(bodyParser.json({
limit: "5000kb"
}));
app.use(bodyParser.raw({
limit: "5000kb"
}));
app.use(bodyParser.urlencoded({
extended: false,
limit: "5000kb"
}));

app.use(bodyParser.text({
type: "text/plain",
limit: "5000kb"
}));


app.post("/xinyong_anhui", (req, res) => {
try {
console.log("开始获取sign");
// 请求发送发过来的参数的req.body中
server_id = req.body.server_id;
column_id = req.body.column_id;
time_stamp = req.body.time;
result = funcs.radms(server_id, column_id, time_stamp);
console.log("获取sign成功" + result);
res.send(result); // 将结果返回

} catch (err) {
console.error(err);
}
});


PORT = 10002 // 设置端口
app.listen(PORT, () => {
});

在自定义的js代码中需要先导出这里想要执行的方法,module.exports = {radms:radms};
这里是使用res.send将生成的参数返回,返回的格式是字符串,如果需要以json格式返回,则先预先定义一个json,然后使用res.json(result)返回。
这个简单的demo基本已经可以应付日常任务了,如果还需要更复杂的功能,则查看express文档即可。

pm2

pm2 是 nodejs 的进程管理器,默认支持负载均衡,能够守护进程。还支持查看应用运行时的性能,资源占用情况等
pm2支持后台运行:普通启动方式:node index.js,关闭终端就结束进程;pm2可以后台运行,终端关闭不影响。
pm2安装:npm install pm2 -g(全局)

PM2常用命令:
启动进程:pm2 start server.js / pm2 start 文件夹

pm2 start app.js –watch 当文件变化时自动重启应用

pm2 list / pm2 status 查看所有启动的应用列表(状态)

pm2 monit 显示每个应用程序的CPU和内存占用情况

pm2 show [app-id/app-name] 显示指定应用程序的所有信息
pm2 log 显示应用程序的日志信息
pm2 log [app-id/app-name] 显示指定应用程序的日志信息

pm2 flush 清空所有日志文件
pm2 stop all 停止所有应用程序
pm2 stop [app-id/app-name] 停止指定应用程序
pm2 restart all 重启所有应用程序
pm2 restart [app-id/app-name] 重启指定应用程序
pm2 delete all 关闭并删除所有应用程序
pm2 delete [app-id/app-name] 删除指定的应用程序

pm2 reload all 重启所有应用程序
pm2 reload [app-id/app-name] 重启指定的应用程序
restart 会同时杀死和重启所有相关进程,在短暂时间内服务是不可用的。 reload 的话则是一个个销毁和重启进程,保证至少一个进程可用,做到零停机部署。

但是如果是直接pm2 start server.js未设置多进程并发,restart和reload没什么区别,只有利用-i参数设置并发时,才有区别。
pm2 start server.js -i 5(启动应用程序,5个进程)