声明:
该文章为学习使用,严禁用于商业用途和非法用途,违者后果自负,由此产生的一切后果均与作者无关
一、websocket简介
WebSocket是一种在Web浏览器和服务器之间进行全双工通信的协议。它允许在客户端和服务器之间建立持久的连接,使得双方可以通过这个连接实时地交换数据。
与传统的HTTP请求-响应模式不同,WebSocket提供了一个长时间运行的连接,可以在客户端和服务器之间进行双向通信。这意味着服务器可以主动向客户端发送数据,而不需要客户端发起请求。这种实时性和双向通信的特性使得WebSocket在许多应用场景下非常有用,如实时聊天应用、在线游戏、股票市场报价等。
WebSocket协议建立在HTTP协议之上,使用HTTP的握手过程来建立连接,然后协议切换到WebSocket协议进行数据交换。WebSocket使用了一种轻量级的帧格式来传输数据,可以发送文本和二进制数据。它还支持心跳机制,以保持连接的活跃状态。
在Web开发中,通常使用WebSocket API来实现WebSocket通信。浏览器提供了WebSocket API,可以在JavaScript中使用它来创建WebSocket对象,建立连接并发送和接收数据。
总结起来,WebSocket是一种在Web浏览器和服务器之间实现实时、双向通信的协议,它允许长时间运行的连接,支持服务器主动推送数据,适用于许多实时性要求较高的应用场景。
二、websocket实现方式
- javascript websocket实现方式
//与服务器约定的连接 以及端口本机的 hosts
const socket = new WebSocket('ws://127.0.0.1:8080/')
//连接发生错误的回调方法
socket.onerror = () => {
console.log("WebSocket连接发生错误");
};
//连接成功建立的回调方法
socket.onopen = function () {
console.log("WebSocket连接成功");
}
//接收到消息的回调方法 接收服务器的数据
socket.onmessage = function (event) {
console.log(event.data);
socket.send('浏览器')
}
//连接关闭的回调方法
socket.onclose = function () {
console.log("WebSocket连接关闭");
}
- python websocket实现方式
import asyncio
import websockets
async def echo(websocket):
# 使用WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时调用send()方法。
message = 'hello world'
# 发送数据
await websocket.send(message)
return True
async def recv_msg(websocket):
while 1:
# 接收数据
recv_text = await websocket.recv()
print(recv_text)
async def main_logic(websocket, path):
await echo(websocket)
await recv_msg(websocket)
start_server = websockets.serve(main_logic, '127.0.0.1', 8080)
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
# 创建了一个连接对象之后,需要不断监听返回的数据,则调用 run_forever 方法,要保持长连接即可
loop.run_forever()
三、websocket爬虫应用
- js运行 atob(‘aHR0cHM6Ly9qenNjLm1vaHVyZC5nb3YuY24vZGF0YS9jb21wYW55’) 拿到网址,F12打开调试工具,点击分页,找到dataservice/query/comp/list请求,分别切换headers、payload、preview会发现只有响应结果加密
- 打开网站:https://spidertools.cn/#/curl2Request,把拷贝好的curl转成python代码,新建jzsc.py,把代码复制到该文件
- XHR定位响应结果解密位置,调试工具切换到Sources,添加 dataservice/query/comp/list 请求拦截
- 点击分页重新发送请求,一直点击跳过当前函数执行,看到响应拦截器打个断点
- 结束调试,点击分页重新发送请求,点击跳过断点,找到刚才打的断点,在控制台输出 b(t.data),会发现响应结果已解密
- 结束调试,添加文件夹
- 右击刚才断点的文件,点击 override content,会在刚才的文件夹下生成一个相同的文件,并刷新页面
- 在刚才的文件内找到 b(t.data),打上断点,点击分页发送请求,如果进入该断点,说明本地文件替换成功
- 修改本地替换的文件 window.reponse_decrypt = b;,保存该文件,并刷新页面
- 新建 websocket.py,添加以下代码,并运行
import asyncio
import websockets
async def echo(websocket):
# 使用WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时调用send()方法。
message = 'socket连接成功'
# 发送数据
await websocket.send(message)
return True
async def recv_msg(websocket):
while 1:
# 接收数据
recv_text = await websocket.recv()
print(recv_text)
async def main_logic(websocket, path):
await echo(websocket)
await recv_msg(websocket)
start_server = websockets.serve(main_logic, '127.0.0.1', 8080)
loop = asyncio.get_event_loop()
loop.run_until_complete(start_server)
# 创建了一个连接对象之后,需要不断监听返回的数据,则调用 run_forever 方法,要保持长连接即可
loop.run_forever()
- 新建 websocket hook,添加以下代码,并运行,控制台看到websocket连接成功说明,已经和python启动的websocket服务建立起连接
(function () {
const websocket = new WebSocket('ws://127.0.0.1:8080');
websocket.onopen = function () {
console.log("WebSocket连接成功");
};
// 接收服务端发送的信息
websocket.onmessage = function (event) {
const data = window.reponse_decrypt(event.data);
console.log("发送数据")
// 发送解密数据给服务端
websocket.send(data)
}
})();
- 关闭所有soucket连接,修改 jzsh.py,并运行
- 运行 websocket hook,看 jzsh.py 控制台,会发现响应结果已解密
四、RPC逆向
RPC 在逆向中,简单来说就是将本地和浏览器,看做是服务端和客户端,二者之间通过 WebSocket 协议进行 RPC 通信,在浏览器中将加密函数暴露出来,在本地直接调用浏览器中对应的加密函数,从而得到加密结果,不必去在意函数具体的执行逻辑,也省去了扣代码、补环境等操作,可以省去大量的逆向调试时间。
这里使用 Sekiro-RPC 说明RPC在爬虫中的应用, Sekiro-RPC 官网:https://sekiro.iinti.cn/sekiro-doc/,
- Sekiro-RPC安装:https://sekiro.iinti.cn/sekiro-doc/01_manual/4.server_install.html#%E5%90%AF%E5%8A%A8-sekiro,访问该链接找到手动部署下载
- Linux & Mac:bin/sekiro.sh 双击打开服务端
- Windows:bin/sekiro.bat 双击打开服务端
- Sekiro-RPC需要java环境:https://repo.huaweicloud.com/java/jdk/8u201-b09/,下载对应系统的安装即可
- 客户端环境:http://file.virjar.com/sekiro_web_client.js?_=123
- 使用参数说明
// 生成唯一标记uuid编号
function guid() {
function S4() {
return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
}
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
}
// 连接服务端
var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=ws-group&clientId="+guid());
// 业务接口
client.registerAction("clientTime",function(request, resolve, reject){
resolve(""+new Date());
})
- group:业务类型(接口组),每个业务一个 group,group 下面可以注册多个终端(SekiroClient),同时group 可以挂载多个 Action;
- clientId:指代设备,多个设备使用多个机器提供 API 服务,提供群控能力和负载均衡能力;
- SekiroClient:服务提供者客户端,主要场景为手机/浏览器等。最终的 Sekiro 调用会转发到 SekiroClient。每个 client 需要有一个惟一的 clientId;
- registerAction:接口,同一个 group 下面可以有多个接口,分别做不同的功能;
- resolve:将内容传回给服务端的方法;
- request:服务端传过来的请求,如果请求里有多个参数,可以以键值对的方式从里面提取参数然后再做处理。
- Sekiro API
- 查看分组列表:http://127.0.0.1:5620/business-demo/groupList
- 查看队列状态:http://127.0.0.1:5620/business-demo/clientQueue?group=rpc-test
- 调用转发:http://127.0.0.1:5620/business-demo/invoke?group=rpc-test&action=clientTime
五、RPC爬虫应用
- 以websocket爬虫应用中的网站为例,直到webscoket的部分不同,其他步骤相同
- 打开油猴,https://extfans.com/ 可通过该网站安装油猴
- 新建油猴脚本
- @name:UserScript的名称。
- @namespace:UserScript的命名空间。
- @version:UserScript的版本号。
- @description:UserScript的描述信息。
- @author:UserScript的作者。
- @homepage:UserScript的主页。
- @icon:UserScript的图标。
- @include:适用于UserScript的网页地址模式。
- @exclude:排除不适用于UserScript的网页地址模式。
- @match:适用于UserScript的网页地址正则表达式。
- @grant:UserScript请求的权限,比如访问某个特定的API。
- @require:UserScript所依赖的其他脚本文件。
- @resource:UserScript所使用的资源文件,比如图像、样式表等。
- @run-at:UserScript的执行时机,比如document-start(页面加载前)或document-end(页面加载后)。
- @noframes:指定UserScript不在框架(iframe)中执行。
// ==UserScript==
// @name Sekiro-RPC
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Sekiro-RPC爬虫应用
// @author You
// @match https://extfans.com/
// @icon https://www.google.com/s2/favicons?sz=64&domain=extfans.com
// @grant none
// @require http://file.virjar.com/sekiro_web_client.js?_=123
// @run-at document-start
// @author CC11001100
// @match *://*/*
// ==/UserScript==
(function() {
'use strict';
function guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=sekiro&clientId=" + guid());
client.registerAction("rpc", function (request, resolve, reject) {
resolve(window.reponse_decrypt(request.param));
})
})();
- 启动Sekiro-RPC服务端,bin/sekiro.bat 双击打开服务端(windows)
- 在网站中启动油猴,并刷新页面,控制台显示这些信息说明Sekiro-RPC连接成功
- 修改 jzsc.py,并运行,会发现响应结果已解密