Files
workphone-sdk/sdk/agent/wireless_agent.py

302 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.

"""
无线 Agent 增强模块 — 为 WorkPhoneAgent 添加 WiFi Frida 支持
=============================================================
功能:
- 自动检测 Root 状态,选择最佳 Frida 连接模式
- 通过 WiFi TCP 连接 Frida无需 USB
- 集成 WirelessBridge 处理服务端指令
- 向服务端注册 Frida 连接信息
- 支持 frida-serverRoot和 frida-gadget免Root
使用方式:
在 agent.py 的 __init__ 中调用 init_wireless()
或在 Termux 中设置环境变量:
WP_FRIDA_MODE=remote
WP_FRIDA_HOST=127.0.0.1
WP_FRIDA_PORT=27042
@author 卡若
@version 1.0.0
"""
import os
import json
import time
import logging
import subprocess
import asyncio
from typing import Optional, Dict, Any
logger = logging.getLogger(__name__)
class WirelessAgentMixin:
"""
无线 Agent 混入类
将此类混入 WorkPhoneAgent为其添加 WiFi Frida 支持。
新增属性:
- wireless_bridge: WirelessBridge 实例
- frida_mode: 当前 Frida 连接模式
- frida_host: Frida 服务地址
- frida_port: Frida 服务端口
"""
def init_wireless(self):
"""
初始化无线 Frida 连接
优先级:
1. 环境变量 WP_FRIDA_MODE / WP_FRIDA_HOST / WP_FRIDA_PORT
2. 自动检测 Root → remote 模式
3. 非 Root → gadget 模式
"""
self.frida_mode = os.environ.get("WP_FRIDA_MODE", "auto").strip().lower()
self.frida_host = os.environ.get("WP_FRIDA_HOST", "127.0.0.1").strip()
self.frida_port = int(os.environ.get("WP_FRIDA_PORT", "27042"))
# 自动检测模式
if self.frida_mode == "auto":
if self._check_root():
self.frida_mode = "remote"
logger.info("✅ Root 检测通过 → 使用 remote 模式frida-server")
else:
self.frida_mode = "gadget"
logger.info("⚠️ 无 Root → 使用 gadget 模式frida-gadget")
# 确保 frida-server 正在运行Root 模式)
if self.frida_mode == "remote":
self._ensure_frida_server_running()
# 初始化 FridaManager使用 WiFi 模式)
self._init_frida_wireless()
# 初始化 WirelessBridge
self._init_wireless_bridge()
logger.info(f"📡 无线 Frida 初始化完成: {self.frida_mode} ({self.frida_host}:{self.frida_port})")
def _check_root(self) -> bool:
"""检查是否有 Root 权限"""
try:
result = subprocess.run(
["su", "-c", "id"],
capture_output=True, text=True, timeout=5,
)
return "uid=0" in result.stdout
except Exception:
return False
def _ensure_frida_server_running(self):
"""确保 frida-server 正在运行"""
try:
# 检查端口是否已监听
result = subprocess.run(
["su", "-c", f"netstat -tlnp 2>/dev/null | grep :{self.frida_port}"],
capture_output=True, text=True, timeout=5, shell=True,
)
if str(self.frida_port) in result.stdout:
logger.info(f"frida-server 已在运行 (端口 {self.frida_port})")
return
# 尝试启动
frida_bin = os.path.expanduser("~/.frida-server/frida-server")
if not os.path.exists(frida_bin):
# 查找其他可能的路径
for path in [
"/data/local/tmp/frida-server",
"/data/local/tmp/app_process64",
os.path.expanduser("~/.frida-server/app_process64"),
]:
if os.path.exists(path):
frida_bin = path
break
if os.path.exists(frida_bin):
subprocess.Popen(
["su", "-c", f"nohup {frida_bin} -l 0.0.0.0:{self.frida_port} &"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
time.sleep(2)
logger.info(f"frida-server 已启动 (端口 {self.frida_port})")
else:
logger.warning("frida-server 二进制文件不存在,请先执行部署脚本")
except Exception as e:
logger.warning(f"frida-server 启动检查失败: {e}")
def _init_frida_wireless(self):
"""初始化 Frida Manager无线模式"""
try:
from hook.frida_manager import FridaManager
# 如果已有 frida_mgr 且已连接,跳过
if hasattr(self, 'frida_mgr') and self.frida_mgr and self.frida_mgr.connected:
logger.info("Frida 已连接,跳过重新初始化")
return
self.frida_mgr = FridaManager(
mode=self.frida_mode,
gadget_host=self.frida_host,
gadget_port=self.frida_port,
on_event=self._on_frida_event if hasattr(self, '_on_frida_event') else None,
auto_reconnect=True,
reconnect_interval=5.0,
)
started = self.frida_mgr.start()
if started:
logger.info(f"🔗 Frida 无线连接成功 ({self.frida_mode})")
else:
logger.warning("⚠️ Frida 无线连接失败")
self.frida_mgr = None
except ImportError:
logger.info("Frida 模块未安装")
self.frida_mgr = None
except Exception as e:
logger.warning(f"Frida 无线初始化异常: {e}")
self.frida_mgr = None
def _init_wireless_bridge(self):
"""初始化 WirelessBridge"""
try:
from hook.wireless_bridge import WirelessBridge
ws_send_fn = None
if hasattr(self, 'ws') and self.ws:
async def _ws_send(data):
if self.ws and self.connected:
await self.ws.send(json.dumps(data))
ws_send_fn = _ws_send
self.wireless_bridge = WirelessBridge(
device_ip=self.frida_host,
frida_port=self.frida_port,
mode=self.frida_mode,
ws_send_fn=ws_send_fn,
)
# 如果 frida_mgr 已连接,直接使用
if self.frida_mgr and self.frida_mgr.connected:
self.wireless_bridge.frida_mgr = self.frida_mgr
self.wireless_bridge._connected = True
logger.info("WirelessBridge 已初始化")
except ImportError:
logger.info("WirelessBridge 模块未安装")
self.wireless_bridge = None
except Exception as e:
logger.warning(f"WirelessBridge 初始化异常: {e}")
self.wireless_bridge = None
async def handle_wireless_command(self, data: dict) -> Optional[dict]:
"""
处理无线 Frida 指令
在 _handle_message 中调用,处理 type=command 的消息。
"""
if not hasattr(self, 'wireless_bridge') or not self.wireless_bridge:
return None
msg_type = data.get("type", "")
if msg_type == "command":
action = data.get("action", "")
# 特殊指令Frida 连接管理
if action == "frida_connect":
params = data.get("params", {})
self.frida_host = params.get("ip", self.frida_host)
self.frida_port = params.get("port", self.frida_port)
self.frida_mode = params.get("mode", self.frida_mode)
self._init_frida_wireless()
self._init_wireless_bridge()
return {
"success": self.frida_mgr is not None and self.frida_mgr.connected,
"command_id": data.get("command_id"),
"wechat_attached": getattr(self.frida_mgr, 'connected', False),
"supported_actions": 112,
}
elif action == "frida_disconnect":
if self.frida_mgr:
self.frida_mgr.stop()
if self.wireless_bridge:
self.wireless_bridge.disconnect()
return {
"success": True,
"command_id": data.get("command_id"),
}
# 普通 Frida RPC 指令
return self.wireless_bridge.execute_command(data)
elif msg_type in ("query", "ping", "batch"):
return self.wireless_bridge.handle_ws_message(data)
return None
async def register_frida_to_server(self):
"""向服务器注册 Frida 连接信息"""
if not hasattr(self, 'ws') or not self.ws or not self.connected:
return
try:
device_ip = self._get_device_ip()
register_data = {
"type": "event",
"event": "frida_registered",
"data": {
"device_id": self.device_id,
"ip": device_ip,
"frida_port": self.frida_port,
"mode": self.frida_mode,
"frida_connected": self.frida_mgr is not None and self.frida_mgr.connected,
"android_version": self._get_prop("ro.build.version.release"),
"device_model": self._get_prop("ro.product.model"),
},
}
await self.ws.send(json.dumps(register_data))
logger.info(f"已向服务器注册 Frida 信息: {device_ip}:{self.frida_port}")
except Exception as e:
logger.warning(f"Frida 注册失败: {e}")
def _get_device_ip(self) -> str:
"""获取设备 WiFi IP"""
try:
result = subprocess.run(
["ip", "route", "get", "1"],
capture_output=True, text=True, timeout=5,
)
parts = result.stdout.strip().split()
for i, p in enumerate(parts):
if p == "src" and i + 1 < len(parts):
return parts[i + 1]
except Exception:
pass
try:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception:
return "127.0.0.1"
def _get_prop(self, prop: str) -> str:
"""获取 Android 系统属性"""
try:
result = subprocess.run(
["getprop", prop],
capture_output=True, text=True, timeout=5,
)
return result.stdout.strip()
except Exception:
return ""