【js逆向专题】12.RPC技术

目录

      • 一. websocket
        • 1. 什么是websocket
        • 2. websocket的原理
        • 3. websocket实现方式
          • 1. 客户端
          • 2.服务端
          • 3. 实际案例
            • 1. 案例目标
            • 2. 解析思路
      • 二. RPC
        • 1. RPC 简介
        • 2.Sekiro-RPC
          • 1. 使用方法
            • 1. 执行方式
            • 2.客户端环境
            • 3.使用参数说明
          • 2. 测试使用
            • 1. 前端代码
            • 2. SK API
            • 3.`python`调用代码
      • 三.项目实战
        • 1. 替换文件注入案例1
          • 1. 逆向目标
          • 2.定位cookie加密位置
        • 2. 替换文件注入案例2
          • 1. 逆向目标
          • 2. 逆向分析
          • 3. 实现代码
        • 3. 油猴注入形式
          • 1. 油猴工具介绍
          • 2.逆向目标
          • 3. 逆向分析
      • 结语

上一篇直通车:【js逆向专题】11.AST节点树

js逆向专题传送门

学习目标:

  1. 了解 websocket协议
  2. 熟悉 websocket实现原理
  3. 掌握 RPC启用和注入方式

RPC,英文 RangPaCong,中文让爬虫,旨在为爬虫开路,秒杀一切,让爬虫畅通无阻!图片

WebSocket的出现,使得浏览器具备了实时双向通信的能力。

参考:https://blog.csdn.net/zyym9009/article/details/104203995

参考:https://www.cnblogs.com/chyingp/p/websocket-deep-in.html

一. websocket

1. 什么是websocket
  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议
2. websocket的原理
  • websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
  • 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
  • websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

总结(总体过程):

  • 首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
  • 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
  • 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。
3. websocket实现方式
1. 客户端
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<input id="box" type="text">
<button onclick="ps()">发送</button><script>// 与服务器约定的连接 以及端口  本机的   hosts文件  localhost      www.ps.comconst websocket = new WebSocket('ws://127.0.0.1:8080/')//连接发生错误的回调方法websocket.onerror = () => {console.log("WebSocket连接发生错误");};//连接成功建立的回调方法websocket.onopen = function () {console.log("WebSocket连接成功");}//接收到消息的回调方法  接收服务器的数据websocket.onmessage = function (event) {console.log(event.data);}//连接关闭的回调方法websocket.onclose = function () {console.log("WebSocket连接关闭");}function ps() {// jquery -> val   JS -> valuevar text = document.getElementById('box').value// 客户端发信息发服务器websocket.send(text)}</script>
</body>
</html>
2.服务端
# encoding: utf-8
import asyncio
import websocketsasync def echo(websocket):# 使用WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时调用send()方法。message = 'hello world'# 发送数据await websocket.send(message)return Trueasync 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()
3. 实际案例
1. 案例目标
  • 网址:https://jzsc.mohurd.gov.cn/data/company

  • 需求:通过websocket解析加密数据

  • 实际注入网站代码

!(function () {if (window.flag) {} else {const websocket = new WebSocket('ws://127.0.0.1:8080')
// 创建一个标记用来判断是否创建套接字window.flag = true;
//  接收服务端发送的信息websocket.onmessage = function (event) {var data = event.data
// 调用js解密var res = b(data)console.log(res)
// 发送解密数据给服务端websocket.send(res)}}
}())
2. 解析思路
  • 定位到加密位置
  • 将我们写的websocket命令注入到代码当中(通过替换的方式实现)

在这里插入图片描述

  • 要注意数据是否正确,不带v请求头数据是有问题的

在这里插入图片描述

  • 注入之后需要刷新页面才能把js 执行起来

  • python执行代码

# encoding: utf-8
import asyncio
import websockets
import requests
import time
import jsondef get_data(page):headers = {"v": "231012","Referer": "https://jzsc.mohurd.gov.cn/data/company","User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",}url = "https://jzsc.mohurd.gov.cn/APi/webApi/dataservice/query/comp/list"params = {"pg": page,"pgsz": "15","total": "450"}response = requests.get(url, headers=headers, params=params)print(response.text)return response.textasync def echo(websocket):for i in range(1, 4):data = get_data(i)await websocket.send(data)# time.sleep(2)# return Trueasync def recv_msg(websocket):while 1:# 接收数据recv_text = await websocket.recv()print(json.loads(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()

二. RPC

1. RPC 简介

为什么要使用RPC技术呢?我们在使用websocket时候可以发现,python在操作的时候,需要创建连接,还需要不断去接受传递数据,非常的麻烦, 那这个时候rpc技术可以帮助到我们,简单来说就是网页直接和rpc服务器进行交互,我们python可以直接调用,rpc暴露的接口,不需要关心,创建连接这一块的问题.

RPC 技术是非常复杂的,对于我们搞爬虫、逆向的来说,不需要完全了解,只需要知道这项技术如何在逆向中应用就行了。

RPC 在逆向中,简单来说就是将本地和浏览器,看做是服务端和客户端,二者之间通过 WebSocket 协议进行 RPC 通信,在浏览器中将加密函数暴露出来,在本地直接调用浏览器中对应的加密函数,从而得到加密结果,不必去在意函数具体的执行逻辑,也省去了扣代码、补环境等操作,可以省去大量的逆向调试时间。

在这里插入图片描述

2.Sekiro-RPC
  • 官网地址:https://sekiro.iinti.cn/sekiro-doc/
1. 使用方法
1. 执行方式
  • 在本地开启服务端
  • 需要有 Java 环境,配置参考:https://baijiahao.baidu.com/s?id=1762153534119669123&wfr=spider&for=pc
    • 下载地址:https://repo.huaweicloud.com/java/jdk/8u201-b09/
  • Linux & Mac:bin/sekiro.sh 双击打开服务端
  • Windows:bin/sekiro.bat 双击打开服务端
2.客户端环境
  • 地址:file.virjar.com/sekiro_web_client.js?_=123 这个地址是在前端创建客户端的时候需要用到的代码,Sekiro-RPC 把他封装在一个地址里面了
3.使用参数说明
  • 使用原理:客户端注入到浏览器环境,然后通过SekiroClientSekiro 服务器通信,即可直接 RPC 调用浏览器内部方法,官方提供的 SekiroClient 代码样例如下:
// 生成唯一标记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("登陆",function(request, resolve, reject){resolve(""+new Date());
})
  • group:业务类型(接口组),每个业务一个 groupgroup 下面可以注册多个终端(SekiroClient),同时group 可以挂载多个 Action

  • clientId:指代设备,多个设备使用多个机器提供 API 服务,提供群控能力和负载均衡能力;

  • SekiroClient:服务提供者客户端,主要场景为手机/浏览器等。最终的 Sekiro 调用会转发到 SekiroClient。每个 client 需要有一个惟一的 clientId

  • registerAction:接口,同一个 group 下面可以有多个接口,分别做不同的功能;

  • resolve:将内容传回给服务端的方法;

  • request:服务端传过来的请求,如果请求里有多个参数,可以以键值对的方式从里面提取参数然后再做处理。

