207 lines
6.7 KiB
Python
207 lines
6.7 KiB
Python
#!/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())
|