feat: 合并系统控制方法到wechat_hook_v2.js + 修复Frida方法名小写问题
This commit is contained in:
@@ -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() };
|
||||
},
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -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"- **操作前截图**: ")
|
||||
if r.get("after_img"):
|
||||
lines.append(f"- **操作后截图**: `{os.path.basename(r['after_img'])}`")
|
||||
lines.append(f"- **操作后截图**: ")
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user