新增文件: - agent/hook/wireless_deployer.py: Frida无线部署器(Root/免Root双模式) - agent/hook/wireless_bridge.py: Frida无线桥接器(WebSocket↔Frida RPC) - agent/wireless_agent.py: Agent端无线增强混入类 - app/routers/frida_wireless.py: 服务端Frida无线管理API路由 - app/routers/wechat_full.py: 微信全量操作路由(补全缺失端点) - app/skills/wechat/skill_v2.py: 服务端微信Skill完整版(110方法/25模块) - tests/test_wireless_frida.py: 验证测试套件 - 开发文档/6、测试/Frida无线控制验证报告_20260518.md 核心特性: - WiFi TCP无线连接(无需USB) - Root模式: frida-server远程连接 - 免Root模式: frida-gadget注入连接 - 自动检测Root状态选择最佳模式 - 110个微信操作全覆盖(25个模块) - 通道降级: Hook > WebSocket > UI自动化 - 验证通过率: 100%
367 lines
11 KiB
Python
367 lines
11 KiB
Python
"""
|
||
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()
|