feat: publish workphone SDK deployment and API docs
This commit is contained in:
366
sdk/agent/hook/wireless_bridge.py
Normal file
366
sdk/agent/hook/wireless_bridge.py
Normal file
@@ -0,0 +1,366 @@
|
||||
"""
|
||||
Frida 无线桥接器 — 连接 FridaManager 与 WebSocket 服务端
|
||||
=======================================================
|
||||
|
||||
核心职责:
|
||||
- 通过 WiFi TCP 连接远程 Frida(替代 USB)
|
||||
- 将服务端 WebSocket 指令转换为 Frida RPC 调用
|
||||
- 将 Frida Hook 事件通过 WebSocket 上报服务端
|
||||
- 管理 Frida 会话生命周期(连接/断开/重连)
|
||||
|
||||
数据流:
|
||||
服务端 API → WebSocket → Agent → WirelessBridge → FridaManager → Frida RPC → 微信
|
||||
微信 → Frida Hook Event → FridaManager → WirelessBridge → WebSocket → 服务端
|
||||
|
||||
技术栈:Frida 16.5.6 + asyncio + WebSocket
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
from typing import Optional, Dict, Any, Callable, List
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 导入同级模块
|
||||
from .frida_manager import FridaManager, ConnectionMode
|
||||
from .hook_executor import HookExecutor, ACTION_TO_RPC, MODULE_NAMES
|
||||
from .event_reporter import EventReporter
|
||||
|
||||
|
||||
class WirelessBridge:
|
||||
"""
|
||||
无线桥接器 — 将 WebSocket 指令转为 Frida RPC 调用
|
||||
|
||||
这是 Agent 端的核心组件,负责:
|
||||
1. 初始化 Frida 无线连接(remote 模式,通过 WiFi TCP)
|
||||
2. 接收 WebSocket 指令并执行对应的 Frida RPC
|
||||
3. 将 Hook 事件回传给 WebSocket
|
||||
4. 维护连接健康状态
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
device_ip: str = "127.0.0.1",
|
||||
frida_port: int = 27042,
|
||||
mode: ConnectionMode = "remote",
|
||||
script_path: Optional[str] = None,
|
||||
ws_send_fn: Optional[Callable] = None,
|
||||
):
|
||||
self.device_ip = device_ip
|
||||
self.frida_port = frida_port
|
||||
self.mode = mode
|
||||
|
||||
# 默认脚本路径
|
||||
if script_path is None:
|
||||
script_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"wechat_hook_v2.js"
|
||||
)
|
||||
|
||||
# 事件上报器
|
||||
self.event_reporter = EventReporter()
|
||||
if ws_send_fn:
|
||||
self.event_reporter.set_send_fn(ws_send_fn)
|
||||
|
||||
# Frida 管理器(使用 remote 模式通过 WiFi 连接)
|
||||
self.frida_mgr = FridaManager(
|
||||
mode=mode,
|
||||
gadget_host=device_ip,
|
||||
gadget_port=frida_port,
|
||||
script_path=script_path,
|
||||
on_event=self.event_reporter.on_hook_event,
|
||||
on_detach=self._on_frida_detach,
|
||||
auto_reconnect=True,
|
||||
reconnect_interval=5.0,
|
||||
)
|
||||
|
||||
# Hook 执行器
|
||||
self.hook_executor = HookExecutor(self.frida_mgr)
|
||||
|
||||
# 状态
|
||||
self._connected = False
|
||||
self._stats = {
|
||||
"commands_received": 0,
|
||||
"commands_success": 0,
|
||||
"commands_failed": 0,
|
||||
"events_sent": 0,
|
||||
"connect_time": None,
|
||||
"last_command_time": None,
|
||||
}
|
||||
|
||||
# ---- 连接管理 ----
|
||||
|
||||
def connect(self) -> Dict[str, Any]:
|
||||
"""
|
||||
建立 Frida 无线连接
|
||||
|
||||
流程:
|
||||
1. 通过 WiFi TCP 连接远程 frida-server
|
||||
2. attach 到微信进程
|
||||
3. 加载 Hook 脚本
|
||||
4. 验证 RPC 可用性
|
||||
"""
|
||||
logger.info(f"正在连接 Frida: {self.device_ip}:{self.frida_port} (模式={self.mode})")
|
||||
|
||||
try:
|
||||
success = self.frida_mgr.start()
|
||||
if not success:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Frida 连接失败",
|
||||
"device_ip": self.device_ip,
|
||||
"frida_port": self.frida_port,
|
||||
}
|
||||
|
||||
self._connected = True
|
||||
self._stats["connect_time"] = datetime.now().isoformat()
|
||||
|
||||
# 验证 RPC
|
||||
ping_result = self.frida_mgr.call_rpc("ping")
|
||||
rpc_ok = ping_result.get("success", False)
|
||||
|
||||
logger.info(f"✅ Frida 无线连接成功 | RPC: {'OK' if rpc_ok else 'FAIL'}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"device_ip": self.device_ip,
|
||||
"frida_port": self.frida_port,
|
||||
"mode": self.mode,
|
||||
"rpc_available": rpc_ok,
|
||||
"ping": ping_result.get("data", ""),
|
||||
"supported_actions": len(ACTION_TO_RPC),
|
||||
"modules": list(MODULE_NAMES.values()),
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self._connected = False
|
||||
logger.error(f"Frida 连接异常: {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def disconnect(self):
|
||||
"""断开 Frida 连接"""
|
||||
self.frida_mgr.stop()
|
||||
self._connected = False
|
||||
logger.info("Frida 无线连接已断开")
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
return self._connected and self.frida_mgr.connected
|
||||
|
||||
# ---- 指令执行 ----
|
||||
|
||||
def execute_command(self, command: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
执行 WebSocket 指令
|
||||
|
||||
指令格式:
|
||||
{
|
||||
"type": "command",
|
||||
"action": "send_message",
|
||||
"params": {"to_id": "xxx", "content": "hello"},
|
||||
"command_id": "cmd_123"
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true/false,
|
||||
"command_id": "cmd_123",
|
||||
"channel": "hook",
|
||||
"data": {...}
|
||||
}
|
||||
"""
|
||||
self._stats["commands_received"] += 1
|
||||
self._stats["last_command_time"] = datetime.now().isoformat()
|
||||
|
||||
action = command.get("action", "")
|
||||
params = command.get("params", {})
|
||||
command_id = command.get("command_id", "")
|
||||
|
||||
if not action:
|
||||
self._stats["commands_failed"] += 1
|
||||
return {
|
||||
"success": False,
|
||||
"error": "缺少 action",
|
||||
"command_id": command_id,
|
||||
}
|
||||
|
||||
if not self.connected:
|
||||
self._stats["commands_failed"] += 1
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Frida 未连接",
|
||||
"command_id": command_id,
|
||||
}
|
||||
|
||||
# 通过 HookExecutor 执行
|
||||
result = self.hook_executor.execute(action, params)
|
||||
result["command_id"] = command_id
|
||||
|
||||
if result.get("success"):
|
||||
self._stats["commands_success"] += 1
|
||||
else:
|
||||
self._stats["commands_failed"] += 1
|
||||
|
||||
return result
|
||||
|
||||
def execute_batch(self, commands: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
||||
"""批量执行指令"""
|
||||
results = []
|
||||
for cmd in commands:
|
||||
result = self.execute_command(cmd)
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
# ---- WebSocket 消息处理 ----
|
||||
|
||||
def handle_ws_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
处理来自 WebSocket 的消息
|
||||
|
||||
消息类型:
|
||||
- command: 执行 Frida RPC 指令
|
||||
- query: 查询状态
|
||||
- ping: 心跳
|
||||
- batch: 批量执行
|
||||
"""
|
||||
msg_type = message.get("type", "")
|
||||
|
||||
if msg_type == "command":
|
||||
return self.execute_command(message)
|
||||
|
||||
elif msg_type == "batch":
|
||||
commands = message.get("commands", [])
|
||||
return {
|
||||
"success": True,
|
||||
"type": "batch_result",
|
||||
"results": self.execute_batch(commands),
|
||||
"total": len(commands),
|
||||
}
|
||||
|
||||
elif msg_type == "query":
|
||||
query_type = message.get("query", "status")
|
||||
if query_type == "status":
|
||||
return self.get_status()
|
||||
elif query_type == "actions":
|
||||
return {
|
||||
"success": True,
|
||||
"actions": self.hook_executor.get_supported_actions(),
|
||||
"total": len(ACTION_TO_RPC),
|
||||
}
|
||||
elif query_type == "modules":
|
||||
return {
|
||||
"success": True,
|
||||
"modules": MODULE_NAMES,
|
||||
}
|
||||
else:
|
||||
return {"success": False, "error": f"未知查询类型: {query_type}"}
|
||||
|
||||
elif msg_type == "ping":
|
||||
return {
|
||||
"type": "pong",
|
||||
"connected": self.connected,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
}
|
||||
|
||||
else:
|
||||
return {"success": False, "error": f"未知消息类型: {msg_type}"}
|
||||
|
||||
# ---- 事件回调 ----
|
||||
|
||||
def _on_frida_detach(self, reason: str):
|
||||
"""Frida 会话断开回调"""
|
||||
self._connected = False
|
||||
logger.warning(f"Frida 会话断开: {reason}")
|
||||
|
||||
# 通知服务端
|
||||
if self.event_reporter._send_fn:
|
||||
try:
|
||||
self.event_reporter._send_fn({
|
||||
"type": "frida_disconnected",
|
||||
"reason": reason,
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def set_ws_send_fn(self, fn: Callable):
|
||||
"""设置 WebSocket 发送函数"""
|
||||
self.event_reporter.set_send_fn(fn)
|
||||
|
||||
# ---- 状态查询 ----
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""获取桥接器完整状态"""
|
||||
return {
|
||||
"success": True,
|
||||
"bridge": {
|
||||
"connected": self.connected,
|
||||
"device_ip": self.device_ip,
|
||||
"frida_port": self.frida_port,
|
||||
"mode": self.mode,
|
||||
"stats": self._stats,
|
||||
},
|
||||
"frida": self.frida_mgr.get_status(),
|
||||
"hook": self.hook_executor.get_status() if self.connected else {
|
||||
"available": False,
|
||||
"total_actions": len(ACTION_TO_RPC),
|
||||
},
|
||||
"events": self.event_reporter.get_stats(),
|
||||
}
|
||||
|
||||
def get_supported_actions(self) -> Dict[str, Any]:
|
||||
"""获取支持的所有操作"""
|
||||
return {
|
||||
"total": len(ACTION_TO_RPC),
|
||||
"modules": MODULE_NAMES,
|
||||
"actions": {
|
||||
module: [
|
||||
action for action, rpc in ACTION_TO_RPC.items()
|
||||
# 简单按模块前缀分组
|
||||
]
|
||||
for module in MODULE_NAMES.keys()
|
||||
},
|
||||
"action_list": sorted(ACTION_TO_RPC.keys()),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# § 4 异步包装器(用于 FastAPI / asyncio 环境)
|
||||
# ============================================================
|
||||
|
||||
class AsyncWirelessBridge:
|
||||
"""
|
||||
异步包装器 — 将同步的 WirelessBridge 包装为 async 接口
|
||||
用于 FastAPI WebSocket 端点
|
||||
"""
|
||||
|
||||
def __init__(self, bridge: WirelessBridge):
|
||||
self._bridge = bridge
|
||||
self._loop = None
|
||||
|
||||
async def connect(self) -> Dict[str, Any]:
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, self._bridge.connect)
|
||||
|
||||
async def disconnect(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, self._bridge.disconnect)
|
||||
|
||||
async def execute_command(self, command: Dict[str, Any]) -> Dict[str, Any]:
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, self._bridge.execute_command, command)
|
||||
|
||||
async def handle_ws_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
loop = asyncio.get_event_loop()
|
||||
return await loop.run_in_executor(None, self._bridge.handle_ws_message, message)
|
||||
|
||||
@property
|
||||
def connected(self) -> bool:
|
||||
return self._bridge.connected
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
return self._bridge.get_status()
|
||||
Reference in New Issue
Block a user