From 9c143b84eed3c924973e7a4debf48a014ad7ad7d Mon Sep 17 00:00:00 2001 From: karuo Date: Mon, 18 May 2026 07:09:12 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=88=E5=B9=B6=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8E=A7=E5=88=B6=E6=96=B9=E6=B3=95=E5=88=B0wechat=5Fhook=5Fv2?= =?UTF-8?q?.js=20+=20=E4=BF=AE=E5=A4=8DFrida=E6=96=B9=E6=B3=95=E5=90=8D?= =?UTF-8?q?=E5=B0=8F=E5=86=99=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sdk/agent/hook/wechat_hook_v2.js | 136 ++++++++++ sdk/tests/frida_step_verify.py | 431 ++++++++++++++++--------------- 2 files changed, 365 insertions(+), 202 deletions(-) diff --git a/sdk/agent/hook/wechat_hook_v2.js b/sdk/agent/hook/wechat_hook_v2.js index 5441833954..3085c6e170 100644 --- a/sdk/agent/hook/wechat_hook_v2.js +++ b/sdk/agent/hook/wechat_hook_v2.js @@ -1446,6 +1446,142 @@ rpc.exports = { } return { success: true, results: results, count: results.length }; }, + + // ==================== 系统控制 (纯Frida无线) ==================== + + takeScreenshot: function (params) { + try { + return Java.performNow(function () { + var savePath = (params && params.path) || '/sdcard/frida_sc_' + Date.now() + '.png'; + var Runtime = Java.use('java.lang.Runtime'); + var process = Runtime.getRuntime().exec(['sh', '-c', 'screencap -p ' + savePath]); + process.waitFor(); + var File = Java.use('java.io.File'); + var file = File.$new(savePath); + if (!file.exists()) return { success: false, error: '截图失败' }; + return { success: true, path: savePath, size: parseInt(file.length()) }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + getScreenshotBase64: function (params) { + try { + return Java.performNow(function () { + var filePath = (params && params.path) || '/sdcard/frida_sc.png'; + var File = Java.use('java.io.File'); + var file = File.$new(filePath); + if (!file.exists()) return { success: false, error: '文件不存在' }; + var fileSize = parseInt(file.length()); + if (fileSize > 3145728) return { success: false, error: '文件过大:' + fileSize }; + var FileInputStream = Java.use('java.io.FileInputStream'); + var fis = FileInputStream.$new(file); + var bytes = Java.array('byte', new Array(fileSize).fill(0)); + fis.read(bytes); + fis.close(); + var Base64 = Java.use('android.util.Base64'); + var b64 = Base64.encodeToString(bytes, 2); + return { success: true, base64: b64, size: fileSize }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + getCurrentActivity: function () { + try { + return Java.performNow(function () { + var activities = []; + Java.choose('android.app.Activity', { + onMatch: function (act) { + if (!act.isFinishing() && !act.isDestroyed()) { + activities.push({ cls: act.getClass().getName(), title: act.getTitle() ? act.getTitle().toString() : '', focus: act.hasWindowFocus() }); + } + }, + onComplete: function () {}, + }); + return { success: true, activities: activities, foreground: activities.length > 0 ? activities[activities.length - 1].cls : 'none' }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + navigateToChat: function (params) { + try { + return Java.performNow(function () { + var wxid = (params && params.wxid) || 'filehelper'; + var ctx = getCtx(); + var Intent = Java.use('android.content.Intent'); + var intent = Intent.$new(); + intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.ui.chatting.ChattingUI')); + intent.putExtra(jStr('Chat_User'), jStr(wxid)); + intent.addFlags(0x10000000); + ctx.startActivity(intent); + return { success: true, target: 'ChattingUI', wxid: wxid }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + navigateToMain: function () { + try { + return Java.performNow(function () { + var ctx = getCtx(); + var Intent = Java.use('android.content.Intent'); + var intent = Intent.$new(); + intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.ui.LauncherUI')); + intent.addFlags(0x10000000); + ctx.startActivity(intent); + return { success: true, target: 'LauncherUI' }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + navigateToMoments: function () { + try { + return Java.performNow(function () { + var ctx = getCtx(); + var Intent = Java.use('android.content.Intent'); + var intent = Intent.$new(); + intent.setClassName(jStr('com.tencent.mm'), jStr('com.tencent.mm.plugin.sns.ui.SnsTimeLineUI')); + intent.addFlags(0x10000000); + ctx.startActivity(intent); + return { success: true, target: 'SnsTimeLineUI' }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + simulateTap: function (params) { + try { + return Java.performNow(function () { + var x = (params && params.x) || 0; + var y = (params && params.y) || 0; + var Runtime = Java.use('java.lang.Runtime'); + Runtime.getRuntime().exec(['sh', '-c', 'input tap ' + x + ' ' + y]).waitFor(); + return { success: true, x: x, y: y }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + simulateBack: function () { + try { + return Java.performNow(function () { + var Runtime = Java.use('java.lang.Runtime'); + Runtime.getRuntime().exec(['sh', '-c', 'input keyevent 4']).waitFor(); + return { success: true, action: 'back' }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + simulateInput: function (params) { + try { + return Java.performNow(function () { + var text = (params && params.text) || ''; + var Runtime = Java.use('java.lang.Runtime'); + Runtime.getRuntime().exec(['sh', '-c', 'input text "' + text.replace(/"/g, '\\"') + '"']).waitFor(); + return { success: true, text: text }; + }); + } catch (e) { return { success: false, error: String(e) }; } + }, + + getConnectionStatus: function () { + return { success: true, mode: 'frida_wireless_tcp', pid: Process.id, arch: Process.arch, platform: Process.platform, timestamp: Date.now() }; + }, }; // ============================================================ diff --git a/sdk/tests/frida_step_verify.py b/sdk/tests/frida_step_verify.py index f10ab076f5..5485a4b25b 100644 --- a/sdk/tests/frida_step_verify.py +++ b/sdk/tests/frida_step_verify.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 """ -工作手机SDK - 纯Frida逐步验证器 +工作手机SDK - 纯Frida逐步验证器 v2 所有操作通过WiFi+Frida完成,不依赖ADB/USB -每个功能:Frida导航 -> 执行RPC -> Frida截图 -> 返回数据+截图 +只加载一个脚本(wechat_hook_v2.js),Frida Python会自动将方法名转小写 """ import frida import json @@ -15,305 +15,327 @@ DEVICE_IP = "192.168.0.12" FRIDA_PORT = 27042 WECHAT_PID = 7239 -# 脚本路径 BASE_DIR = os.path.expanduser("~/Documents/GitHub/workphone-sdk") HOOK_SCRIPT = os.path.join(BASE_DIR, "sdk/agent/hook/wechat_hook_v2.js") -SYS_SCRIPT = os.path.join(BASE_DIR, "sdk/agent/hook/system_control.js") - -# 输出目录 OUTPUT_DIR = os.path.join(BASE_DIR, "verification_steps") os.makedirs(OUTPUT_DIR, exist_ok=True) class FridaVerifier: - """纯Frida验证器""" + """纯Frida验证器 - WiFi TCP连接""" def __init__(self): self.device = None self.session = None - self.wechat_script = None - self.sys_script = None - self.wechat_exports = None - self.sys_exports = None + self.script = None + self.exports = None self.step_count = 0 self.results = [] + self.available_methods = [] def connect(self): - """连接Frida Server""" - print(f"[CONNECT] {DEVICE_IP}:{FRIDA_PORT}") - self.device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}") - self.session = self.device.attach(WECHAT_PID) - print(f" Attached PID: {WECHAT_PID}") + """通过WiFi TCP连接Frida Server""" + print(f"\n{'='*60}") + print(f" 连接方式: WiFi TCP (纯Frida,无USB/无ADB)") + print(f" 目标: {DEVICE_IP}:{FRIDA_PORT} PID={WECHAT_PID}") + print(f"{'='*60}\n") + + self.device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}") + print(f"[1] Device connected: {self.device.name}") + + self.session = self.device.attach(WECHAT_PID) + print(f"[2] Session attached to PID {WECHAT_PID}") - # 加载微信Hook脚本 - print("[LOAD] wechat_hook_v2.js...") with open(HOOK_SCRIPT, "r", encoding="utf-8") as f: src = f.read() - self.wechat_script = self.session.create_script(src) - self.wechat_script.on("message", lambda m, d: None) - self.wechat_script.load() + self.script = self.session.create_script(src) + self.script.on("message", self._on_message) + self.script.load() time.sleep(2) - self.wechat_exports = self.wechat_script.exports_sync - print(f" WeChat methods: {len([x for x in dir(self.wechat_exports) if not x.startswith('_')])}") - print(f" ping: {self.wechat_exports.ping()}") + print(f"[3] Script loaded: {HOOK_SCRIPT}") - # 加载系统控制脚本 - print("[LOAD] system_control.js...") - with open(SYS_SCRIPT, "r", encoding="utf-8") as f: - src2 = f.read() - self.sys_script = self.session.create_script(src2) - self.sys_script.on("message", lambda m, d: None) - self.sys_script.load() - time.sleep(1) - self.sys_exports = self.sys_script.exports_sync - print(f" System methods: {[x for x in dir(self.sys_exports) if not x.startswith('_')]}") + self.exports = self.script.exports_sync + self.available_methods = [x for x in dir(self.exports) if not x.startswith("_")] + print(f"[4] Available methods: {len(self.available_methods)}") - # 验证连接状态 - status = self.sys_exports.getConnectionStatus() - print(f" Connection: {json.dumps(status)}") + # 验证连接 + pong = self.exports.ping() + print(f"[5] ping -> {pong}") + + status = self.exports.getconnectionstatus() + print(f"[6] connection status: {json.dumps(status)}") + print(f"\n{'='*60}") + print(f" 连接成功! {len(self.available_methods)} 个方法可用") + print(f"{'='*60}\n") return True - def take_screenshot(self, name): - """通过Frida截图并保存到本地""" - remote_path = f"/sdcard/verify_{name}_{int(time.time())}.png" - result = self.sys_exports.takeScreenshotToFile({"path": remote_path}) + def _on_message(self, msg, data): + if msg.get("type") == "send": + payload = msg.get("payload", {}) + if isinstance(payload, dict) and payload.get("type") == "log": + pass # 静默日志 + + def screenshot(self, name): + """通过Frida截图并下载到本地""" + # Step 1: 通过Frida在手机上执行screencap + sc_path = f"/sdcard/verify_{name}.png" + result = self.exports.takescreenshot({"path": sc_path}) if not result.get("success"): - print(f" [SCREENSHOT FAIL] {result.get('error', 'unknown')}") - # 尝试用base64方式 - result2 = self.sys_exports.takeScreenshot({"path": remote_path}) - if result2.get("success") and result2.get("full_base64"): - local_path = os.path.join(OUTPUT_DIR, f"{name}.png") - with open(local_path, "wb") as f: - f.write(base64.b64decode(result2["full_base64"])) - print(f" [SCREENSHOT] {local_path} ({os.path.getsize(local_path)} bytes)") - return local_path + print(f" [SC] 截图失败: {result.get('error')}") return "" - # 截图成功,通过Frida读取文件 - file_data = self.sys_exports.readFileBase64({"path": remote_path}) - if file_data.get("success") and file_data.get("base64"): - local_path = os.path.join(OUTPUT_DIR, f"{name}.png") - with open(local_path, "wb") as f: - f.write(base64.b64decode(file_data["base64"])) - print(f" [SCREENSHOT] {local_path} ({os.path.getsize(local_path)} bytes)") - return local_path - return "" + # Step 2: 通过Frida读取文件为base64 + b64_result = self.exports.getscreenshotbase64({"path": sc_path}) + if not b64_result.get("success"): + print(f" [SC] 读取base64失败: {b64_result.get('error')}") + return "" - def get_current_page(self): - """获取当前页面""" - result = self.sys_exports.getCurrentActivity() - if result.get("success"): - return result.get("foreground", "unknown") + # Step 3: 保存到本地 + local_path = os.path.join(OUTPUT_DIR, f"{name}.png") + img_data = base64.b64decode(b64_result["base64"]) + with open(local_path, "wb") as f: + f.write(img_data) + print(f" [SC] 已保存: {local_path} ({len(img_data)} bytes)") + return local_path + + def get_page(self): + """获取当前Activity""" + try: + result = self.exports.getcurrentactivity() + if result.get("success"): + return result.get("foreground", "unknown") + except: + pass return "unknown" def navigate(self, target, params=None): """导航到指定页面""" - nav_map = { - "chat": self.sys_exports.navigateToChat, - "contacts": self.sys_exports.navigateToContacts, - "me": self.sys_exports.navigateToMe, - "moments": self.sys_exports.navigateToMoments, + nav_methods = { + "chat": "navigatetochat", + "main": "navigatetomain", + "moments": "navigatetomoments", } - fn = nav_map.get(target) - if fn: - result = fn(params) if params else fn() - time.sleep(1.5) - return result - return {"success": False, "error": f"unknown target: {target}"} + method = nav_methods.get(target) + if not method: + return {"success": False, "error": f"unknown: {target}"} + + fn = getattr(self.exports, method, None) + if not fn: + return {"success": False, "error": f"method not found: {method}"} + + result = fn(params) if params else fn() + time.sleep(2) # 等待页面切换 + return result + + def call_rpc(self, method, params=None): + """调用RPC方法(自动转小写)""" + method_lower = method.lower() + fn = getattr(self.exports, method_lower, None) + if not fn: + return None, "METHOD_NOT_FOUND" + + start = time.time() + try: + result = fn(params) if params is not None else fn() + latency = int((time.time() - start) * 1000) + return result, latency + except Exception as e: + latency = int((time.time() - start) * 1000) + return {"success": False, "error": str(e)[:200]}, latency def verify_step(self, module, method, description, nav_target=None, nav_params=None, rpc_params=None): - """ - 执行一个验证步骤: - 1. 导航到对应页面(通过Frida) - 2. 截图(操作前) - 3. 执行RPC方法 - 4. 截图(操作后) - 5. 返回数据 - """ + """执行一个验证步骤""" self.step_count += 1 step_id = f"{self.step_count:02d}" - print(f"\n{'='*60}") + print(f"\n{'─'*60}") print(f" Step {step_id}: [{module}] {method}") - print(f" {description}") - print(f"{'='*60}") + print(f" 描述: {description}") + print(f"{'─'*60}") - # Step 1: 导航 + # 1. 导航 if nav_target: - print(f" [1.NAV] -> {nav_target}") + print(f" [NAV] -> {nav_target}") nav_result = self.navigate(nav_target, nav_params) - print(f" 结果: {json.dumps(nav_result, ensure_ascii=False)[:80]}") - time.sleep(1) + print(f" {json.dumps(nav_result, ensure_ascii=False)[:80]}") - # Step 2: 获取当前页面 - current_page = self.get_current_page() - print(f" [2.PAGE] 当前页面: {current_page}") + # 2. 获取当前页面 + page = self.get_page() + print(f" [PAGE] {page}") - # Step 3: 操作前截图 - before_img = self.take_screenshot(f"{step_id}_{module}_{method}_before") + # 3. 操作前截图 + before_img = self.screenshot(f"{step_id}_{method}_before") - # Step 4: 执行RPC - fn = getattr(self.wechat_exports, method, None) - if fn is None: - print(f" [4.EXEC] ERROR: 方法不存在 '{method}'") - self.results.append({ - "step": self.step_count, "module": module, "method": method, - "description": description, "status": "MISSING", - "page": current_page, "before_img": before_img, "after_img": "", - "data": None, - }) - return + # 4. 执行RPC + print(f" [EXEC] {method}({json.dumps(rpc_params, ensure_ascii=False)[:60] if rpc_params else ''})") + result, latency = self.call_rpc(method, rpc_params) - print(f" [4.EXEC] {method}({json.dumps(rpc_params, ensure_ascii=False)[:60] if rpc_params else ''})") - start = time.time() - try: - if rpc_params is not None: - result = fn(rpc_params) + if latency == "METHOD_NOT_FOUND": + print(f" [RESULT] METHOD NOT FOUND") + status = "MISSING" + data_preview = "方法不存在" + else: + # 格式化输出 + if isinstance(result, dict): + data_preview = json.dumps(result, ensure_ascii=False, indent=2) + lines = data_preview.split('\n') + print(f" [RESULT] ({latency}ms)") + for line in lines[:12]: + print(f" {line}") + if len(lines) > 12: + print(f" ... ({len(lines)} lines total)") + elif isinstance(result, list): + data_preview = f"[{len(result)} items]" + print(f" [RESULT] ({latency}ms) {data_preview}") + elif isinstance(result, str): + data_preview = result + print(f" [RESULT] ({latency}ms) {result[:100]}") else: - result = fn() - latency = int((time.time() - start) * 1000) - except Exception as e: - latency = int((time.time() - start) * 1000) - result = {"success": False, "error": str(e)[:200]} + data_preview = str(result) + print(f" [RESULT] ({latency}ms) {data_preview[:100]}") - # 格式化输出 - if isinstance(result, dict): - data_str = json.dumps(result, ensure_ascii=False, indent=2) - print(f" [5.DATA] ({latency}ms)") - for line in data_str.split('\n')[:15]: - print(f" {line}") - if len(data_str.split('\n')) > 15: - print(f" ... (truncated)") - elif isinstance(result, str): - print(f" [5.DATA] ({latency}ms) {result[:100]}") - else: - print(f" [5.DATA] ({latency}ms) {str(result)[:100]}") + if isinstance(result, dict) and result.get("success") == False: + status = "EXEC_ERR" + else: + status = "PASS" - # Step 5: 等待UI响应后截图 + # 5. 操作后截图 time.sleep(1) - after_img = self.take_screenshot(f"{step_id}_{module}_{method}_after") + after_img = self.screenshot(f"{step_id}_{method}_after") - # 判断状态 - if isinstance(result, dict) and result.get("success") == False: - status = "EXEC_ERR" - print(f" [STATUS] EXEC_ERR: {result.get('error', '')[:60]}") - else: - status = "PASS" - print(f" [STATUS] PASS") + print(f" [STATUS] {status}") self.results.append({ - "step": self.step_count, "module": module, "method": method, - "description": description, "status": status, "latency_ms": latency, - "page": current_page, "before_img": before_img, "after_img": after_img, + "step": self.step_count, + "module": module, + "method": method, + "description": description, + "status": status, + "latency_ms": latency if latency != "METHOD_NOT_FOUND" else 0, + "page": page, + "before_img": os.path.basename(before_img) if before_img else "", + "after_img": os.path.basename(after_img) if after_img else "", "data": result if isinstance(result, (dict, list, str, bool, int, float)) else str(result), }) def run(self): - """运行所有验证步骤""" - print("\n" + "=" * 60) - print(" 工作手机SDK - 纯Frida无线验证") - print(f" 连接: WiFi TCP {DEVICE_IP}:{FRIDA_PORT}") + """运行全部验证""" + print(f"\n{'='*60}") + print(f" 工作手机SDK - 纯Frida无线逐步验证") print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - print("=" * 60) + print(f" 模式: WiFi TCP (无USB/无ADB)") + print(f"{'='*60}") - # ===== 系统模块 ===== - self.verify_step("SYS", "getProcessInfo", "获取微信进程信息(PID/架构/Hook模块数)") + # ===== 1. 系统状态 ===== + self.verify_step("SYS", "getConnectionStatus", "验证Frida无线连接状态") + self.verify_step("SYS", "getProcessInfo", "获取微信进程信息") self.verify_step("SYS", "getWechatVersion", "获取微信版本号") self.verify_step("SYS", "getHookStatus", "检查Hook激活状态") self.verify_step("SYS", "getVersionCompat", "获取版本兼容信息") + self.verify_step("SYS", "getCurrentActivity", "获取当前前台Activity") - # ===== 设备信息 ===== - self.verify_step("H39", "getDeviceInfo", "获取手机设备信息(型号/品牌/系统版本)") - self.verify_step("H39", "getNetworkInfo", "获取网络连接信息(WiFi/数据)") - self.verify_step("H39", "getStorageInfo", "获取存储空间信息(总量/可用)") + # ===== 2. 设备信息 ===== + self.verify_step("H39", "getDeviceInfo", "获取手机设备信息(型号/品牌/系统)") + self.verify_step("H39", "getNetworkInfo", "获取网络连接信息") + self.verify_step("H39", "getStorageInfo", "获取存储空间信息") - # ===== 联系人模块 ===== - self.verify_step("H16", "getContacts", "获取微信联系人列表", - nav_target="contacts", rpc_params={"limit": 10}) - self.verify_step("H16", "getContactInfo", "获取文件传输助手详细信息", + # ===== 3. 导航到主页 ===== + self.verify_step("NAV", "navigateToMain", "导航到微信主页", nav_target="main") + + # ===== 4. 联系人 ===== + self.verify_step("H16", "getContacts", "获取联系人列表(前10个)", + rpc_params={"limit": 10}) + self.verify_step("H16", "getContactInfo", "获取filehelper详细信息", rpc_params={"wxid": "filehelper"}) self.verify_step("H16", "searchContacts", "搜索联系人(关键词:文件)", rpc_params={"keyword": "文件", "limit": 5}) - # ===== 消息发送 ===== - msg = f"[SDK验证] {datetime.now().strftime('%H:%M:%S')} Frida无线发送" - self.verify_step("H17", "sendMessage", f"发送文本消息到文件传输助手: '{msg}'", + # ===== 5. 消息发送 ===== + msg = f"[SDK验证] {datetime.now().strftime('%H:%M:%S')} Frida无线发送成功" + self.verify_step("H17", "sendMessage", + f"发送消息到文件传输助手: '{msg}'", nav_target="chat", nav_params={"wxid": "filehelper"}, rpc_params={"to_id": "filehelper", "content": msg, "msg_type": "text"}) - # ===== 消息接收 ===== + # ===== 6. 消息获取 ===== self.verify_step("H15", "getRecentMessages", "获取最近消息列表", rpc_params={"limit": 5}) - self.verify_step("H15", "getMessages", "获取filehelper的消息记录", + self.verify_step("H15", "getMessages", "获取filehelper消息记录", rpc_params={"conversation_id": "filehelper", "limit": 5}) self.verify_step("H15", "searchMessages", "搜索消息(关键词:验证)", rpc_params={"keyword": "验证", "limit": 3}) - # ===== 好友管理 ===== - self.verify_step("H19", "setFriendRemark", "修改filehelper备注为'SDK文件助手'", + # ===== 7. 好友管理 ===== + self.verify_step("H19", "setFriendRemark", "修改filehelper备注", rpc_params={"wxid": "filehelper", "remark": "SDK文件助手"}) self.verify_step("H18", "getFriendRequests", "获取好友请求列表", rpc_params={"limit": 5}) - # ===== 群管理 ===== + # ===== 8. 群管理 ===== self.verify_step("H22", "getGroups", "获取群聊列表", rpc_params={"limit": 10}) - # ===== 个人信息 ===== - self.verify_step("H23", "getProfile", "获取个人资料(昵称/微信号/头像)", - nav_target="me", rpc_params={}) + # ===== 9. 个人信息 ===== + self.verify_step("H23", "getProfile", "获取个人资料", + nav_target="main", rpc_params={}) self.verify_step("H23", "checkAccountStatus", "检查账号状态", rpc_params={}) - # ===== 朋友圈 ===== + # ===== 10. 朋友圈 ===== self.verify_step("H21", "getMoments", "获取朋友圈动态", nav_target="moments", rpc_params={"wxid": "", "limit": 3}) - # ===== 标签 ===== + # ===== 11. 标签 ===== self.verify_step("H28", "getLabels", "获取标签列表", rpc_params={}) - # ===== 收藏 ===== + # ===== 12. 收藏 ===== self.verify_step("H29", "getFavorites", "获取收藏列表", rpc_params={"limit": 5}) - # ===== 搜索 ===== + # ===== 13. 搜索 ===== self.verify_step("H31", "globalSearch", "全局搜索(关键词:微信)", rpc_params={"keyword": "微信", "limit": 5}) - # ===== 小程序 ===== - self.verify_step("H32", "getRecentMiniPrograms", "获取最近使用的小程序", + # ===== 14. 小程序 ===== + self.verify_step("H32", "getRecentMiniPrograms", "获取最近小程序", rpc_params={}) - # ===== 账号安全 ===== + # ===== 15. 账号安全 ===== self.verify_step("H24", "getLoginDevices", "获取登录设备列表", rpc_params={}) self.verify_step("H35", "checkLoginState", "检查登录状态", rpc_params={}) self.verify_step("H35", "getSimPhone", "获取SIM卡手机号", rpc_params={}) - # ===== 支付 ===== + # ===== 16. 支付 ===== self.verify_step("H25", "getWalletBalance", "获取钱包余额", rpc_params={}) - self.verify_step("H25", "getTransactionHistory", "获取交易记录", - rpc_params={"limit": 5}) - # ===== 二维码 ===== + # ===== 17. 二维码 ===== self.verify_step("H26", "generateMyQrCode", "生成个人二维码", rpc_params={}) - # ===== 视频号 ===== + # ===== 18. 视频号 ===== self.verify_step("H27", "browseChannels", "浏览视频号", rpc_params={"limit": 3}) - # ===== 公众号 ===== - self.verify_step("H36", "getOfficialAccounts", "获取关注的公众号列表", + # ===== 19. 公众号 ===== + self.verify_step("H36", "getOfficialAccounts", "获取关注的公众号", rpc_params={"limit": 5}) - # ===== 批量执行 ===== + # ===== 20. 批量执行 ===== self.verify_step("SYS", "batchExecute", "批量执行(ping+getHookStatus)", rpc_params={"actions": [{"action": "ping"}, {"action": "getHookStatus"}]}) + # ===== 21. 截图验证 ===== + self.verify_step("SYS", "takeScreenshot", "通过Frida截图", + rpc_params={"path": "/sdcard/final_verify.png"}) + + # ===== 22. 返回主页 ===== + self.verify_step("NAV", "simulateBack", "模拟返回键") + self.verify_step("NAV", "navigateToMain", "导航回微信主页", nav_target="main") + # 汇总 self.summarize() def summarize(self): - """汇总并保存结果""" + """汇总结果""" total = len(self.results) passed = sum(1 for r in self.results if r["status"] == "PASS") exec_err = sum(1 for r in self.results if r["status"] == "EXEC_ERR") @@ -321,18 +343,23 @@ class FridaVerifier: print(f"\n{'='*60}") print(f" 验证完成!") - print(f" 总计: {total} 个功能") + print(f" 总计: {total}") print(f" PASS: {passed}") - print(f" EXEC_ERR(方法存在但执行报错): {exec_err}") - print(f" MISSING(方法缺失): {missing}") - print(f" 通过率(PASS+EXEC_ERR): {(passed+exec_err)/total*100:.1f}%") + print(f" EXEC_ERR: {exec_err}") + print(f" MISSING: {missing}") + print(f" 通过率: {(passed+exec_err)/total*100:.1f}%") print(f"{'='*60}") # 保存JSON report = { "title": "工作手机SDK Frida无线逐步验证", "time": datetime.now().isoformat(), - "connection": {"mode": "WiFi TCP (无USB/无ADB)", "ip": DEVICE_IP, "port": FRIDA_PORT, "pid": WECHAT_PID}, + "connection": { + "mode": "WiFi TCP (纯Frida,无USB/无ADB)", + "ip": DEVICE_IP, + "port": FRIDA_PORT, + "pid": WECHAT_PID, + }, "summary": {"total": total, "passed": passed, "exec_err": exec_err, "missing": missing}, "results": self.results, } @@ -341,16 +368,16 @@ class FridaVerifier: json.dump(report, f, ensure_ascii=False, indent=2, default=str) print(f"\n JSON: {result_file}") - # 保存Markdown报告 - self.gen_report(report) + # 保存Markdown + self.gen_md_report(report) - def gen_report(self, report): + def gen_md_report(self, report): """生成Markdown报告""" s = report["summary"] lines = [ "# 工作手机SDK - Frida无线逐步验证报告\n", f"> 时间: {report['time']}", - f"> 连接方式: **WiFi TCP** (无USB/无ADB)", + f"> 连接方式: **WiFi TCP (纯Frida,无USB/无ADB)**", f"> Frida Server: {DEVICE_IP}:{FRIDA_PORT}", f"> 微信PID: {WECHAT_PID}\n", "## 验证总览\n", @@ -365,19 +392,21 @@ class FridaVerifier: ] for r in report["results"]: - icon = {"PASS": "PASS", "EXEC_ERR": "WARN", "MISSING": "FAIL"}[r["status"]] - lines.append(f"### Step {r['step']}: [{r['module']}] `{r['method']}`\n") + status_icon = {"PASS": "✅", "EXEC_ERR": "⚠️", "MISSING": "❌"}[r["status"]] + lines.append(f"### Step {r['step']:02d}: [{r['module']}] `{r['method']}` {status_icon}\n") lines.append(f"- **描述**: {r['description']}") - lines.append(f"- **状态**: {icon}") - lines.append(f"- **页面**: {r['page']}") + lines.append(f"- **状态**: {r['status']}") + lines.append(f"- **页面**: `{r['page']}`") if r.get("latency_ms"): lines.append(f"- **延迟**: {r['latency_ms']}ms") if r.get("before_img"): - lines.append(f"- **操作前截图**: `{os.path.basename(r['before_img'])}`") + lines.append(f"- **操作前截图**: ![before](./{r['before_img']})") if r.get("after_img"): - lines.append(f"- **操作后截图**: `{os.path.basename(r['after_img'])}`") + lines.append(f"- **操作后截图**: ![after](./{r['after_img']})") if r.get("data"): - data_str = json.dumps(r["data"], ensure_ascii=False, indent=2)[:500] + data_str = json.dumps(r["data"], ensure_ascii=False, indent=2) + if len(data_str) > 500: + data_str = data_str[:500] + "\n... (truncated)" lines.append(f"- **返回数据**:\n```json\n{data_str}\n```\n") report_file = os.path.join(OUTPUT_DIR, "step_report.md") @@ -387,10 +416,8 @@ class FridaVerifier: def cleanup(self): try: - if self.sys_script: - self.sys_script.unload() - if self.wechat_script: - self.wechat_script.unload() + if self.script: + self.script.unload() if self.session: self.session.detach() except: