test: 添加真机验证脚本

This commit is contained in:
karuo
2026-05-18 06:37:22 +00:00
parent af186060dc
commit 6a0b656c75

View File

@@ -0,0 +1,359 @@
#!/usr/bin/env python3
"""
工作手机SDK - 真机验证脚本
通过WiFi连接frida-server加载wechat_hook_v2.js逐个验证所有RPC方法
"""
import frida
import json
import time
import os
import sys
import subprocess
from datetime import datetime
from typing import Dict, Any, List, Tuple
# 配置
DEVICE_IP = "192.168.0.12"
FRIDA_PORT = 27042
WECHAT_PACKAGE = "com.tencent.mm"
DEVICE_SERIAL = "xgfe65eimrrofyws"
# Hook脚本路径
SCRIPT_PATH = os.path.join(os.path.dirname(__file__), '..', 'agent', 'hook', 'wechat_hook_v2.js')
if not os.path.exists(SCRIPT_PATH):
# 尝试从GitHub仓库路径
SCRIPT_PATH = os.path.expanduser("~/Documents/GitHub/workphone-sdk/sdk/agent/hook/wechat_hook_v2.js")
# 截图保存目录
SCREENSHOT_DIR = os.path.join(os.path.dirname(__file__), '..', '..', 'verification_screenshots')
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
# 结果文件
RESULT_FILE = os.path.join(os.path.dirname(__file__), '..', '..', 'verification_results.json')
def take_screenshot(name: str) -> str:
"""截取手机屏幕"""
filename = f"{name}_{int(time.time())}.png"
filepath = os.path.join(SCREENSHOT_DIR, filename)
try:
subprocess.run(
["adb", "-s", DEVICE_SERIAL, "shell", "screencap", "-p", f"/sdcard/screenshot.png"],
capture_output=True, timeout=10
)
subprocess.run(
["adb", "-s", DEVICE_SERIAL, "pull", "/sdcard/screenshot.png", filepath],
capture_output=True, timeout=10
)
if os.path.exists(filepath):
return filepath
except Exception as e:
print(f" 截图失败: {e}")
return ""
def connect_frida():
"""连接Frida并attach微信"""
print(f"[1/4] 连接 Frida Server ({DEVICE_IP}:{FRIDA_PORT})...")
mgr = frida.get_device_manager()
device = mgr.add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}")
print(f" 设备: {device.name}")
# 找微信主进程
print("[2/4] 查找微信主进程...")
procs = [p for p in device.enumerate_processes() if p.name == WECHAT_PACKAGE]
if not procs:
# 尝试通过应用列表
apps = [a for a in device.enumerate_applications() if a.identifier == WECHAT_PACKAGE]
if apps and apps[0].pid > 0:
pid = apps[0].pid
else:
print(" 微信主进程未找到!尝试启动微信...")
subprocess.run(
["adb", "-s", DEVICE_SERIAL, "shell", "am", "start", "-n",
"com.tencent.mm/.ui.LauncherUI"],
capture_output=True, timeout=10
)
time.sleep(3)
procs = [p for p in device.enumerate_processes() if p.name == WECHAT_PACKAGE]
if not procs:
raise Exception("微信主进程无法启动")
pid = procs[0].pid
else:
pid = procs[0].pid
print(f" 微信 PID: {pid}")
# Attach
print("[3/4] Attach 微信进程...")
session = device.attach(pid)
print(" Attach 成功!")
# 加载脚本
print(f"[4/4] 加载 Hook 脚本...")
if not os.path.exists(SCRIPT_PATH):
raise FileNotFoundError(f"Hook脚本不存在: {SCRIPT_PATH}")
with open(SCRIPT_PATH, 'r', encoding='utf-8') as f:
source = f.read()
script = session.create_script(source)
messages = []
def on_message(message, data):
if message.get('type') == 'send':
messages.append(message.get('payload', {}))
elif message.get('type') == 'error':
print(f" [脚本错误] {message.get('description', '')}")
script.on('message', on_message)
script.load()
rpc = script.exports_sync
# 验证连接
try:
pong = rpc.ping()
print(f" RPC ping: {pong}")
except Exception as e:
print(f" RPC ping 失败: {e}")
return device, session, script, rpc
# 所有需要验证的操作(按模块分组)
VERIFICATION_PLAN = {
"H15_消息接收": {
"get_messages": {"conversation_id": "", "limit": 3},
"get_recent_messages": {"limit": 5},
"search_messages": {"keyword": "你好", "limit": 3},
},
"H16_联系人": {
"get_contacts": {"limit": 10},
"get_contact_info": {"wxid": "filehelper"},
"search_contacts": {"keyword": "文件", "limit": 5},
},
"H17_消息发送": {
"send_message": {"to_id": "filehelper", "content": "[SDK验证] 消息发送测试 " + datetime.now().strftime("%H:%M:%S"), "msg_type": "text"},
},
"H18_好友请求": {
"get_friend_requests": {"limit": 5},
},
"H19_好友管理": {
"set_friend_remark": {"wxid": "filehelper", "remark": "文件传输助手"},
},
"H20_朋友圈发布": {
# 跳过实际发布,只验证接口存在
},
"H21_朋友圈浏览": {
"get_moments": {"wxid": "", "limit": 3},
},
"H22_群管理": {
"get_groups": {"limit": 10},
},
"H23_账号管理": {
"get_profile": {},
"check_account_status": {},
},
"H24_账号安全": {
"get_login_devices": {},
},
"H25_支付": {
"get_wallet_balance": {},
},
"H26_二维码": {
"generate_my_qr_code": {},
},
"H27_视频号": {
"browse_channels": {"limit": 3},
},
"H28_标签": {
"get_labels": {},
},
"H29_收藏": {
"get_favorites": {"limit": 5},
},
"H30_设置": {
# 只读操作
},
"H31_搜索": {
"global_search": {"keyword": "微信", "limit": 5},
},
"H32_小程序": {
"get_recent_mini_programs": {},
},
"H33_文件传输": {
"send_image": {"to_id": "filehelper", "image_path": "/sdcard/DCIM/test.jpg"},
},
"H34_消息转发": {
# 需要msg_id跳过
},
"H35_注册登录": {
"check_login_state": {},
"get_sim_phone": {},
},
"H36_公众号": {
"get_official_accounts": {"limit": 5},
},
"H37_表情": {
# 需要emoji_md5跳过
},
"H38_浮窗": {
# 需要特定条件,跳过
},
"H39_设备信息": {
"get_device_info": {},
"get_storage_info": {},
"get_network_info": {},
},
"系统_Hook状态": {
"get_hook_status": {},
"get_process_info": {},
"get_wechat_version": {},
},
}
# RPC方法名映射snake_case -> camelCase
def to_camel(name: str) -> str:
parts = name.split('_')
return parts[0] + ''.join(p.capitalize() for p in parts[1:])
def run_verification():
"""运行完整验证"""
print("=" * 60)
print(" 工作手机SDK - Frida无线真机验证")
print(f" 设备: {DEVICE_IP}:{FRIDA_PORT}")
print(f" 时间: {datetime.now().isoformat()}")
print("=" * 60)
print()
# 连接
device, session, script, rpc = connect_frida()
print("\n✅ Frida 连接成功!开始验证...\n")
# 初始截图
take_screenshot("00_initial_state")
results = []
total = 0
passed = 0
failed = 0
for module_name, actions in VERIFICATION_PLAN.items():
print(f"\n{'='*50}")
print(f" 模块: {module_name}")
print(f"{'='*50}")
for action, params in actions.items():
total += 1
rpc_method = to_camel(action)
print(f"\n [{total}] {action} -> rpc.{rpc_method}()")
print(f" 参数: {json.dumps(params, ensure_ascii=False)[:80]}")
start_time = time.time()
try:
fn = getattr(rpc, rpc_method, None)
if fn is None:
# 尝试下划线版本
fn = getattr(rpc, action, None)
if fn is None:
result = {"success": False, "error": f"RPC方法不存在: {rpc_method}"}
status = "FAIL"
failed += 1
else:
result = fn(params) if params else fn({})
if isinstance(result, dict) and result.get("success", False):
status = "PASS"
passed += 1
elif isinstance(result, dict) and "error" in result:
status = "FAIL"
failed += 1
else:
# 有返回就算通过
status = "PASS"
passed += 1
except Exception as e:
result = {"success": False, "error": str(e)}
status = "FAIL"
failed += 1
latency = int((time.time() - start_time) * 1000)
icon = "" if status == "PASS" else ""
print(f" {icon} {status} ({latency}ms)")
if isinstance(result, dict):
# 打印简要结果
preview = json.dumps(result, ensure_ascii=False)[:120]
print(f" 结果: {preview}")
# 截图
screenshot = take_screenshot(f"{module_name}_{action}")
results.append({
"module": module_name,
"action": action,
"rpc_method": rpc_method,
"params": params,
"status": status,
"latency_ms": latency,
"result": result if isinstance(result, dict) else str(result),
"screenshot": screenshot,
"timestamp": datetime.now().isoformat(),
})
# 每个操作间隔一下,避免触发风控
time.sleep(0.5)
# 最终截图
take_screenshot("99_final_state")
# 保存结果
report = {
"title": "工作手机SDK - Frida无线真机验证报告",
"device": {
"ip": DEVICE_IP,
"port": FRIDA_PORT,
"serial": DEVICE_SERIAL,
"model": "2312DRAABC",
"android": "13",
},
"test_time": datetime.now().isoformat(),
"total": total,
"passed": passed,
"failed": failed,
"pass_rate": f"{passed/total*100:.1f}%" if total > 0 else "0%",
"results": results,
}
with open(RESULT_FILE, 'w', encoding='utf-8') as f:
json.dump(report, f, ensure_ascii=False, indent=2)
print(f"\n{'='*60}")
print(f" 验证完成!")
print(f" 总计: {total} | 通过: {passed} | 失败: {failed}")
print(f" 通过率: {passed/total*100:.1f}%" if total > 0 else " 无测试")
print(f" 结果: {RESULT_FILE}")
print(f" 截图: {SCREENSHOT_DIR}")
print(f"{'='*60}")
# 清理
try:
script.unload()
session.detach()
except:
pass
return report
if __name__ == "__main__":
try:
report = run_verification()
except Exception as e:
print(f"\n❌ 验证失败: {e}")
import traceback
traceback.print_exc()
sys.exit(1)