#!/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__), '..', '..', '..', '开发文档', '8、部署', '05-测试验收', 'screenshots', 'verification_110', ) 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)