RPC 远程调用

RPC(Remote Procedure Call)方案不需要将加密代码扣出来补环境,而是让加密代码继续跑在浏览器原始环境中,通过 WebSocket 通道暴露给外部调用。 适合:加密逻辑极其复杂、补环境成本过高、或对运行速度要求不高的场景。 Sekiro(https://github.com/virjar/sekiro) 是专为 RPC 场景设计的框架,支持 Java/Node.js 服务端,提供完整的路由和多客户端管理。 浏览器端注入(使用 Sekiro JS SDK): Python 调用: Python 服务端用 asyncio + websoc

分享

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

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

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


核心架构

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

最简实现

服务端(Node.js WebSocket)

// 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 中执行)

// 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 调用端

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 是专为 RPC 场景设计的框架,支持 Java/Node.js 服务端,提供完整的路由和多客户端管理。

浏览器端注入(使用 Sekiro JS SDK):

// 引入 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 调用:

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.EventFuture 等待特定 ID 的响应。


参见

补环境
jsdom完全指南
js逆向调试技巧
mitmproxy完全指南

阅读更多

Web 安全基础

1. HTML 转义(服务端渲染必须): 2. CSP(Content Security Policy): 3. HttpOnly Cookie:防止 JS 读取会话 Cookie: 4. 前端框架防护: 攻击者在第三方网站构造一个表单,诱导已登录用户提交,浏览器会自动携带目标站的 Cookie。 触发条件: 1. 用户已登录目标网站(Cookie 有效) 2. 目标 API 仅凭 Cookie 识别用户身份 3. 请求来源未验证 1. CSRF Token(推荐): 2. SameSite Cookie: 3. 验证 Origin/Referer 头:

By yellowdog

HTTP 协议深度指南

HTTP(HyperText Transfer Protocol)是 Web 的基础传输协议,基于 TCP/IP,采用请求/响应模型。 相关文档:Web安全基础(/web-an-quan-ji-chu/) FastAPI完全指南(/fastapi-wan-quan-zhi-nan/) Nginx完全指南(/nginx-wan-quan-zhi-nan/) 幂等性:多次执行相同请求,服务器状态结果相同。PUT /users/1 多次执行结果一致;POST /users 每次创建新资源,非幂等。 浏览器直接从本地缓存读取,不向服务器发送请求。 缓存命中时,状

By yellowdog

系统设计基础

SLA 对照表: 选择建议:无状态服务(Web 层、API 层)优先水平扩展;数据库初期垂直扩展,达到瓶颈后考虑分库分表或读写分离。 缓存穿透(查询不存在的 key,每次都打到 DB): 缓存击穿(热点 key 过期,瞬间大量请求打到 DB): 缓存雪崩(大量 key 同时过期,或缓存服务宕机): 令牌桶 Python 实现: Redis 实现分布式限流(滑动窗口): URL 命名规则: Cursor 分页响应格式: 雪花算法结构(64 bit): 定义:分布式系统不能同时满足以下三个特性: 在分布式环境中 P 是必须保证的,所以实际是 CP vs AP

By yellowdog

算法思路与模板

二分查找要求序列有序,每次将搜索范围缩减一半,时间复杂度 O(log n)。 两个指针从两端向中间收缩,常用于有序数组。 滑动窗口维护一个满足条件的区间 left, right,right 不断向右扩张,条件不满足时收缩 left。 滑动窗口通用框架: 1. 确定"子问题":原问题可以分解为哪些规模更小的同类问题 2. 定义 dpi 或 dpij 的含义,要足够清晰 3. 推导状态转移方程 4. 确定初始状态(边界条件) 5. 确定计算顺序(确保依赖的子问题先计算) 每件物品最多选一次。dpj = 容量为 j 时的最大价值,逆序遍历容量防止重复选取。 每

By yellowdog