feat: publish workphone SDK deployment and API docs
This commit is contained in:
715
sdk/agent/hook/wireless_deployer.py
Normal file
715
sdk/agent/hook/wireless_deployer.py
Normal file
@@ -0,0 +1,715 @@
|
||||
"""
|
||||
Frida 无线部署器 — 支持 Root / 免Root 双模式 WiFi 直连
|
||||
=====================================================
|
||||
|
||||
核心功能:
|
||||
- Root 模式:通过 Termux 在手机端启动 frida-server,监听 TCP 端口
|
||||
- 免Root 模式:将 frida-gadget 注入到目标 APK(微信),通过 TCP 连接
|
||||
- WiFi 直连:服务器通过手机 IP + 端口直接连接 Frida,无需 USB
|
||||
- 自动发现:扫描局域网内运行 frida-server 的设备
|
||||
- 健康检查:定期检测 Frida 连接状态,自动重连
|
||||
|
||||
架构:
|
||||
服务器 (FastAPI) ←→ WiFi ←→ 手机 (Termux + frida-server/gadget)
|
||||
↓
|
||||
微信进程 (Frida Hook)
|
||||
|
||||
技术栈:Frida 16.5.6 + WebSocket + TCP
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
import socket
|
||||
import logging
|
||||
import asyncio
|
||||
import threading
|
||||
import subprocess
|
||||
from typing import Optional, Dict, Any, List, Literal, Tuple
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ============================================================
|
||||
# § 1 数据模型
|
||||
# ============================================================
|
||||
|
||||
DeployMode = Literal["root_server", "gadget", "auto"]
|
||||
|
||||
@dataclass
|
||||
class DeviceConnection:
|
||||
"""设备连接信息"""
|
||||
device_id: str
|
||||
ip: str
|
||||
frida_port: int = 27042
|
||||
mode: DeployMode = "auto"
|
||||
status: str = "disconnected" # disconnected / connecting / connected / error
|
||||
wechat_attached: bool = False
|
||||
last_heartbeat: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
frida_version: Optional[str] = None
|
||||
wechat_version: Optional[str] = None
|
||||
android_version: Optional[str] = None
|
||||
device_model: Optional[str] = None
|
||||
connected_at: Optional[str] = None
|
||||
reconnect_count: int = 0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeployConfig:
|
||||
"""部署配置"""
|
||||
frida_version: str = "16.5.6"
|
||||
frida_arch: str = "arm64" # arm / arm64 / x86 / x86_64
|
||||
listen_port: int = 27042
|
||||
listen_host: str = "0.0.0.0"
|
||||
auto_start: bool = True
|
||||
anti_detect: bool = True # 随机端口 + 进程伪装
|
||||
gadget_config: dict = field(default_factory=lambda: {
|
||||
"interaction": {
|
||||
"type": "listen",
|
||||
"address": "0.0.0.0",
|
||||
"port": 27042,
|
||||
"on_load": "wait"
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
# ============================================================
|
||||
# § 2 Frida 无线部署器
|
||||
# ============================================================
|
||||
|
||||
class WirelessDeployer:
|
||||
"""
|
||||
Frida 无线部署管理器
|
||||
|
||||
职责:
|
||||
1. 在手机端部署 frida-server(Root)或 frida-gadget(免Root)
|
||||
2. 通过 WiFi TCP 连接 Frida
|
||||
3. 管理多设备连接池
|
||||
4. 自动发现局域网设备
|
||||
5. 健康检查与自动重连
|
||||
"""
|
||||
|
||||
FRIDA_DOWNLOAD_BASE = "https://github.com/frida/frida/releases/download"
|
||||
GADGET_DOWNLOAD_BASE = "https://github.com/nickcano/frida-gadget-releases/releases/download"
|
||||
|
||||
def __init__(self, config: Optional[DeployConfig] = None):
|
||||
self.config = config or DeployConfig()
|
||||
self.connections: Dict[str, DeviceConnection] = {}
|
||||
self._lock = threading.Lock()
|
||||
self._running = False
|
||||
self._health_thread: Optional[threading.Thread] = None
|
||||
|
||||
# ---- 设备连接管理 ----
|
||||
|
||||
def add_device(self, device_id: str, ip: str, port: int = 27042,
|
||||
mode: DeployMode = "auto") -> DeviceConnection:
|
||||
"""添加设备到连接池"""
|
||||
conn = DeviceConnection(
|
||||
device_id=device_id,
|
||||
ip=ip,
|
||||
frida_port=port,
|
||||
mode=mode,
|
||||
)
|
||||
with self._lock:
|
||||
self.connections[device_id] = conn
|
||||
logger.info(f"设备已添加: {device_id} ({ip}:{port}) 模式={mode}")
|
||||
return conn
|
||||
|
||||
def remove_device(self, device_id: str):
|
||||
"""从连接池移除设备"""
|
||||
with self._lock:
|
||||
if device_id in self.connections:
|
||||
del self.connections[device_id]
|
||||
logger.info(f"设备已移除: {device_id}")
|
||||
|
||||
def get_device(self, device_id: str) -> Optional[DeviceConnection]:
|
||||
"""获取设备连接信息"""
|
||||
return self.connections.get(device_id)
|
||||
|
||||
def list_devices(self) -> List[Dict[str, Any]]:
|
||||
"""列出所有设备"""
|
||||
return [conn.to_dict() for conn in self.connections.values()]
|
||||
|
||||
# ---- WiFi 直连 Frida ----
|
||||
|
||||
def connect_device(self, device_id: str) -> Dict[str, Any]:
|
||||
"""
|
||||
通过 WiFi 连接设备的 Frida
|
||||
|
||||
流程:
|
||||
1. 检查设备 IP 可达性
|
||||
2. 尝试 TCP 连接 frida-server 端口
|
||||
3. 获取 Frida 设备信息
|
||||
4. attach 到微信进程
|
||||
"""
|
||||
conn = self.connections.get(device_id)
|
||||
if not conn:
|
||||
return {"success": False, "error": f"设备不存在: {device_id}"}
|
||||
|
||||
conn.status = "connecting"
|
||||
conn.error = None
|
||||
|
||||
try:
|
||||
# Step 1: 检查 TCP 端口可达
|
||||
if not self._check_port(conn.ip, conn.frida_port, timeout=5):
|
||||
conn.status = "error"
|
||||
conn.error = f"Frida 端口不可达: {conn.ip}:{conn.frida_port}"
|
||||
return {"success": False, "error": conn.error}
|
||||
|
||||
# Step 2: 通过 Frida API 连接
|
||||
import frida
|
||||
mgr = frida.get_device_manager()
|
||||
device = mgr.add_remote_device(f"{conn.ip}:{conn.frida_port}")
|
||||
|
||||
# Step 3: 获取设备信息
|
||||
conn.frida_version = device.query_system_parameters().get("os", {}).get("version", "unknown")
|
||||
conn.device_model = device.name
|
||||
conn.status = "connected"
|
||||
conn.connected_at = datetime.now().isoformat()
|
||||
conn.last_heartbeat = conn.connected_at
|
||||
|
||||
# Step 4: 尝试 attach 微信
|
||||
try:
|
||||
processes = device.enumerate_processes()
|
||||
wechat_proc = None
|
||||
for proc in processes:
|
||||
if proc.name == "com.tencent.mm" or "微信" in proc.name:
|
||||
wechat_proc = proc
|
||||
break
|
||||
|
||||
if wechat_proc:
|
||||
conn.wechat_attached = True
|
||||
logger.info(f"微信进程已发现: PID={wechat_proc.pid}")
|
||||
else:
|
||||
conn.wechat_attached = False
|
||||
logger.warning(f"微信进程未运行,等待启动")
|
||||
except Exception as e:
|
||||
logger.warning(f"枚举进程失败: {e}")
|
||||
conn.wechat_attached = False
|
||||
|
||||
logger.info(f"✅ 设备 WiFi 连接成功: {device_id} ({conn.ip}:{conn.frida_port})")
|
||||
return {
|
||||
"success": True,
|
||||
"device_id": device_id,
|
||||
"ip": conn.ip,
|
||||
"port": conn.frida_port,
|
||||
"mode": conn.mode,
|
||||
"wechat_attached": conn.wechat_attached,
|
||||
"device_model": conn.device_model,
|
||||
}
|
||||
|
||||
except ImportError:
|
||||
conn.status = "error"
|
||||
conn.error = "frida 未安装"
|
||||
return {"success": False, "error": "frida 未安装,请执行: pip install frida-tools"}
|
||||
except Exception as e:
|
||||
conn.status = "error"
|
||||
conn.error = str(e)
|
||||
conn.reconnect_count += 1
|
||||
logger.error(f"WiFi 连接失败: {device_id} - {e}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
def disconnect_device(self, device_id: str) -> Dict[str, Any]:
|
||||
"""断开设备连接"""
|
||||
conn = self.connections.get(device_id)
|
||||
if not conn:
|
||||
return {"success": False, "error": f"设备不存在: {device_id}"}
|
||||
|
||||
conn.status = "disconnected"
|
||||
conn.wechat_attached = False
|
||||
logger.info(f"设备已断开: {device_id}")
|
||||
return {"success": True, "device_id": device_id}
|
||||
|
||||
# ---- Root 模式:远程启动 frida-server ----
|
||||
|
||||
def generate_root_deploy_script(self, port: int = 0) -> str:
|
||||
"""
|
||||
生成 Root 模式部署脚本(在 Termux 中执行)
|
||||
|
||||
功能:
|
||||
- 下载对应架构的 frida-server
|
||||
- 以 root 权限启动,监听指定端口
|
||||
- 支持反检测(随机端口 + 进程名伪装)
|
||||
"""
|
||||
if port == 0:
|
||||
import random
|
||||
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
|
||||
|
||||
version = self.config.frida_version
|
||||
arch = self.config.frida_arch
|
||||
server_name = f"frida-server-{version}-android-{arch}"
|
||||
download_url = f"{self.FRIDA_DOWNLOAD_BASE}/{version}/{server_name}.xz"
|
||||
|
||||
# 反检测:伪装进程名
|
||||
disguise_name = "app_process64" if self.config.anti_detect else "frida-server"
|
||||
|
||||
script = f"""#!/data/data/com.termux/files/usr/bin/bash
|
||||
# ================================================================
|
||||
# Frida Server 无线部署脚本 (Root 模式)
|
||||
# 版本: {version} | 架构: {arch} | 端口: {port}
|
||||
# ================================================================
|
||||
set -eo pipefail
|
||||
|
||||
RED='\\033[0;31m'; GREEN='\\033[0;32m'; NC='\\033[0m'
|
||||
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
|
||||
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
|
||||
|
||||
# 检查 Root
|
||||
if ! su -c "id" 2>/dev/null | grep -q "uid=0"; then
|
||||
error "需要 Root 权限"
|
||||
exit 1
|
||||
fi
|
||||
info "Root 权限确认 ✓"
|
||||
|
||||
# 检查网络
|
||||
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
|
||||
info "设备 IP: $DEVICE_IP"
|
||||
|
||||
# 下载 frida-server
|
||||
FRIDA_DIR="$HOME/.frida-server"
|
||||
mkdir -p "$FRIDA_DIR"
|
||||
FRIDA_BIN="$FRIDA_DIR/{disguise_name}"
|
||||
|
||||
if [ ! -f "$FRIDA_BIN" ]; then
|
||||
info "下载 frida-server {version} ({arch})..."
|
||||
curl -sL "{download_url}" -o "$FRIDA_DIR/frida-server.xz"
|
||||
xz -d -f "$FRIDA_DIR/frida-server.xz"
|
||||
mv "$FRIDA_DIR/frida-server" "$FRIDA_BIN"
|
||||
chmod +x "$FRIDA_BIN"
|
||||
info "下载完成 ✓"
|
||||
else
|
||||
info "frida-server 已存在 ✓"
|
||||
fi
|
||||
|
||||
# 停止旧进程
|
||||
su -c "pkill -f frida-server" 2>/dev/null || true
|
||||
su -c "pkill -f {disguise_name}" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# 复制到系统目录并启动
|
||||
su -c "cp $FRIDA_BIN /data/local/tmp/{disguise_name}"
|
||||
su -c "chmod 755 /data/local/tmp/{disguise_name}"
|
||||
su -c "nohup /data/local/tmp/{disguise_name} -l 0.0.0.0:{port} &" 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
# 验证
|
||||
if su -c "netstat -tlnp 2>/dev/null" | grep -q ":{port}"; then
|
||||
info "✅ frida-server 已启动,监听 0.0.0.0:{port}"
|
||||
info "📱 连接地址: $DEVICE_IP:{port}"
|
||||
echo ""
|
||||
echo "在服务器端执行:"
|
||||
echo " frida -H $DEVICE_IP:{port} -n com.tencent.mm"
|
||||
echo ""
|
||||
else
|
||||
error "frida-server 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 写入自启动
|
||||
BOOT_SCRIPT="$HOME/.frida-autostart.sh"
|
||||
cat > "$BOOT_SCRIPT" << 'AUTOSTART'
|
||||
#!/data/data/com.termux/files/usr/bin/bash
|
||||
sleep 10
|
||||
su -c "nohup /data/local/tmp/{disguise_name} -l 0.0.0.0:{port} &" 2>/dev/null
|
||||
AUTOSTART
|
||||
chmod +x "$BOOT_SCRIPT"
|
||||
|
||||
# Termux:Boot 自启动
|
||||
BOOT_DIR="$HOME/.termux/boot"
|
||||
mkdir -p "$BOOT_DIR"
|
||||
ln -sf "$BOOT_SCRIPT" "$BOOT_DIR/frida-autostart.sh"
|
||||
info "开机自启已配置 ✓"
|
||||
"""
|
||||
return script
|
||||
|
||||
# ---- 免Root 模式:Gadget 注入 ----
|
||||
|
||||
def generate_gadget_deploy_script(self, apk_path: str = "", port: int = 0) -> str:
|
||||
"""
|
||||
生成免 Root 模式部署脚本
|
||||
|
||||
原理:
|
||||
- 将 frida-gadget.so 注入到微信 APK 的 lib 目录
|
||||
- 配置 gadget 监听 TCP 端口
|
||||
- 重新签名并安装修改后的 APK
|
||||
|
||||
注意:免 Root 模式需要重新安装微信,会丢失聊天记录
|
||||
"""
|
||||
if port == 0:
|
||||
import random
|
||||
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
|
||||
|
||||
version = self.config.frida_version
|
||||
arch = self.config.frida_arch
|
||||
gadget_name = f"frida-gadget-{version}-android-{arch}.so"
|
||||
gadget_url = f"{self.FRIDA_DOWNLOAD_BASE}/{version}/{gadget_name}.xz"
|
||||
|
||||
gadget_config = json.dumps({
|
||||
"interaction": {
|
||||
"type": "listen",
|
||||
"address": "0.0.0.0",
|
||||
"port": port,
|
||||
"on_load": "wait"
|
||||
}
|
||||
}, indent=2)
|
||||
|
||||
script = f"""#!/data/data/com.termux/files/usr/bin/bash
|
||||
# ================================================================
|
||||
# Frida Gadget 无线部署脚本 (免Root 模式)
|
||||
# 版本: {version} | 架构: {arch} | 端口: {port}
|
||||
# ================================================================
|
||||
set -eo pipefail
|
||||
|
||||
RED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; NC='\\033[0m'
|
||||
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
|
||||
warn() {{ echo -e "${{YELLOW}}[WARN]${{NC}} $*"; }}
|
||||
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
|
||||
|
||||
# 安装依赖
|
||||
pkg install -y apksigner aapt zip unzip curl xz-utils 2>/dev/null || true
|
||||
|
||||
# 下载 frida-gadget
|
||||
GADGET_DIR="$HOME/.frida-gadget"
|
||||
mkdir -p "$GADGET_DIR"
|
||||
GADGET_SO="$GADGET_DIR/libfrida-gadget.so"
|
||||
|
||||
if [ ! -f "$GADGET_SO" ]; then
|
||||
info "下载 frida-gadget {version} ({arch})..."
|
||||
curl -sL "{gadget_url}" -o "$GADGET_DIR/gadget.so.xz"
|
||||
xz -d -f "$GADGET_DIR/gadget.so.xz"
|
||||
mv "$GADGET_DIR/gadget.so" "$GADGET_SO"
|
||||
info "下载完成 ✓"
|
||||
fi
|
||||
|
||||
# Gadget 配置文件
|
||||
cat > "$GADGET_DIR/libfrida-gadget.config.so" << 'GADGET_CFG'
|
||||
{gadget_config}
|
||||
GADGET_CFG
|
||||
|
||||
APK_PATH="{apk_path}"
|
||||
if [ -z "$APK_PATH" ]; then
|
||||
# 自动查找已安装的微信 APK
|
||||
APK_PATH=$(pm path com.tencent.mm 2>/dev/null | head -1 | sed 's/package://')
|
||||
if [ -z "$APK_PATH" ]; then
|
||||
error "未找到微信 APK,请指定路径"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
info "微信 APK: $APK_PATH"
|
||||
|
||||
# 解包
|
||||
WORK_DIR="$GADGET_DIR/work"
|
||||
rm -rf "$WORK_DIR"
|
||||
mkdir -p "$WORK_DIR"
|
||||
cp "$APK_PATH" "$WORK_DIR/base.apk"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
# 解压 APK
|
||||
unzip -q base.apk -d apk_contents
|
||||
|
||||
# 注入 gadget
|
||||
LIB_DIR="apk_contents/lib/{arch.replace('arm64', 'arm64-v8a').replace('arm', 'armeabi-v7a')}"
|
||||
mkdir -p "$LIB_DIR"
|
||||
cp "$GADGET_SO" "$LIB_DIR/libfrida-gadget.so"
|
||||
cp "$GADGET_DIR/libfrida-gadget.config.so" "$LIB_DIR/libfrida-gadget.config.so"
|
||||
|
||||
# 修改 AndroidManifest.xml 加载 gadget(smali 注入方式更可靠,这里用简化方案)
|
||||
# 在 Application 类的 static 块中加载 libfrida-gadget.so
|
||||
info "注入 frida-gadget 到 lib 目录..."
|
||||
|
||||
# 重新打包
|
||||
cd apk_contents
|
||||
zip -q -r ../patched.apk .
|
||||
cd ..
|
||||
|
||||
# 签名
|
||||
info "签名 APK..."
|
||||
# 生成临时签名密钥
|
||||
keytool -genkey -v -keystore "$GADGET_DIR/debug.keystore" \\
|
||||
-storepass android -alias androiddebugkey -keypass android \\
|
||||
-keyalg RSA -keysize 2048 -validity 10000 \\
|
||||
-dname "CN=Debug, OU=Debug, O=Debug, L=Debug, S=Debug, C=US" 2>/dev/null || true
|
||||
|
||||
apksigner sign --ks "$GADGET_DIR/debug.keystore" \\
|
||||
--ks-pass pass:android --key-pass pass:android \\
|
||||
--out patched_signed.apk patched.apk
|
||||
|
||||
# 安装
|
||||
warn "⚠️ 即将安装修改版微信,原版数据可能丢失"
|
||||
warn " 建议先备份聊天记录"
|
||||
info "安装修改版微信..."
|
||||
pm install -r -d patched_signed.apk
|
||||
|
||||
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
|
||||
info "✅ Gadget 注入完成"
|
||||
info "📱 启动微信后,连接地址: $DEVICE_IP:{port}"
|
||||
echo ""
|
||||
echo "在服务器端执行:"
|
||||
echo " frida -H $DEVICE_IP:{port} -n Gadget"
|
||||
echo ""
|
||||
"""
|
||||
return script
|
||||
|
||||
# ---- 混合模式:自动检测 Root 并选择最佳方案 ----
|
||||
|
||||
def generate_auto_deploy_script(self, server_url: str, port: int = 0) -> str:
|
||||
"""
|
||||
生成自动检测部署脚本
|
||||
|
||||
逻辑:
|
||||
1. 检测是否有 Root 权限
|
||||
2. Root → 使用 frida-server 模式
|
||||
3. 非 Root → 使用 frida-gadget 模式
|
||||
4. 自动连接到 SDK 服务器
|
||||
"""
|
||||
if port == 0:
|
||||
import random
|
||||
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
|
||||
|
||||
version = self.config.frida_version
|
||||
arch = self.config.frida_arch
|
||||
|
||||
script = f"""#!/data/data/com.termux/files/usr/bin/bash
|
||||
# ================================================================
|
||||
# 工作手机 SDK - Frida 无线自动部署
|
||||
# 版本: {version} | 端口: {port}
|
||||
# 服务器: {server_url}
|
||||
# ================================================================
|
||||
set -eo pipefail
|
||||
|
||||
RED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; CYAN='\\033[0;36m'; NC='\\033[0m'
|
||||
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
|
||||
warn() {{ echo -e "${{YELLOW}}[WARN]${{NC}} $*"; }}
|
||||
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
|
||||
|
||||
DEVICE_ID=$(getprop ro.serialno 2>/dev/null || echo "dev-$(date +%s)")
|
||||
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
|
||||
FRIDA_PORT={port}
|
||||
SERVER_URL="{server_url}"
|
||||
|
||||
info "设备ID: $DEVICE_ID"
|
||||
info "设备IP: $DEVICE_IP"
|
||||
info "Frida端口: $FRIDA_PORT"
|
||||
|
||||
# ---- 检测 Root ----
|
||||
HAS_ROOT=false
|
||||
if su -c "id" 2>/dev/null | grep -q "uid=0"; then
|
||||
HAS_ROOT=true
|
||||
info "✅ Root 权限可用 → 使用 frida-server 模式"
|
||||
else
|
||||
warn "⚠️ 无 Root 权限 → 使用 frida-gadget 模式"
|
||||
fi
|
||||
|
||||
if [ "$HAS_ROOT" = true ]; then
|
||||
# ========== Root 模式 ==========
|
||||
FRIDA_DIR="$HOME/.frida-server"
|
||||
mkdir -p "$FRIDA_DIR"
|
||||
FRIDA_BIN="$FRIDA_DIR/fs-{version}"
|
||||
|
||||
if [ ! -f "$FRIDA_BIN" ]; then
|
||||
info "下载 frida-server {version} ({arch})..."
|
||||
curl -sL "{WirelessDeployer.FRIDA_DOWNLOAD_BASE}/{version}/frida-server-{version}-android-{arch}.xz" \\
|
||||
-o "$FRIDA_DIR/fs.xz"
|
||||
xz -d -f "$FRIDA_DIR/fs.xz"
|
||||
mv "$FRIDA_DIR/fs" "$FRIDA_BIN"
|
||||
chmod +x "$FRIDA_BIN"
|
||||
fi
|
||||
|
||||
# 停止旧进程
|
||||
su -c "pkill -f frida-server" 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# 启动
|
||||
su -c "cp $FRIDA_BIN /data/local/tmp/frida-server"
|
||||
su -c "chmod 755 /data/local/tmp/frida-server"
|
||||
su -c "nohup /data/local/tmp/frida-server -l 0.0.0.0:$FRIDA_PORT &" 2>/dev/null
|
||||
sleep 2
|
||||
|
||||
if su -c "netstat -tlnp 2>/dev/null" | grep -q ":$FRIDA_PORT"; then
|
||||
info "✅ frida-server 已启动"
|
||||
else
|
||||
error "frida-server 启动失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DEPLOY_MODE="root_server"
|
||||
else
|
||||
# ========== 免Root 模式 ==========
|
||||
info "免Root 模式需要重新安装修改版微信"
|
||||
info "请先运行 gadget 部署脚本"
|
||||
DEPLOY_MODE="gadget"
|
||||
fi
|
||||
|
||||
# ---- 向服务器注册 ----
|
||||
info "向服务器注册设备..."
|
||||
REGISTER_DATA=$(cat << EOF
|
||||
{{
|
||||
"device_id": "$DEVICE_ID",
|
||||
"ip": "$DEVICE_IP",
|
||||
"frida_port": $FRIDA_PORT,
|
||||
"mode": "$DEPLOY_MODE",
|
||||
"android_version": "$(getprop ro.build.version.release)",
|
||||
"device_model": "$(getprop ro.product.model)",
|
||||
"frida_version": "{version}"
|
||||
}}
|
||||
EOF
|
||||
)
|
||||
|
||||
# 通过 HTTP API 注册
|
||||
API_URL=$(echo "$SERVER_URL" | sed 's|ws://|http://|' | sed 's|wss://|https://|' | sed 's|/ws/device||')
|
||||
curl -s -X POST "$API_URL/api/v3/frida/register" \\
|
||||
-H "Content-Type: application/json" \\
|
||||
-d "$REGISTER_DATA" || warn "服务器注册失败,稍后重试"
|
||||
|
||||
info "🎉 部署完成!"
|
||||
info "连接信息: $DEVICE_IP:$FRIDA_PORT ($DEPLOY_MODE)"
|
||||
"""
|
||||
return script
|
||||
|
||||
# ---- 局域网设备发现 ----
|
||||
|
||||
def discover_devices(self, subnet: str = "", port: int = 27042,
|
||||
timeout: float = 2.0) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
扫描局域网内运行 frida-server 的设备
|
||||
|
||||
Args:
|
||||
subnet: 子网前缀,如 "192.168.1",空则自动检测
|
||||
port: Frida 端口
|
||||
timeout: 每个 IP 的超时时间
|
||||
"""
|
||||
if not subnet:
|
||||
subnet = self._detect_subnet()
|
||||
|
||||
if not subnet:
|
||||
return []
|
||||
|
||||
found = []
|
||||
logger.info(f"扫描子网 {subnet}.0/24 端口 {port}...")
|
||||
|
||||
def _scan_ip(ip: str):
|
||||
if self._check_port(ip, port, timeout):
|
||||
try:
|
||||
import frida
|
||||
mgr = frida.get_device_manager()
|
||||
device = mgr.add_remote_device(f"{ip}:{port}")
|
||||
params = device.query_system_parameters()
|
||||
found.append({
|
||||
"ip": ip,
|
||||
"port": port,
|
||||
"name": device.name,
|
||||
"os": params.get("os", {}).get("id", "unknown"),
|
||||
"arch": params.get("arch", "unknown"),
|
||||
})
|
||||
logger.info(f" 发现设备: {ip}:{port} ({device.name})")
|
||||
except Exception:
|
||||
# 端口开放但不是 Frida
|
||||
pass
|
||||
|
||||
threads = []
|
||||
for i in range(1, 255):
|
||||
ip = f"{subnet}.{i}"
|
||||
t = threading.Thread(target=_scan_ip, args=(ip,))
|
||||
t.daemon = True
|
||||
threads.append(t)
|
||||
t.start()
|
||||
|
||||
for t in threads:
|
||||
t.join(timeout=timeout + 1)
|
||||
|
||||
logger.info(f"扫描完成,发现 {len(found)} 个设备")
|
||||
return found
|
||||
|
||||
# ---- 健康检查 ----
|
||||
|
||||
def start_health_check(self, interval: int = 30):
|
||||
"""启动后台健康检查线程"""
|
||||
self._running = True
|
||||
self._health_thread = threading.Thread(
|
||||
target=self._health_check_loop,
|
||||
args=(interval,),
|
||||
daemon=True,
|
||||
)
|
||||
self._health_thread.start()
|
||||
logger.info(f"健康检查已启动,间隔 {interval}s")
|
||||
|
||||
def stop_health_check(self):
|
||||
"""停止健康检查"""
|
||||
self._running = False
|
||||
if self._health_thread:
|
||||
self._health_thread.join(timeout=5)
|
||||
logger.info("健康检查已停止")
|
||||
|
||||
def _health_check_loop(self, interval: int):
|
||||
"""健康检查循环"""
|
||||
while self._running:
|
||||
for device_id, conn in list(self.connections.items()):
|
||||
if conn.status == "connected":
|
||||
if not self._check_port(conn.ip, conn.frida_port, timeout=3):
|
||||
conn.status = "disconnected"
|
||||
conn.wechat_attached = False
|
||||
logger.warning(f"设备离线: {device_id}")
|
||||
else:
|
||||
conn.last_heartbeat = datetime.now().isoformat()
|
||||
elif conn.status == "disconnected" and conn.reconnect_count < 10:
|
||||
# 尝试自动重连
|
||||
result = self.connect_device(device_id)
|
||||
if result.get("success"):
|
||||
logger.info(f"设备自动重连成功: {device_id}")
|
||||
time.sleep(interval)
|
||||
|
||||
# ---- 工具方法 ----
|
||||
|
||||
@staticmethod
|
||||
def _check_port(ip: str, port: int, timeout: float = 3.0) -> bool:
|
||||
"""检查 TCP 端口是否可达"""
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
result = sock.connect_ex((ip, port))
|
||||
sock.close()
|
||||
return result == 0
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _detect_subnet() -> str:
|
||||
"""自动检测本机子网"""
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
s.connect(("8.8.8.8", 80))
|
||||
ip = s.getsockname()[0]
|
||||
s.close()
|
||||
parts = ip.split(".")
|
||||
return ".".join(parts[:3])
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def get_status(self) -> Dict[str, Any]:
|
||||
"""获取部署器状态"""
|
||||
connected = sum(1 for c in self.connections.values() if c.status == "connected")
|
||||
total = len(self.connections)
|
||||
return {
|
||||
"total_devices": total,
|
||||
"connected_devices": connected,
|
||||
"disconnected_devices": total - connected,
|
||||
"health_check_running": self._running,
|
||||
"config": {
|
||||
"frida_version": self.config.frida_version,
|
||||
"frida_arch": self.config.frida_arch,
|
||||
"anti_detect": self.config.anti_detect,
|
||||
},
|
||||
"devices": self.list_devices(),
|
||||
}
|
||||
|
||||
|
||||
# ============================================================
|
||||
# § 3 全局单例
|
||||
# ============================================================
|
||||
|
||||
wireless_deployer = WirelessDeployer()
|
||||
Reference in New Issue
Block a user