Files
workphone-sdk/sdk/agent/hook/wireless_bridge.py

367 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
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()