2. 测试使用
1. 前端代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><script src="http://file.virjar.com/sekiro_web_client.js?_=123"></script>
<script>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=rpc-test&clientId=" + guid());client.registerAction("clientTime", function (request, resolve, reject) {resolve("" + new Date());})</script>
</body>
</html>
2. SK API

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

3.python调用代码
import requestsparams = {"group": "rpc-test","action": "clientTime",
}
res = requests.get("http://127.0.0.1:5620/business-demo/invoke", params=params)
print(res.text)

三.项目实战

1. 替换文件注入案例1
1. 逆向目标
  • 地址:http://q.10jqka.com.cn/
  • 接口:http://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/2/ajax/1/
  • 对抗:cookie反爬虫处理,关键字v
2.定位cookie加密位置
(function () {Object.defineProperty(document, 'cookie', {set: function (val) {if (val.indexOf('v') != -1) {debugger;}console.log('Hook捕获到cookie设置->', val);return val;}});
})();

在这里插入图片描述

  • 确定好位置之后我们就可以替换当前文件,把我们需要的rpc的代码给注入到网页中

  • JavaScript代码

(function () {function SekiroClient(wsURL) {this.wsURL = wsURL;this.handlers = {};this.socket = {};this.base64 = false;// checkif (!wsURL) {throw new Error('wsURL can not be empty!!')}this.webSocketFactory = this.resolveWebSocketFactory();this.connect()}SekiroClient.prototype.resolveWebSocketFactory = function () {if (typeof window === 'object') {var theWebSocket = window.WebSocket ? window.WebSocket : window.MozWebSocket;return function (wsURL) {function WindowWebSocketWrapper(wsURL) {this.mSocket = new theWebSocket(wsURL);}WindowWebSocketWrapper.prototype.close = function () {this.mSocket.close();};WindowWebSocketWrapper.prototype.onmessage = function (onMessageFunction) {this.mSocket.onmessage = onMessageFunction;};WindowWebSocketWrapper.prototype.onopen = function (onOpenFunction) {this.mSocket.onopen = onOpenFunction;};WindowWebSocketWrapper.prototype.onclose = function (onCloseFunction) {this.mSocket.onclose = onCloseFunction;};WindowWebSocketWrapper.prototype.send = function (message) {this.mSocket.send(message);};return new WindowWebSocketWrapper(wsURL);}}if (typeof weex === 'object') {// this is weex env : https://weex.apache.org/zh/docs/modules/websockets.htmltry {console.log("test webSocket for weex");var ws = weex.requireModule('webSocket');console.log("find webSocket for weex:" + ws);return function (wsURL) {try {ws.close();} catch (e) {}ws.WebSocket(wsURL, '');return ws;}} catch (e) {console.log(e);//ignore}}//TODO support ReactNativeif (typeof WebSocket === 'object') {return function (wsURL) {return new theWebSocket(wsURL);}}throw new Error("the js environment do not support websocket");};SekiroClient.prototype.connect = function () {console.log('sekiro: begin of connect to wsURL: ' + this.wsURL);var _this = this;// 涓峜heck close锛岃// if (this.socket && this.socket.readyState === 1) {//     this.socket.close();// }try {this.socket = this.webSocketFactory(this.wsURL);} catch (e) {console.log("sekiro: create connection failed,reconnect after 2s");setTimeout(function () {_this.connect()}, 2000)}this.socket.onmessage(function (event) {_this.handleSekiroRequest(event.data)});this.socket.onopen(function (event) {console.log('sekiro: open a sekiro client connection')});this.socket.onclose(function (event) {console.log('sekiro: disconnected ,reconnection after 2s');setTimeout(function () {_this.connect()}, 2000)});};SekiroClient.prototype.handleSekiroRequest = function (requestJson) {console.log("receive sekiro request: " + requestJson);var request = JSON.parse(requestJson);var seq = request['__sekiro_seq__'];if (!request['action']) {this.sendFailed(seq, 'need request param {action}');return}var action = request['action'];if (!this.handlers[action]) {this.sendFailed(seq, 'no action handler: ' + action + ' defined');return}var theHandler = this.handlers[action];var _this = this;try {theHandler(request, function (response) {try {_this.sendSuccess(seq, response)} catch (e) {_this.sendFailed(seq, "e:" + e);}}, function (errorMessage) {_this.sendFailed(seq, errorMessage)})} catch (e) {console.log("error: " + e);_this.sendFailed(seq, ":" + e);}};SekiroClient.prototype.sendSuccess = function (seq, response) {var responseJson;if (typeof response == 'string') {try {responseJson = JSON.parse(response);} catch (e) {responseJson = {};responseJson['data'] = response;}} else if (typeof response == 'object') {responseJson = response;} else {responseJson = {};responseJson['data'] = response;}if (typeof response == 'string') {responseJson = {};responseJson['data'] = response;}if (Array.isArray(responseJson)) {responseJson = {data: responseJson,code: 0}}if (responseJson['code']) {responseJson['code'] = 0;} else if (responseJson['status']) {responseJson['status'] = 0;} else {responseJson['status'] = 0;}responseJson['__sekiro_seq__'] = seq;var responseText = JSON.stringify(responseJson);console.log("response :" + responseText);if (responseText.length < 1024 * 6) {this.socket.send(responseText);return;}if (this.base64) {responseText = this.base64Encode(responseText)}//澶ф姤鏂囪鍒嗘浼犺緭var segmentSize = 1024 * 5;var i = 0, totalFrameIndex = Math.floor(responseText.length / segmentSize) + 1;for (; i < totalFrameIndex; i++) {var frameData = JSON.stringify({__sekiro_frame_total: totalFrameIndex,__sekiro_index: i,__sekiro_seq__: seq,__sekiro_base64: this.base64,__sekiro_is_frame: true,__sekiro_content: responseText.substring(i * segmentSize, (i + 1) * segmentSize)});console.log("frame: " + frameData);this.socket.send(frameData);}};SekiroClient.prototype.sendFailed = function (seq, errorMessage) {if (typeof errorMessage != 'string') {errorMessage = JSON.stringify(errorMessage);}var responseJson = {};responseJson['message'] = errorMessage;responseJson['status'] = -1;responseJson['__sekiro_seq__'] = seq;var responseText = JSON.stringify(responseJson);console.log("sekiro: response :" + responseText);this.socket.send(responseText)};SekiroClient.prototype.registerAction = function (action, handler) {if (typeof action !== 'string') {throw new Error("an action must be string");}if (typeof handler !== 'function') {throw new Error("a handler must be function");}console.log("sekiro: register action: " + action);this.handlers[action] = handler;return this;};SekiroClient.prototype.encodeWithBase64 = function () {this.base64 = arguments && arguments.length > 0 && arguments[0];};SekiroClient.prototype.base64Encode = function (s) {if (arguments.length !== 1) {throw "SyntaxError: exactly one argument required";}s = String(s);if (s.length === 0) {return s;}function _get_chars(ch, y) {if (ch < 0x80) y.push(ch);else if (ch < 0x800) {y.push(0xc0 + ((ch >> 6) & 0x1f));y.push(0x80 + (ch & 0x3f));} else {y.push(0xe0 + ((ch >> 12) & 0xf));y.push(0x80 + ((ch >> 6) & 0x3f));y.push(0x80 + (ch & 0x3f));}}var _PADCHAR = "=",_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_VERSION = "1.1";//Mr. Ruan fix to 1.1 to support asian char(utf8)//s = _encode_utf8(s);var i,b10,y = [],x = [],len = s.length;i = 0;while (i < len) {_get_chars(s.charCodeAt(i), y);while (y.length >= 3) {var ch1 = y.shift();var ch2 = y.shift();var ch3 = y.shift();b10 = (ch1 << 16) | (ch2 << 8) | ch3;x.push(_ALPHA.charAt(b10 >> 18));x.push(_ALPHA.charAt((b10 >> 12) & 0x3F));x.push(_ALPHA.charAt((b10 >> 6) & 0x3f));x.push(_ALPHA.charAt(b10 & 0x3f));}i++;}switch (y.length) {case 1:var ch = y.shift();b10 = ch << 16;x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _PADCHAR + _PADCHAR);break;case 2:var ch1 = y.shift();var ch2 = y.shift();b10 = (ch1 << 16) | (ch2 << 8);x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _ALPHA.charAt((b10 >> 6) & 0x3f) + _PADCHAR);break;}return x.join("");};function startRpc() {if (window.flag) {} else {function guid() {function S4() {return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);}return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());}// 创建一个标记用来判断是否创建套接字window.flag = true;var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=rpc-test&clientId=" + guid());client.registerAction("ths", function (request, resolve, reject) {resolve(rt.update());})}}setTimeout(startRpc, 1000)
})()
  • 控制台出现一下页面说明注入是成功的
    在这里插入图片描述

  • python代码

import requestsdata = {"group": "rpc-test","action": "ths",
}
res = requests.get("http://127.0.0.1:5620/business-demo/invoke", params=data)
print(res.text)
2. 替换文件注入案例2
1. 逆向目标
  • 地址:https://www.zhipin.com/web/geek/job?query=python&city=101250100&page=3
  • 接口:详情页面__zp_stoken__cookie值
2. 逆向分析
  • 根据关键字定位到cookie生成的位置

在这里插入图片描述

  • 可以看出来n的赋值就是__zp_stoken__生成的位置
  • 那我们的rpc就能在这一块来进行编写,直接调用(new a).z(e, parseInt(t) + 60 * (480 + (new Date).getTimezoneOffset()) * 1e3)
  • 需要用到两个入参,一个是e一个是t,两个参数是来自这个详情页面第一次请求成功之后返回响应头里的数据信息,是服务器返回的

在这里插入图片描述

  • 那我们的就需要先把js文件进行替换,把我们rpc的代码替换到网址当中
  • python代码就需要先请求一次这个网址,获取到响应数据,在发送给接口进行加密
3. 实现代码
  • JavaScript注入代码
(function () {/*Copyright (C) 2020 virjar <virjar@virjar.com> for https://github.com/virjar/sekiroRedistribution and use in source and binary forms, with or withoutmodification, are permitted provided that the following conditions are met:* Redistributions of source code must retain the above copyrightnotice, this list of conditions and the following disclaimer.* Redistributions in binary form must reproduce the above copyrightnotice, this list of conditions and the following disclaimer in thedocumentation and/or other materials provided with the distribution.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THEIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSEARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANYDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED ANDON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OFTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.*/function SekiroClient(wsURL) {this.wsURL = wsURL;this.handlers = {};this.socket = {};this.base64 = false;// checkif (!wsURL) {throw new Error('wsURL can not be empty!!')}this.webSocketFactory = this.resolveWebSocketFactory();this.connect()}SekiroClient.prototype.resolveWebSocketFactory = function () {if (typeof window === 'object') {var theWebSocket = window.WebSocket ? window.WebSocket : window.MozWebSocket;return function (wsURL) {function WindowWebSocketWrapper(wsURL) {this.mSocket = new theWebSocket(wsURL);}WindowWebSocketWrapper.prototype.close = function () {this.mSocket.close();};WindowWebSocketWrapper.prototype.onmessage = function (onMessageFunction) {this.mSocket.onmessage = onMessageFunction;};WindowWebSocketWrapper.prototype.onopen = function (onOpenFunction) {this.mSocket.onopen = onOpenFunction;};WindowWebSocketWrapper.prototype.onclose = function (onCloseFunction) {this.mSocket.onclose = onCloseFunction;};WindowWebSocketWrapper.prototype.send = function (message) {this.mSocket.send(message);};return new WindowWebSocketWrapper(wsURL);}}if (typeof weex === 'object') {// this is weex env : https://weex.apache.org/zh/docs/modules/websockets.htmltry {console.log("test webSocket for weex");var ws = weex.requireModule('webSocket');console.log("find webSocket for weex:" + ws);return function (wsURL) {try {ws.close();} catch (e) {}ws.WebSocket(wsURL, '');return ws;}} catch (e) {console.log(e);//ignore}}//TODO support ReactNativeif (typeof WebSocket === 'object') {return function (wsURL) {return new theWebSocket(wsURL);}}// weex 鍜� PC鐜鐨剋ebsocket API涓嶅畬鍏ㄤ竴鑷达紝鎵€浠ュ仛浜嗘娊璞″吋瀹�throw new Error("the js environment do not support websocket");};SekiroClient.prototype.connect = function () {console.log('sekiro: begin of connect to wsURL: ' + this.wsURL);var _this = this;// 涓峜heck close锛岃// if (this.socket && this.socket.readyState === 1) {//     this.socket.close();// }try {this.socket = this.webSocketFactory(this.wsURL);} catch (e) {console.log("sekiro: create connection failed,reconnect after 2s");setTimeout(function () {_this.connect()}, 2000)}this.socket.onmessage(function (event) {_this.handleSekiroRequest(event.data)});this.socket.onopen(function (event) {console.log('sekiro: open a sekiro client connection')});this.socket.onclose(function (event) {console.log('sekiro: disconnected ,reconnection after 2s');setTimeout(function () {_this.connect()}, 2000)});};SekiroClient.prototype.handleSekiroRequest = function (requestJson) {console.log("receive sekiro request: " + requestJson);var request = JSON.parse(requestJson);var seq = request['__sekiro_seq__'];if (!request['action']) {this.sendFailed(seq, 'need request param {action}');return}var action = request['action'];if (!this.handlers[action]) {this.sendFailed(seq, 'no action handler: ' + action + ' defined');return}var theHandler = this.handlers[action];var _this = this;try {theHandler(request, function (response) {try {_this.sendSuccess(seq, response)} catch (e) {_this.sendFailed(seq, "e:" + e);}}, function (errorMessage) {_this.sendFailed(seq, errorMessage)})} catch (e) {console.log("error: " + e);_this.sendFailed(seq, ":" + e);}};SekiroClient.prototype.sendSuccess = function (seq, response) {var responseJson;if (typeof response == 'string') {try {responseJson = JSON.parse(response);} catch (e) {responseJson = {};responseJson['data'] = response;}} else if (typeof response == 'object') {responseJson = response;} else {responseJson = {};responseJson['data'] = response;}if (typeof response == 'string') {responseJson = {};responseJson['data'] = response;}if (Array.isArray(responseJson)) {responseJson = {data: responseJson,code: 0}}if (responseJson['code']) {responseJson['code'] = 0;} else if (responseJson['status']) {responseJson['status'] = 0;} else {responseJson['status'] = 0;}responseJson['__sekiro_seq__'] = seq;var responseText = JSON.stringify(responseJson);console.log("response :" + responseText);if (responseText.length < 1024 * 6) {this.socket.send(responseText);return;}if (this.base64) {responseText = this.base64Encode(responseText)}//澶ф姤鏂囪鍒嗘浼犺緭var segmentSize = 1024 * 5;var i = 0, totalFrameIndex = Math.floor(responseText.length / segmentSize) + 1;for (; i < totalFrameIndex; i++) {var frameData = JSON.stringify({__sekiro_frame_total: totalFrameIndex,__sekiro_index: i,__sekiro_seq__: seq,__sekiro_base64: this.base64,__sekiro_is_frame: true,__sekiro_content: responseText.substring(i * segmentSize, (i + 1) * segmentSize)});console.log("frame: " + frameData);this.socket.send(frameData);}};SekiroClient.prototype.sendFailed = function (seq, errorMessage) {if (typeof errorMessage != 'string') {errorMessage = JSON.stringify(errorMessage);}var responseJson = {};responseJson['message'] = errorMessage;responseJson['status'] = -1;responseJson['__sekiro_seq__'] = seq;var responseText = JSON.stringify(responseJson);console.log("sekiro: response :" + responseText);this.socket.send(responseText)};SekiroClient.prototype.registerAction = function (action, handler) {if (typeof action !== 'string') {throw new Error("an action must be string");}if (typeof handler !== 'function') {throw new Error("a handler must be function");}console.log("sekiro: register action: " + action);this.handlers[action] = handler;return this;};SekiroClient.prototype.encodeWithBase64 = function () {this.base64 = arguments && arguments.length > 0 && arguments[0];};SekiroClient.prototype.base64Encode = function (s) {if (arguments.length !== 1) {throw "SyntaxError: exactly one argument required";}s = String(s);if (s.length === 0) {return s;}function _get_chars(ch, y) {if (ch < 0x80) y.push(ch);else if (ch < 0x800) {y.push(0xc0 + ((ch >> 6) & 0x1f));y.push(0x80 + (ch & 0x3f));} else {y.push(0xe0 + ((ch >> 12) & 0xf));y.push(0x80 + ((ch >> 6) & 0x3f));y.push(0x80 + (ch & 0x3f));}}var _PADCHAR = "=",_ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_VERSION = "1.1";//Mr. Ruan fix to 1.1 to support asian char(utf8)//s = _encode_utf8(s);var i,b10,y = [],x = [],len = s.length;i = 0;while (i < len) {_get_chars(s.charCodeAt(i), y);while (y.length >= 3) {var ch1 = y.shift();var ch2 = y.shift();var ch3 = y.shift();b10 = (ch1 << 16) | (ch2 << 8) | ch3;x.push(_ALPHA.charAt(b10 >> 18));x.push(_ALPHA.charAt((b10 >> 12) & 0x3F));x.push(_ALPHA.charAt((b10 >> 6) & 0x3f));x.push(_ALPHA.charAt(b10 & 0x3f));}i++;}switch (y.length) {case 1:var ch = y.shift();b10 = ch << 16;x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _PADCHAR + _PADCHAR);break;case 2:var ch1 = y.shift();var ch2 = y.shift();b10 = (ch1 << 16) | (ch2 << 8);x.push(_ALPHA.charAt(b10 >> 18) + _ALPHA.charAt((b10 >> 12) & 0x3F) + _ALPHA.charAt((b10 >> 6) & 0x3f) + _PADCHAR);break;}return x.join("");};// 上面的代码是SekiroClient生成的客户端代码代码function startRpc() {if (window.flag) {} else {function guid() {function S4() {return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);}return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());}// 创建一个标记用来判断是否创建套接字window.flag = true;var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=rpc-test&clientId=" + guid());client.registerAction("boss", function (request, resolve, reject) {e = request['seed'];t = request['ts']n = (new a).z(e, parseInt(t) + 60 * (480 + (new Date).getTimezoneOffset()) * 1e3)resolve(n);})}}setTimeout(startRpc, 1000)})()
  • python连接代码
import requests
import re
from urllib import parsedef get_seed_ts():url = f"https://www.zhipin.com/job_detail/fa99eaf310ebb9681nZ-2tS-FldR.html?lid=20Zu96fzYW2.search.97&securityId=6ThsGuZNTR1PL-D137itvqn6an8wLyzyX2N5_41Gpap1iPd8YnCOgy3WdbjF10uUixQzOT2FuOlljwvbz7qx5-ASxQvUCq2EDr6GWMttfNLsSMsVXA~~&sessionId="headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36",}response = requests.get(url, headers=headers, allow_redirects=False)seed = re.findall('\?seed=(.*?)&', response.headers['location'])[0]seed = parse.unquote(seed)ts = re.findall('&ts=(.*?)&c', response.headers['location'])[0]return seed, tsdef get_sig():seed, ts = get_seed_ts()print(seed, ts)data = {"group": "rpc-test","action": "boss",'seed': seed,'ts': ts}res = requests.post(url="http://127.0.0.1:5620/business-demo/invoke", data=data, verify=False)if res.status_code == 200:return res.json().get('data')def get_index():url = 'https://www.zhipin.com/job_detail/fa99eaf310ebb9681nZ-2tS-FldR.html?lid=20Zu96fzYW2.search.97&securityId=6ThsGuZNTR1PL-D137itvqn6an8wLyzyX2N5_41Gpap1iPd8YnCOgy3WdbjF10uUixQzOT2FuOlljwvbz7qx5-ASxQvUCq2EDr6GWMttfNLsSMsVXA~~&sessionId='token = get_sig()print(token)headers = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36","cookie": f"__zp_stoken__={token}"}res = requests.get(url, headers=headers)res.encoding = 'utf-8'print(res.text)get_index()
3. 油猴注入形式
1. 油猴工具介绍

Tampermonkey是一款免费的浏览器扩展和最为流行的用户脚本管理器,它适用于 Chrome, Microsoft Edge, Safari, Opera Next, 和 Firefox。
以上是油猴官网给出的介绍。它可以让用户自行在添加脚本,并在开启对应页面时应用。如果你了解"脚本注入",你可以把它认为是一个给自己注入脚本的一个工具。

用户油猴脚本:https://greasyfork.org/zh-CN/scripts

大家可以自行娱乐娱乐, 比如说啊VIP电影免费破解

用油猴可以帮助我们一直去动态的监听网址,有些参数可能是刷新网站的时候生成的比较快,控制台没有那么快去执行代码

选项含义
@name脚本的名称
@namespace命名空间,用来区分相同名称的脚本,一般写作者名字或者网址就可以
@version脚本版本,油猴脚本的更新会读取这个版本号
@description描述这个脚本是干什么用的
@author编写这个脚本的作者的名字
@match从字符串的起始位置匹配正则表达式,只有匹配的网址才会执行对应的脚本,例如 * 匹配所有,https://www.baidu.com/* 匹配百度等,可以参考 Python re 模块里面的 re.match() 方法,允许多个实例
@include和 @match 类似,只有匹配的网址才会执行对应的脚本,但是 @include 不会从字符串起始位置匹配,例如 *://*baidu.com/* 匹配百度,具体区别可以参考 TamperMonkey 官方文档[8]
@icon脚本的 icon 图标
@grant指定脚本运行所需权限,如果脚本拥有相应的权限,就可以调用油猴扩展提供的 API 与浏览器进行交互。如果设置为 none 的话,则不使用沙箱环境,脚本会直接运行在网页的环境中,这时候无法使用大部分油猴扩展的 API。如果不指定的话,油猴会默认添加几个最常用的 API
@require如果脚本依赖其他 JS 库的话,可以使用 require 指令导入,在运行脚本之前先加载其它库
@run-at脚本注入时机,该选项是能不能 hook 到的关键,有五个值可选:document-start:网页开始时;document-body:body出现时;document-end:载入时或者之后执行;document-idle:载入完成后执行,默认选项;context-menu:在浏览器上下文菜单中单击该脚本时,一般将其设置为 document-start
2.逆向目标
  • 目标网址:https://www.toutiao.com/
  • 解析:_signature
3. 逆向分析
  • 通过关键字的方式定位到加密位置

在这里插入图片描述

  • 跟I方法看看执行的内容

在这里插入图片描述

  • 可以直接把当前方法导出
  • 油猴代码
// ==UserScript==
// @name        头条-rpc
// @match        https://www.toutiao.com/*
// @grant        none
// @require      http://file.virjar.com/sekiro_web_client.js?_=123
// ==/UserScript==(function() {'use strict';var client = new SekiroClient("ws://127.0.0.1:5620/business-demo/register?group=test&clientId=" + Math.random());client.registerAction("tt", function (request, resolve, reject) {var url = request['url'];  // 接收python传的地址if (!url){reject("url 不能为空")}resolve({"signature": window.byted_acrawler.sign({url}), "cookie": document.cookie})});})();
  • python代码
# encoding: utf-8
"""
@file: 头条测试.py
"""
import urllib3,requests
urllib3.disable_warnings()def get_sig(url):data = {"group": "test","action": "tt","url": url}res = requests.post(url="http://127.0.0.1:5620/business-demo/invoke", data=data, verify=False)resp = res.json()print(res.json())if "?" in url:url += "&_signature={}".format(resp['signature'])else:url += "?_signature={}".format(resp['signature'])return urlurl = get_sig("https://www.toutiao.com/api/pc/list/feed?channel_id=0&max_behot_time=1698925370&offset=0&category=pc_profile_recommend&aid=24&app_name=toutiao_web")
print(url)
headers = {"authority": "www.toutiao.com","accept": "application/json, text/plain, */*","accept-language": "zh-CN,zh;q=0.9","cache-control": "no-cache","pragma": "no-cache","referer": "https://www.toutiao.com/","sec-ch-ua": "^\\^Google","sec-ch-ua-mobile": "?0","sec-ch-ua-platform": "^\\^Windows^^","sec-fetch-dest": "empty","sec-fetch-mode": "cors","sec-fetch-site": "same-origin","user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
}
res = requests.get(url, headers=headers)
print(res.text)
# https://www.toutiao.com/api/pc/list/feed?channel_id=0&min_behot_time=1636703275&refresh_count=2&category=pc_profile_recommend&_signature=_02B4Z6wo00d01KWcaZwAAIDAJZ6T3JmB4wiluG0AAEwpfdsN1DmbuNsUZxKy6hQ9zmq5aoV6APEJmbKSJmmYKcV7Mr4VnVYu3tJ11y1TYvRcyhTGsiq5RdbNdsSdf1msDFZUvL.AAJ-zz4GM34
  • 油猴rpc,适用于全局的方法,要是函数内部的方法导出不了

    结语

以上就是关于js逆向技术中的RPC技术全部内容了,欢迎同学们在评论区讨论交流,有任何js逆向、数据采集相关需求也可以V后台regentwan与我联系哟~

上一篇直通车:【js逆向专题】11.AST节点树

js逆向专题传送门

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/456146.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LabVIEW伺服压机是如何实现压力位移的精度?

LabVIEW伺服压机通过精确的压力和位移控制&#xff0c;实现了高精度的压装操作。为了达到这种精度&#xff0c;系统通常依赖于多个硬件和软件模块的协同工作&#xff0c;包括伺服电机、压力传感器、位移传感器以及LabVIEW的实时控制和数据处理功能。以下是LabVIEW伺服压机如何实…

论文阅读与写作入门

文章目录 1.阅读第一篇论文(1)论文结构(2)目标 2.使用GPT辅助论文的阅读与写作3.专有名词(1)架构(2)网络(3)机器学习 4.文献翻译软件5.从哪里下载文献&#xff1f;6.如何判断(你自己的)研究工作的价值or贡献【论文精读李沐】7.经典论文(1)AlexNet 2012(2)FCN 全卷积 2014(3)Res…

【java面向对象编程】第一弹----类与对象的理解及类和对象的内存分配机制

一、类与对象 1.1类与对象的理解 &#xff08;1&#xff09;类就是数据类型&#xff0c;比如String类 &#xff08;2&#xff09;对象就是一个具体的实例 1.1.2类和对象的区别与联系 1) 类是抽象的&#xff0c;概念的&#xff0c;代表一类事物,比如人类,猫类.., 即它是数据…

异地组网最简单的方法

异地组网的方法多种多样&#xff0c;每种方法都有其特定的优缺点和适用场景&#xff0c;本期梳理一些相对简单且常用的异地组网方法&#xff0c;开始~ 一、使用硬件路由器的 VPN 功能 前提条件 你需要有支持 VPN 功能的路由器&#xff0c;如华硕、中兴等品牌。这些路由器在设置…

Newstar_week1_week2_wp

week1 wp crypto 一眼秒了 n费马分解再rsa flag&#xff1a; import libnum import gmpy2 from Crypto.Util.number import * p 9648423029010515676590551740010426534945737639235739800643989352039852507298491399561035009163427050370107570733633350911691280297…

「C/C++」C++17 之 std::variant 安全的联合体(变体)

#1024程序员节&#xff5c;征文# ✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计…

vue2 el-select赋值无效(无法选中)

背景&#xff1a;点击添加明细时&#xff0c;el-table会新增一条数据&#xff0c;其中&#xff0c;存货原申购用途 会根据 费用承担事业部 下拉框的值改变而改变&#xff0c;所以每次费用承担事业部发生变化时&#xff0c;都需要清空存货原申购用途的值 最开始是直接这样写的&a…

基于Java(SSM框架)+MySQL开发的小型英语学习网站

一、需求分析 英语已经越来越凸显其重要性。大学生一般都需要考CET-4或者CET-6&#xff0c;对于程序员&#xff0c;如果没有扎实的英语基础&#xff0c;看有些API文档也比较费力。生活中处处存在英语&#xff0c;也越来越体现英语的重要性&#xff0c;如何高效学习英语成了关键…

Openlayers高级交互(7/20):点击某点弹出窗口,自动播放视频

在使用 OpenLayers 构建 WebGIS 应用程序时,如果你想在地图上嵌入视频,通常的做法是将视频作为覆盖层(Overlay)添加到地图上。这里是一个示例,点击某点弹出窗口,自动播放视频。 效果图 专栏名称内容介绍Openlayers基础实战 (72篇)专栏提供73篇文章,为小白群体提供基础…

Vxe UI vue vxe-table 虚拟树表格的使用,流畅的渲染万级数据树结构表格

Vxe UI vue vxe-table 虚拟树表格的使用&#xff0c;流畅的渲染万级数据树结构表格 代码 普通树表格&#xff0c;一般存数据库里都是平级数据&#xff0c;vxe-table 的树渲染这就非常友好了&#xff0c;只有带有父子id关联的数组&#xff0c;就可以自动渲染树表格。 <te…

用 Vue 打造高效 Gherkin 自动化测试脚本编写工具

之前曾撰写过一篇有关运用 Pytest - BDD 框架开展自动化测试的文章。行为驱动开发&#xff08;BDD&#xff09;因着重于以业务需求和用户行为作为导向来编写测试脚本&#xff0c;故而广受关注。Gherkin 作为 BDD 中常用的语言&#xff0c;用于描述测试场景与步骤&#xff0c;具…

C#学习笔记(五)

C#学习笔记&#xff08;五&#xff09; 第 三 章 基本语句以及语法一、控制台的基本语句使用1. 方法重载2. 输入输出3.字符串格式化 二、赋值运算符、算数运算符、比较运算符三、数据类型转换常用方法使用、比较和选择1. 自动类型转换(隐式转换)2. 强制类型转换2.1 数值类型之间…

k8s-service详解

Service介绍 在kubernetes中&#xff0c;pod是应用程序的载体&#xff0c;我们可以通过pod的ip来访问应用程序&#xff0c;但是pod的ip地址不是固定的&#xff0c;这也就意味着不方便直接采用pod的ip对服务进行访问。 为了解决这个问题&#xff0c;kubernetes提供了Service资源…

COVON全意卫生巾,轻薄透气,绵柔速干,马来西亚热销中

随着女性健康意识的提高&#xff0c;卫生巾作为女性日常生活中的必需品&#xff0c;其品质和舒适度越来越受到关注。今天&#xff0c;我们要为大家介绍一款来自马来西亚热销的卫生巾——COVON全意卫生巾&#xff0c;以其轻薄透气、绵柔速干的特点&#xff0c;赢得了广大女性的喜…

故障诊断 | CNN-GRU卷积神经网络-门控循环单元组合模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | CNN-GRU卷积神经网络-门控循环单元组合模型的故障诊断(Matlab) 源码设计 %% CNN-GRU多特征分类预测

ORA-12170: TNS:Connect timeout occurred

tnsping xxx通 telnet xxx 端口通 sqlplus xxx/xxx提示ORA-12170: TNS:Connect timeout occurred 沟通了解到&#xff0c;目标端为oracle rac两节点&#xff0c;目前只开通了scan ip的端口策略。 处理办法&#xff1a; 开通两个vip的端口策略。 由于本次连接只是测试用的…

CSS综合案例——新闻详情

一、知识点 1、文字颜色 属性名&#xff1a;color 属性值&#xff1a; 颜色表示方式属性值说明使用场景颜色关键字颜色英文单词red,green,blue学习测试rgb表示法rg(r,g,b)r,g,b表示红绿蓝三原色&#xff0c;取值0-255了解rgba表示法rgba(r,g,b,a)a表示透明度&#xff0c;取…

Android 自定义 Dialog 实现列表 单选,多选,搜索

前言 在Android开发中&#xff0c;通过对话框让用户选择&#xff0c;筛选信息是很方便也很常见的操作。本文详细介绍了如何使用自定义 Dialog、RecyclerView 以及自定义搜索框 来实现选中状态和用户交互&#xff0c;文中大本分代码都有明确注释&#xff0c;主打一个简单明了&a…

JS | 如何使用 JavaScript 实现图片懒加载的淡入效果?

在现代的网页设计中&#xff0c;为了提高用户体验和网站性能&#xff0c;图片懒加载已经成为一个常见的技术。图片懒加载可以减少初始加载时间&#xff0c;延迟加载图片直到用户滚动到它们所在的位置。另外&#xff0c;为了进一步提升用户体验&#xff0c;添加淡入效果可以使页…

【忍无可忍,无需再忍】永久解决xshell or xftp 更新问题

1 背景介绍 提示“要继续使用此程序,您必须应用最新的更新或使用新版本”&#xff0c;笔者版本是xshell 6 距离一段时间不使用&#xff0c;或者遇到更新场景&#xff0c;总是会弹出这个框&#xff0c;实在是忍无可忍&#xff0c;无需再忍。 2 思路介绍 笔者上一篇关于如何解…