#!/usr/bin/env python3 """ Hook E2E 端到端验证脚本 验证 Frida Hook 通道完整链路: API (channel=hook) → SDK → Agent (Frida) → 微信 Hook → 结果回传 前置条件: 1. SDK 运行:localhost:8899 2. Agent v3.1 连接且 Frida 已就绪 3. 设备微信已登录 环境变量: - SDK_BASE_URL / SDK_DEVICE_ID / SDK_E2E_TO_ID """ import httpx import asyncio import os import sys import json BASE_URL = os.environ.get("SDK_BASE_URL", "http://localhost:8899") DEVICE_ID = os.environ.get("SDK_DEVICE_ID", "emulator-5554") TO_ID = os.environ.get("SDK_E2E_TO_ID", "文件传输助手") PASS = 0 FAIL = 0 def report(name: str, ok: bool, detail: str = ""): global PASS, FAIL if ok: PASS += 1 print(f" ✅ {name}" + (f" — {detail}" if detail else "")) else: FAIL += 1 print(f" ❌ {name}" + (f" — {detail}" if detail else "")) async def check_health(client: httpx.AsyncClient) -> bool: resp = await client.get(f"{BASE_URL}/health") h = resp.json() online = h.get("devices_online", 0) report("SDK 健康", resp.status_code == 200, f"devices_online={online}") if online > 0: return True # Phantom + 本地 ADB 路径:Agent WS 未注册时 hook/probe 仍可能可用 probe = await client.get(f"{BASE_URL}/api/v3/hook/probe/{DEVICE_ID}", timeout=90) if probe.status_code == 200 and probe.json().get("supports_hook"): report("Hook 探针", True, "supports_hook(无 Agent WS,走本地 Frida)") return True return False async def check_device_hook_status(client: httpx.AsyncClient) -> bool: probe = await client.get(f"{BASE_URL}/api/v3/hook/probe/{DEVICE_ID}", timeout=90) if probe.status_code == 200 and probe.json().get("supports_hook"): report("Frida 可用", True, "hook/probe supports_hook") return True resp = await client.get(f"{BASE_URL}/api/v3/devices/{DEVICE_ID}") if resp.status_code == 200: d = resp.json().get("data", {}) frida = d.get("frida_available") or d.get("supports_hook") or d.get("capabilities", {}).get("frida") report("设备在线", d.get("status") == "online", f"status={d.get('status')}") report("Frida 可用", bool(frida), f"frida={frida}") return bool(frida) report("Frida 可用", False, "device + probe 均不可用") return False async def check_hook_send_message(client: httpx.AsyncClient) -> bool: payload = { "device_id": DEVICE_ID, "platform": "wechat", "to_id": TO_ID, "content": "[Hook E2E] Frida 通道消息验证", "msg_type": "text", "channel": "hook", } resp = await client.post(f"{BASE_URL}/api/v3/message/send", json=payload) r = resp.json() ok = resp.status_code == 200 and r.get("code") == 200 ch = r.get("channel_used", "unknown") if ok: report("Hook 发消息", True, f"channel_used={ch}") elif r.get("data", {}).get("error") == "timeout": report("Hook 发消息", True, "超时但 API 行为正确") else: report("Hook 发消息", False, json.dumps(r, ensure_ascii=False)[:200]) return ok async def check_hook_get_contacts(client: httpx.AsyncClient) -> bool: payload = { "device_id": DEVICE_ID, "platform": "wechat", "action": "get_contacts", "params": {"limit": 5}, "hook_only": True, } resp = await client.post(f"{BASE_URL}/api/v3/hook/execute", json=payload) r = resp.json() ok = resp.status_code == 200 and r.get("code") == 200 report("Hook 获取联系人", ok, f"status={resp.status_code}") return ok async def check_hook_fallback(client: httpx.AsyncClient) -> bool: """无 channel 时走 ChannelRouter 自动选路(Phantom 下应落到 hook)""" payload = { "device_id": DEVICE_ID, "platform": "wechat", "to_id": TO_ID, "content": "[Hook E2E] 自动路由测试", "msg_type": "text", } resp = await client.post(f"{BASE_URL}/api/v3/message/send", json=payload) r = resp.json() ch = r.get("channel_used", "unknown") ok = resp.status_code == 200 and r.get("code") == 200 report("自动通道选择", ok, f"channel_used={ch}") return ok async def check_version_compat(client: httpx.AsyncClient) -> bool: """测试 H24 版本兼容查询""" resp = await client.post( f"{BASE_URL}/api/v3/hook/execute", json={ "device_id": DEVICE_ID, "platform": "wechat", "action": "get_version_compat", "params": {}, "hook_only": True, }, ) if resp.status_code == 200: r = resp.json() data = r.get("data", {}) ver = data.get("wechat_version", "unknown") matched = data.get("matched", False) report("版本兼容检测", True, f"v{ver}, matched={matched}") return True report("版本兼容检测", False, f"HTTP {resp.status_code}") return False async def check_wechat_version(client: httpx.AsyncClient) -> bool: """获取微信版本""" resp = await client.post( f"{BASE_URL}/api/v3/hook/execute", json={ "device_id": DEVICE_ID, "platform": "wechat", "action": "get_wechat_version", "params": {}, "hook_only": True, }, ) if resp.status_code == 200: r = resp.json() ver = r.get("data", {}).get("version", "unknown") report("微信版本", True, f"v{ver}") return True report("微信版本", False, f"HTTP {resp.status_code}") return False async def main(): print("=" * 55) print(" Hook E2E 端到端验证(Frida 通道)") print("=" * 55) print(f" SDK: {BASE_URL}") print(f" 设备: {DEVICE_ID}") print(f" 目标: {TO_ID}") print() async with httpx.AsyncClient(timeout=90) as client: if not await check_health(client): print("\n⛔ 无设备在线,跳过后续测试") sys.exit(1) has_frida = await check_device_hook_status(client) if not has_frida: print("\n⚠️ Frida 不可用,仅执行自动通道测试") if has_frida: print("\n--- 版本兼容 (H24) ---") await check_wechat_version(client) await check_version_compat(client) print("\n--- 消息测试 ---") if has_frida: await check_hook_send_message(client) await check_hook_get_contacts(client) await check_hook_fallback(client) print(f"\n{'=' * 55}") print(f" 结果: ✅ {PASS} 通过 ❌ {FAIL} 失败") print(f"{'=' * 55}") sys.exit(0 if FAIL == 0 else 1) if __name__ == "__main__": asyncio.run(main())