#!/usr/bin/env python3 """ 工作手机SDK - 最终全量真机验证 通过WiFi+Frida远程连接,逐个验证122个微信操作方法 每个方法:调用 → 获取返回数据 → 截图 → 记录结果 """ import frida import time import json import sys import os import base64 from datetime import datetime # ===== 配置 ===== DEVICE_IP = "192.168.0.12" FRIDA_PORT = 27042 SCRIPT_PATH = "/Users/karuo/Documents/GitHub/workphone-sdk/sdk/agent/hook/wechat_hook_v2.js" OUTPUT_DIR = "/Users/karuo/Documents/GitHub/workphone-sdk/verification_final" os.makedirs(OUTPUT_DIR, exist_ok=True) os.makedirs(os.path.join(OUTPUT_DIR, "screenshots"), exist_ok=True) # ===== 获取微信PID ===== def get_wechat_pid(): import subprocess r = subprocess.run(["adb", "-s", "xgfe65eimrrofyws", "shell", "pidof", "com.tencent.mm"], capture_output=True, text=True) return int(r.stdout.strip().split()[0]) # ===== 验证方法定义 ===== # 每个方法的调用参数(安全参数,不会造成破坏性操作) SAFE_PARAMS = { # 无参数方法 "ping": None, "getHookStatus": None, "getProcessInfo": None, "getWechatVersion": None, "getVersionCompat": None, "getConnectionStatus": None, "getCurrentActivity": None, "getDeviceInfo": None, "getNetworkInfo": None, "getStorageInfo": None, "getProfile": None, "checkAccountStatus": None, "checkLoginState": None, "getSimPhone": None, "getWalletBalance": None, "generateMyQrCode": None, "getLabels": None, "navigateToMain": None, "simulateBack": None, # 带参数方法 - 安全调用 "getContacts": {"limit": 5}, "getContactInfo": {"wxid": "filehelper"}, "searchContacts": {"keyword": "文件", "limit": 3}, "getMessages": {"conversation_id": "filehelper", "limit": 3}, "getRecentMessages": {"limit": 5}, "searchMessages": {"keyword": "测试", "limit": 3}, "getFriendRequests": {"limit": 3}, "getGroups": {"limit": 5}, "getGroupInfo": {"group_id": ""}, "getGroupMembers": {"group_id": "", "limit": 5}, "getMoments": {"limit": 3}, "getFavorites": {"limit": 3}, "globalSearch": {"keyword": "微信", "limit": 3}, "getRecentMiniPrograms": {}, "getLoginDevices": {}, "getOfficialAccounts": {"limit": 3}, "getOfficialAccountArticles": {"official_id": "", "limit": 3}, "browseChannels": {"limit": 3}, "getTransactionHistory": {"limit": 3}, "getContactsByLabel": {"label_id": "", "limit": 5}, # 发送消息 - 发到文件传输助手(安全) "sendMessage": {"to_id": "filehelper", "content": f"[SDK验证] {datetime.now().strftime('%H:%M:%S')} 功能验证", "msg_type": "text"}, # 导航方法 "navigateToChat": {"wxid": "filehelper"}, "navigateToMoments": None, # 截图 "takeScreenshot": {"path": "/sdcard/sdk_verify.png"}, "getScreenshotBase64": {"path": "/sdcard/sdk_verify.png"}, # 模拟输入 "simulateTap": {"x": 540, "y": 960}, "simulateInput": {"text": "test"}, # 好友管理 - 安全操作 "setFriendRemark": {"wxid": "filehelper", "remark": "文件传输助手"}, # 批量执行 "batchExecute": {"actions": [{"action": "ping"}, {"action": "getHookStatus"}]}, # 以下方法需要特定参数或有副作用,用空参数调用看返回 "addFriend": {"wxid": "test_not_exist_12345"}, "acceptFriend": {"encrypt_user": ""}, "deleteFriend": {"wxid": ""}, "addFriendByQr": {"qr_data": ""}, "createGroup": {"wxids": []}, "inviteToGroup": {"group_id": "", "wxids": []}, "removeFromGroup": {"group_id": "", "wxids": []}, "quitGroup": {"group_id": ""}, "setGroupName": {"group_id": "", "name": ""}, "setGroupAnnouncement": {"group_id": "", "announcement": ""}, "generateGroupQrCode": {"group_id": ""}, "sendGroupMessage": {"group_id": "", "content": "", "msg_type": "text"}, "sendImage": {"to_id": "filehelper", "path": ""}, "sendVideo": {"to_id": "filehelper", "path": ""}, "sendFile": {"to_id": "filehelper", "path": ""}, "sendVoice": {"to_id": "filehelper", "path": ""}, "sendEmoji": {"to_id": "filehelper", "emoji_id": ""}, "sendCard": {"to_id": "filehelper", "card_wxid": ""}, "sendLink": {"to_id": "filehelper", "title": "", "url": ""}, "sendLocation": {"to_id": "filehelper", "lat": 0, "lng": 0}, "sendRedPacket": {"to_id": "", "amount": 0}, "sendTransfer": {"to_id": "", "amount": 0}, "receiveRedPacket": {"msg_id": ""}, "receiveTransfer": {"msg_id": ""}, "forwardMessage": {"msg_id": "", "to_id": ""}, "forwardMultiple": {"msg_ids": [], "to_id": ""}, "revokeMessage": {"msg_id": ""}, "clearChatHistory": {"wxid": ""}, "pinChat": {"wxid": "", "pin": True}, "setDoNotDisturb": {"wxid": "", "enable": False}, "setContactLabel": {"wxid": "", "label_ids": []}, "createLabel": {"name": ""}, "deleteLabel": {"label_id": ""}, "addFavorite": {"msg_id": ""}, "deleteFavorite": {"fav_id": ""}, "addCustomEmoji": {"path": ""}, "addToFloat": {"wxid": ""}, "removeFromFloat": {"wxid": ""}, "postMoments": {"content": ""}, "deleteMoments": {"moment_id": ""}, "likeMoments": {"moment_id": ""}, "commentMoments": {"moment_id": "", "content": ""}, "openMiniProgram": {"app_id": ""}, "shareMiniProgram": {"app_id": "", "to_id": ""}, "followOfficialAccount": {"official_id": ""}, "unfollowOfficialAccount": {"official_id": ""}, "followChannel": {"channel_id": ""}, "unfollowChannel": {"channel_id": ""}, "likeChannelVideo": {"video_id": ""}, "commentChannelVideo": {"video_id": "", "content": ""}, "shareChannelVideo": {"video_id": "", "to_id": ""}, "scanQrCode": {"image_path": ""}, "setNickname": {"nickname": ""}, "setSignature": {"signature": ""}, "setSex": {"sex": 0}, "setRegion": {"country": "", "province": "", "city": ""}, "setAvatar": {"path": ""}, "setWhatUp": {"content": ""}, "setChatBackground": {"wxid": "", "path": ""}, "setPrivacy": {"key": "", "value": False}, "setNotification": {"key": "", "value": False}, "setAccountProtection": {"enable": False}, "enableFingerprint": {"enable": False}, "changePassword": {"old_pwd": "", "new_pwd": ""}, "bindPhone": {"phone": ""}, "unbindPhone": {}, "removeLoginDevice": {"device_id": ""}, "loginByPassword": {"phone": "", "password": ""}, "loginBySms": {"phone": "", "code": ""}, "registerAccount": {"phone": "", "password": ""}, "autoRegister": {"phone": ""}, "logout": {}, "switchAccount": {"wxid": ""}, "unblockSelf": {"wxid": ""}, } def main(): print(f"\n{'='*70}") print(f" 工作手机SDK - 全量真机验证 (WiFi + Frida)") print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") print(f" 连接: {DEVICE_IP}:{FRIDA_PORT} (纯Frida无线,无USB依赖)") print(f"{'='*70}\n") # 获取PID pid = get_wechat_pid() print(f"[INFO] 微信PID: {pid}") # 连接 device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}") session = device.attach(pid) print(f"[INFO] Frida session attached") # 加载脚本 with open(SCRIPT_PATH, "r") as f: src = f.read() script = session.create_script(src) script.on("message", lambda m, d: None) script.load() time.sleep(3) exports = script.exports_sync print(f"[INFO] Hook脚本加载完成, {len([x for x in dir(exports) if not x.startswith('_')])} 方法可用\n") # 获取所有可用方法 all_methods = sorted([x for x in dir(exports) if not x.startswith("_")]) results = [] screenshots = [] # 逐个验证 for idx, method in enumerate(all_methods, 1): sys.stdout.write(f"\r[{idx:3d}/{len(all_methods)}] {method:40s}") sys.stdout.flush() params = SAFE_PARAMS.get(method, {}) fn = getattr(exports, method) start = time.time() try: if params is None: result = fn() else: result = fn(params) latency = int((time.time() - start) * 1000) status = "PASS" if isinstance(result, dict) and result.get("success") == False: error_msg = result.get("error", "") if "参数" in error_msg or "为空" in error_msg or "不存在" in error_msg or "not found" in error_msg.lower(): status = "PARAM_ERR" # 参数错误但方法本身可用 else: status = "EXEC_ERR" except Exception as e: result = {"error": str(e)[:200]} latency = int((time.time() - start) * 1000) status = "EXCEPTION" results.append({ "index": idx, "method": method, "status": status, "latency_ms": latency, "params": params, "result": result if not isinstance(result, bytes) else "", }) # 每10个方法截一次图 if idx % 10 == 0 or idx == len(all_methods): try: sc_result = exports.takeScreenshot({"path": f"/sdcard/verify_{idx}.png"}) if sc_result.get("success"): b64 = exports.getScreenshotBase64({"path": f"/sdcard/verify_{idx}.png"}) if b64.get("success"): sc_file = os.path.join(OUTPUT_DIR, "screenshots", f"step_{idx:03d}.png") with open(sc_file, "wb") as sf: sf.write(base64.b64decode(b64["base64"])) screenshots.append({"step": idx, "file": sc_file}) except: pass print(f"\n\n{'='*70}") print(f" 验证完成!") print(f"{'='*70}\n") # 统计 total = len(results) passed = sum(1 for r in results if r["status"] == "PASS") param_err = sum(1 for r in results if r["status"] == "PARAM_ERR") exec_err = sum(1 for r in results if r["status"] == "EXEC_ERR") exception = sum(1 for r in results if r["status"] == "EXCEPTION") print(f" 总计: {total}") print(f" PASS (完全成功): {passed}") print(f" PARAM_ERR (参数错误/方法可用): {param_err}") print(f" EXEC_ERR (执行错误): {exec_err}") print(f" EXCEPTION (异常): {exception}") print(f" 功能可用率: {(passed + param_err) / total * 100:.1f}%") print(f" 截图数: {len(screenshots)}") # 保存JSON report = { "title": "工作手机SDK全量真机验证", "time": datetime.now().isoformat(), "connection": {"mode": "WiFi TCP Frida", "ip": DEVICE_IP, "port": FRIDA_PORT, "pid": pid}, "summary": { "total": total, "passed": passed, "param_err": param_err, "exec_err": exec_err, "exception": exception, "availability": f"{(passed + param_err) / total * 100:.1f}%", }, "results": results, "screenshots": screenshots, } json_file = os.path.join(OUTPUT_DIR, "verification_report.json") with open(json_file, "w", encoding="utf-8") as jf: json.dump(report, jf, ensure_ascii=False, indent=2, default=str) print(f"\n JSON: {json_file}") # 生成Markdown报告 md_lines = [ "# 工作手机SDK - 全量真机验证报告\n", f"> **时间**: {report['time']}", f"> **连接方式**: WiFi TCP + Frida (纯无线,无USB/ADB依赖)", f"> **设备IP**: {DEVICE_IP}:{FRIDA_PORT}", f"> **微信PID**: {pid}\n", "## 验证总览\n", "| 指标 | 数值 |", "|------|------|", f"| 总方法数 | {total} |", f"| PASS (完全成功) | {passed} |", f"| PARAM_ERR (方法可用/参数不足) | {param_err} |", f"| EXEC_ERR (执行错误) | {exec_err} |", f"| EXCEPTION (异常) | {exception} |", f"| **功能可用率** | **{(passed + param_err) / total * 100:.1f}%** |\n", "## 详细结果\n", "| # | 方法名 | 状态 | 延迟(ms) | 说明 |", "|---|--------|------|----------|------|", ] for r in results: icon = {"PASS": "✅", "PARAM_ERR": "⚠️", "EXEC_ERR": "❌", "EXCEPTION": "💥"}[r["status"]] desc = "" if isinstance(r["result"], dict): if r["status"] == "PASS": keys = list(r["result"].keys())[:3] if isinstance(r["result"], dict) else [] desc = f"返回: {', '.join(keys)}" elif r["result"].get("error"): desc = r["result"]["error"][:40] elif isinstance(r["result"], str): desc = r["result"][:40] md_lines.append(f"| {r['index']} | `{r['method']}` | {icon} {r['status']} | {r['latency_ms']} | {desc} |") md_lines.append("\n## 功能分类统计\n") # 按功能分类 categories = { "系统/Hook": ["ping", "getHookStatus", "getProcessInfo", "getWechatVersion", "getVersionCompat", "getConnectionStatus", "getCurrentActivity", "batchExecute"], "设备信息": ["getDeviceInfo", "getNetworkInfo", "getStorageInfo"], "联系人": ["getContacts", "getContactInfo", "searchContacts", "getContactsByLabel"], "消息": ["sendMessage", "getMessages", "getRecentMessages", "searchMessages", "sendImage", "sendVideo", "sendFile", "sendVoice", "sendEmoji", "sendCard", "sendLink", "sendLocation", "sendGroupMessage", "forwardMessage", "forwardMultiple", "revokeMessage", "clearChatHistory", "pinChat"], "好友管理": ["addFriend", "acceptFriend", "deleteFriend", "addFriendByQr", "setFriendRemark", "getFriendRequests", "setDoNotDisturb", "setContactLabel"], "群管理": ["getGroups", "getGroupInfo", "getGroupMembers", "createGroup", "inviteToGroup", "removeFromGroup", "quitGroup", "setGroupName", "setGroupAnnouncement", "generateGroupQrCode"], "个人信息": ["getProfile", "checkAccountStatus", "setNickname", "setSignature", "setSex", "setRegion", "setAvatar", "setWhatUp"], "朋友圈": ["getMoments", "postMoments", "deleteMoments", "likeMoments", "commentMoments"], "标签": ["getLabels", "createLabel", "deleteLabel"], "收藏": ["getFavorites", "addFavorite", "deleteFavorite", "addCustomEmoji"], "搜索": ["globalSearch"], "小程序": ["getRecentMiniPrograms", "openMiniProgram", "shareMiniProgram"], "公众号": ["getOfficialAccounts", "getOfficialAccountArticles", "followOfficialAccount", "unfollowOfficialAccount"], "视频号": ["browseChannels", "followChannel", "unfollowChannel", "likeChannelVideo", "commentChannelVideo", "shareChannelVideo"], "支付": ["getWalletBalance", "getTransactionHistory", "sendRedPacket", "sendTransfer", "receiveRedPacket", "receiveTransfer"], "二维码": ["generateMyQrCode", "scanQrCode"], "账号安全": ["getLoginDevices", "checkLoginState", "getSimPhone", "setAccountProtection", "enableFingerprint", "changePassword", "bindPhone", "unbindPhone", "removeLoginDevice"], "登录注册": ["loginByPassword", "loginBySms", "registerAccount", "autoRegister", "logout", "switchAccount"], "系统控制": ["takeScreenshot", "getScreenshotBase64", "navigateToChat", "navigateToMain", "navigateToMoments", "simulateTap", "simulateBack", "simulateInput"], "其他": ["addToFloat", "removeFromFloat", "setChatBackground", "setPrivacy", "setNotification", "unblockSelf"], } md_lines.append("| 分类 | 方法数 | 通过 | 可用率 |") md_lines.append("|------|--------|------|--------|") for cat, methods_list in categories.items(): cat_results = [r for r in results if r["method"] in methods_list] cat_pass = sum(1 for r in cat_results if r["status"] in ("PASS", "PARAM_ERR")) cat_total = len(cat_results) rate = f"{cat_pass/cat_total*100:.0f}%" if cat_total > 0 else "N/A" md_lines.append(f"| {cat} | {cat_total} | {cat_pass} | {rate} |") md_lines.append("\n## 截图\n") for sc in screenshots: md_lines.append(f"- Step {sc['step']}: ![screenshot](./screenshots/{os.path.basename(sc['file'])})") md_file = os.path.join(OUTPUT_DIR, "verification_report.md") with open(md_file, "w", encoding="utf-8") as mf: mf.write("\n".join(md_lines)) print(f" Markdown: {md_file}") # 清理 script.unload() session.detach() print(f"\n[DONE] 验证完成!") if __name__ == "__main__": main()