> ## Content Index
> Fetch the complete content index at: https://blog.vercanti.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# RPC 远程调用
- URL: https://blog.vercanti.com/rpc-yuan-cheng-diao-yong/
- Published: 2026-08-28T14:35:08.000Z
- Updated: 2026-08-28T14:58:08.000Z
- Description: RPC（Remote Procedure Call）方案不需要将加密代码扣出来补环境，而是让加密代码继续跑在浏览器原始环境中，通过 WebSocket 通道暴露给外部调用。 适合：加密逻辑极其复杂、补环境成本过高、或对运行速度要求不高的场景。 Sekiro(https://github.com/virjar/sekiro) 是专为 RPC 场景设计的框架，支持 Java/Node.js 服务端，提供完整的路由和多客户端管理。 浏览器端注入（使用 Sekiro JS SDK）： Python 调用： Python 服务端用 asyncio + websoc
- Author: yellowdog
- Tags: js逆向, 技巧步骤

> 官方文档：<https://developer.mozilla.org/zh-CN/docs/Web/API/WebSockets%5FAPI>  
> 适用场景：加密逻辑过于复杂难以扣代码时，通过 WebSocket 让浏览器直接执行加密并返回结果

RPC（Remote Procedure Call）方案不需要将加密代码扣出来补环境，而是让加密代码继续跑在浏览器原始环境中，通过 WebSocket 通道暴露给外部调用。

适合：加密逻辑极其复杂、补环境成本过高、或对运行速度要求不高的场景。

---

## 核心架构

```
Python 脚本 (客户端)
      |
      | WebSocket 发送参数
      v
本地 WebSocket 服务端 (Node.js 或 Python)
      |
      | 转发给注入的浏览器端代码
      v
浏览器 JS 环境 (已注入 RPC 客户端)
      |
      | 调用目标加密函数，返回结果
      v
Python 脚本拿到加密结果

```

---

## 最简实现

### 服务端（Node.js WebSocket）

```javascript
// server.js
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 23333 });

const clients = new Set();

wss.on('connection', function(ws) {
    clients.add(ws);
    console.log('浏览器已连接');

    ws.on('message', function(message) {
        // 将浏览器的响应转发给等待的 Python 请求
        console.log('收到响应:', message.toString());
    });

    ws.on('close', function() {
        clients.delete(ws);
    });
});

console.log('RPC Server 运行在 ws://localhost:23333');

```

### 注入浏览器端（在 Console 或 Tampermonkey 中执行）

```javascript
// browser_client.js - 注入到目标页面
(function() {
    var ws = new WebSocket('ws://localhost:23333');

    ws.onopen = function() {
        console.log('RPC 客户端已连接');
    };

    ws.onmessage = function(event) {
        var msg = JSON.parse(event.data);
        var result;

        try {
            // 调用页面中的加密函数
            // 根据实际情况修改
            result = window._encrypt(msg.params);
        } catch(e) {
            result = { error: e.message };
        }

        ws.send(JSON.stringify({
            id: msg.id,
            result: result
        }));
    };
})();

```

### Python 调用端

```python
import asyncio
import websockets
import json
import uuid

async def call_browser_encrypt(params):
    async with websockets.connect('ws://localhost:23333') as ws:
        msg_id = str(uuid.uuid4())
        await ws.send(json.dumps({'id': msg_id, 'params': params}))
        response = await ws.recv()
        return json.loads(response)['result']

# 使用示例
result = asyncio.run(call_browser_encrypt({'data': 'hello'}))
print(result)

```

---

## Sekiro 框架

[Sekiro](https://github.com/virjar/sekiro) 是专为 RPC 场景设计的框架，支持 Java/Node.js 服务端，提供完整的路由和多客户端管理。

**浏览器端注入（使用 Sekiro JS SDK）：**

```javascript
// 引入 sekiro 客户端脚本后
var client = new SekiroClient("ws://localhost:5620/business-demo/register?group=ws-group&clientId=" + Math.random());

client.registerAction("getEncryptedSign", function(request, resolve, reject) {
    try {
        var data = request['data'];
        var sign = window._generateSign(data);  // 调用页面加密函数
        resolve(sign);
    } catch(e) {
        reject(e.message);
    }
});

```

**Python 调用：**

```python
import requests

def get_sign(data):
    resp = requests.get('http://localhost:5620/business-demo/invoke', params={
        'group': 'ws-group',
        'action': 'getEncryptedSign',
        'data': data
    })
    return resp.json()['data']

```

---

## 优缺点对比

| 维度   | RPC 方案      | 扣代码补环境     |
| ---- | ----------- | ---------- |
| 开发成本 | 低（无需分析算法细节） | 高（需完整还原环境） |
| 运行速度 | 慢（网络延迟）     | 快（本地直接调用）  |
| 稳定性  | 依赖浏览器保持打开   | 独立运行，稳定    |
| 可维护性 | 网站更新后通常自动适配 | 网站更新后需重新分析 |
| 适用场景 | 临时脚本、复杂环境   | 生产环境、高并发   |

---

## 注意事项

- 浏览器端 WebSocket 连接到本地服务，不会产生跨域问题（ws 协议不受同源策略限制）
- 如果网站 CSP 限制了 WebSocket 连接，可通过 mitmproxy 修改响应头绕过
- 多并发场景下需在服务端维护请求队列，避免响应错位

---

## 最佳实践

**Python 服务端用 `asyncio` \+ `websockets` 库**：异步 WebSocket 服务可以轻松处理多个并发请求；用 `asyncio.Queue` 维护请求队列，保证顺序；每个请求带唯一 ID，响应中携带同一 ID 匹配，避免并发时响应错位。

**Tampermonkey 脚本注入 WebSocket 客户端**：在 Tampermonkey 脚本中 `@run-at document-start` 建立 WebSocket 连接，Hook 目标函数后通过 WebSocket 接收参数、调用函数、发送结果，整个流程在浏览器内运行，完全真实环境。

**用 `requestId` 匹配请求和响应**：高并发时 Python 同时发出多个请求，浏览器按事件循环顺序处理不保证顺序；每个请求带 `{ "id": uuid, "params": ... }`，响应带同一 `id`，Python 用字典匹配。

**设置超时机制保护主流程**：浏览器 tab 关闭或卡住时 WebSocket 不一定立即报错；Python 服务端每次请求设置 `asyncio.wait_for(timeout=10)` 超时，超时后重试或降级处理。

**RPC 适合打 PoC，补环境适合生产**：RPC 依赖浏览器实例持续运行，无法水平扩展；验证可行性后，对高频调用的签名函数仍然值得投入时间做补环境，性能和稳定性会更好。

---

## 常见陷阱

### 陷阱：WebSocket 建立后过一会儿自动断开

**现象：** RPC 服务正常运行一段时间后，突然开始报连接关闭错误。  
**原因：** 部分 WebSocket 服务器（或浏览器）在空闲超时后关闭连接；或网站有 CSRF/token 检测，发现 WebSocket 来源可疑后断开。  
**解决：** 在 Tampermonkey 脚本中加心跳（每 30 秒发送 ping），断开后自动重连；Python 服务端监听 `close` 事件并重建连接。

### 陷阱：CSP 阻止了 WebSocket 连接

**现象：** Tampermonkey 脚本中 `new WebSocket('ws://localhost:8080')` 报错 `Refused to connect`。  
**原因：** 目标网站的 `Content-Security-Policy` 响应头限制了 `connect-src`，不允许连接到 localhost。  
**解决：** 用 mitmproxy 拦截目标站响应，在 `response` 钩子中修改 `Content-Security-Policy` 头，移除或放宽 `connect-src` 限制。

### 陷阱：并发请求时响应错位

**现象：** 高并发时部分请求拿到了其他请求的签名结果，导致业务逻辑出错。  
**原因：** 没有用请求 ID 做匹配，响应按到达顺序返回，并发时顺序不保证。  
**解决：** 每个请求携带唯一 `requestId`（UUID），响应带同一 `requestId`，Python 用 `asyncio.Event` 或 `Future` 等待特定 ID 的响应。

---

## 参见

[补环境](https://blog.vercanti.com/bu-huan-jing/)  
[jsdom完全指南](https://blog.vercanti.com/jsdom-wan-quan-zhi-nan-jsni-xiang-shi-zhan/)  
[js逆向调试技巧](https://blog.vercanti.com/js-ni-xiang-diao-shi-ji-qiao/)  
[mitmproxy完全指南](https://blog.vercanti.com/mitmproxy-wan-quan-zhi-nan/)