feat: 合并系统控制方法到wechat_hook_v2.js + 修复Frida方法名小写问题

This commit is contained in:
karuo
2026-05-18 07:09:12 +00:00
parent 1237c28f3f
commit 9c143b84ee
2 changed files with 365 additions and 202 deletions

View File

@@ -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() };
},
};
// ============================================================

View File

@@ -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: