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

embedded/2024/10/25 18:07:07/

目录

      • 一. 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>javascript">// 与服务器约定的连接 以及端口  本机的   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_235">二. RPC

RPC__237">1. RPC 简介

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

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

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

在这里插入图片描述

RPC_248">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>javascript">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.ppmy.cn/embedded/132390.html

相关文章

CTF-RE 从0到N: 理解base64算法

Base64是一种常用的编码方式&#xff0c;用于将二进制数据转换为文本格式。它常用于在需要通过文本传输二进制数据的场景中&#xff0c;例如在电子邮件和URL中传递二进制文件。Base64的核心原理如下&#xff1a; 原理 输入数据分组&#xff1a; 将输入的二进制数据按字节分成每…

社交媒体与客户服务:新时代的沟通桥梁

在数字化时代&#xff0c;社交媒体已成为人们日常生活中不可或缺的一部分&#xff0c;它不仅改变了人们的沟通方式&#xff0c;也深刻影响着企业的客户服务模式。从传统的电话、邮件到如今的社交媒体平台&#xff0c;客户服务的渠道正在经历一场前所未有的变革。社交媒体以其即…

深入探索卷积神经网络(CNN):图像分类的利器

深入探索卷积神经网络&#xff08;CNN&#xff09;&#xff1a;图像分类的利器 前言CNN的崛起&#xff1a;为何我们需要它&#xff1f;图像卷积&#xff1a;CNN的基石轮廓过滤器&#xff1a;捕捉边缘特征 图像池化&#xff1a;降低维度的利器CNN的组成&#xff1a;卷积层、池化…

草地杂草数据集野外草地数据集田间野草数据集YOLO格式VOC格式目标检测计算机视觉数据集

一、数据集概述 数据集名称&#xff1a;杂草图像数据集 数据集是一个包含野草种类的集合&#xff0c;其中每种野草都有详细的特征描述和标记。这些数据可以包括野草的图片、生长习性、叶片形状、颜色等特征。 1.1可能应用的领域 农业领域: 农业专家和农民可以利用这一数据集来…

AI 通俗解读统计学和机器学习的主要区别

统计学和机器学习虽然都涉及数据分析和建模&#xff0c;但它们的核心理念、方法和应用场景有所不同。以下是它们主要的区别&#xff1a; 生活化例子 统计学&#xff1a;想象你是一个侦探&#xff0c;通过找出过去的证据&#xff08;数据&#xff09;&#xff0c;分析事情发生…

记录一次线上环境svchost.exe antimalware service executable 进程占用CPU过高问题

博主介绍&#xff1a; 大家好&#xff0c;我是想成为Super的Yuperman&#xff0c;互联网宇宙厂经验&#xff0c;17年医疗健康行业的码拉松奔跑者&#xff0c;曾担任技术专家、架构师、研发总监负责和主导多个应用架构。 技术范围&#xff1a; 目前专注java体系&#xff0c;有多…

怎么做系统性能优化

对于软件或系统的性能优化&#xff0c;可以采取多种措施来提高效率和响应速度。这里为您列举一些常见的方法&#xff1a; 1. 代码优化&#xff1a;检查并优化算法复杂度&#xff0c;减少不必要的计算。使用更高效的数据结构和算法。 2. 数据库优化&#xff1a; •索引优化&…

【含开题报告+文档+PPT+源码】基于springboot的旅游路线推荐系统的设计与实现

开题报告 随着互联网和移动互联网的普及&#xff0c;人们获取信息的渠道变得更加便利和多样化。旅游者不再满足于传统的旅游指南和旅行社推荐&#xff0c;他们更倾向于通过网络平台获取个性化、多样化的旅游推荐信息。因此&#xff0c;旅游推荐管理系统应运而生&#xff0c;为…