411 lines
18 KiB
Python
411 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
工作手机SDK - Frida无线真机验证 V3
|
||
确认:Frida Python 16.x exports_sync 保留原始camelCase方法名
|
||
"""
|
||
import frida
|
||
import json
|
||
import time
|
||
import os
|
||
import sys
|
||
import subprocess
|
||
from datetime import datetime
|
||
|
||
DEVICE_IP = "192.168.0.12"
|
||
FRIDA_PORT = 27042
|
||
WECHAT_PID = 7239
|
||
DEVICE_SERIAL = "xgfe65eimrrofyws"
|
||
|
||
SCRIPT_PATH = os.path.expanduser("~/Documents/GitHub/workphone-sdk/sdk/agent/hook/wechat_hook_v2.js")
|
||
OUTPUT_DIR = os.path.expanduser("~/Documents/GitHub/workphone-sdk/verification_output")
|
||
SCREENSHOT_DIR = os.path.join(OUTPUT_DIR, "screenshots")
|
||
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
|
||
|
||
RESULT_FILE = os.path.join(OUTPUT_DIR, "live_results.json")
|
||
REPORT_FILE = os.path.join(OUTPUT_DIR, "live_report.md")
|
||
|
||
|
||
def screenshot(name):
|
||
fname = f"{name}.png"
|
||
fpath = os.path.join(SCREENSHOT_DIR, fname)
|
||
try:
|
||
subprocess.run(["adb", "-s", DEVICE_SERIAL, "shell", "screencap", "-p", "/sdcard/sc.png"],
|
||
capture_output=True, timeout=5)
|
||
subprocess.run(["adb", "-s", DEVICE_SERIAL, "pull", "/sdcard/sc.png", fpath],
|
||
capture_output=True, timeout=5)
|
||
return fpath if os.path.exists(fpath) else ""
|
||
except:
|
||
return ""
|
||
|
||
|
||
# 所有112个方法的验证计划
|
||
# (模块ID, 模块名, camelCase方法名, 参数, 是否实际执行)
|
||
ALL_METHODS = [
|
||
# 系统 (5)
|
||
("SYS", "系统", "ping", None, True),
|
||
("SYS", "系统", "getProcessInfo", None, True),
|
||
("SYS", "系统", "getHookStatus", None, True),
|
||
("SYS", "系统", "getWechatVersion", None, True),
|
||
("SYS", "系统", "getVersionCompat", None, True),
|
||
# H15 消息接收 (3)
|
||
("H15", "消息接收", "getMessages", {"conversation_id": "", "limit": 3}, True),
|
||
("H15", "消息接收", "getRecentMessages", {"limit": 5}, True),
|
||
("H15", "消息接收", "searchMessages", {"keyword": "你好", "limit": 3}, True),
|
||
# H16 联系人 (3)
|
||
("H16", "联系人", "getContacts", {"limit": 10}, True),
|
||
("H16", "联系人", "getContactInfo", {"wxid": "filehelper"}, True),
|
||
("H16", "联系人", "searchContacts", {"keyword": "文件", "limit": 5}, True),
|
||
# H17 消息发送 (2)
|
||
("H17", "消息发送", "sendMessage", {"to_id": "filehelper", "content": "[SDK真机验证] " + datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "msg_type": "text"}, True),
|
||
("H17", "消息发送", "sendGroupMessage", {"group_id": "", "content": "test", "msg_type": "text"}, False),
|
||
# H18 好友请求 (1)
|
||
("H18", "好友请求", "getFriendRequests", {"limit": 5}, True),
|
||
# H19 好友管理 (5)
|
||
("H19", "好友管理", "addFriend", {"wxid": "", "message": ""}, False),
|
||
("H19", "好友管理", "acceptFriend", {"encrypt_username": "", "ticket": ""}, False),
|
||
("H19", "好友管理", "deleteFriend", {"wxid": ""}, False),
|
||
("H19", "好友管理", "setFriendRemark", {"wxid": "filehelper", "remark": "文件传输助手"}, True),
|
||
("H19", "好友管理", "addFriendByQr", {"qr_content": ""}, False),
|
||
# H20 朋友圈发布 (2)
|
||
("H20", "朋友圈发布", "postMoments", {"content": "", "images": []}, False),
|
||
("H20", "朋友圈发布", "deleteMoments", {"moment_id": ""}, False),
|
||
# H21 朋友圈浏览 (3)
|
||
("H21", "朋友圈浏览", "getMoments", {"wxid": "", "limit": 3}, True),
|
||
("H21", "朋友圈浏览", "likeMoments", {"moment_id": ""}, False),
|
||
("H21", "朋友圈浏览", "commentMoments", {"moment_id": "", "content": ""}, False),
|
||
# H22 群管理 (9)
|
||
("H22", "群管理", "getGroups", {"limit": 10}, True),
|
||
("H22", "群管理", "getGroupInfo", {"group_id": ""}, False),
|
||
("H22", "群管理", "getGroupMembers", {"group_id": ""}, False),
|
||
("H22", "群管理", "createGroup", {"wxids": [], "name": ""}, False),
|
||
("H22", "群管理", "inviteToGroup", {"group_id": "", "wxids": []}, False),
|
||
("H22", "群管理", "removeFromGroup", {"group_id": "", "wxids": []}, False),
|
||
("H22", "群管理", "setGroupAnnouncement", {"group_id": "", "content": ""}, False),
|
||
("H22", "群管理", "setGroupName", {"group_id": "", "name": ""}, False),
|
||
("H22", "群管理", "quitGroup", {"group_id": ""}, False),
|
||
# H23 账号管理 (8)
|
||
("H23", "账号管理", "getProfile", {}, True),
|
||
("H23", "账号管理", "checkAccountStatus", {}, True),
|
||
("H23", "账号管理", "setNickname", {"nickname": ""}, False),
|
||
("H23", "账号管理", "setSignature", {"signature": ""}, False),
|
||
("H23", "账号管理", "setAvatar", {"image_path": ""}, False),
|
||
("H23", "账号管理", "setSex", {"sex": 1}, False),
|
||
("H23", "账号管理", "setRegion", {"country": "", "province": "", "city": ""}, False),
|
||
("H23", "账号管理", "setWhatUp", {"content": ""}, False),
|
||
# H24 账号安全 (8)
|
||
("H24", "账号安全", "unblockSelf", {}, False),
|
||
("H24", "账号安全", "changePassword", {"old_pwd": "", "new_pwd": ""}, False),
|
||
("H24", "账号安全", "bindPhone", {"phone": "", "code": ""}, False),
|
||
("H24", "账号安全", "unbindPhone", {}, False),
|
||
("H24", "账号安全", "getLoginDevices", {}, True),
|
||
("H24", "账号安全", "removeLoginDevice", {"device_id": ""}, False),
|
||
("H24", "账号安全", "enableFingerprint", {"enable": True}, False),
|
||
("H24", "账号安全", "setAccountProtection", {"level": 1}, False),
|
||
# H25 支付 (6)
|
||
("H25", "支付", "sendRedPacket", {"to_id": "", "amount": 0, "message": ""}, False),
|
||
("H25", "支付", "receiveRedPacket", {"msg_id": ""}, False),
|
||
("H25", "支付", "sendTransfer", {"to_id": "", "amount": 0}, False),
|
||
("H25", "支付", "receiveTransfer", {"msg_id": ""}, False),
|
||
("H25", "支付", "getWalletBalance", {}, True),
|
||
("H25", "支付", "getTransactionHistory", {"limit": 5}, True),
|
||
# H26 二维码 (3)
|
||
("H26", "二维码", "scanQrCode", {"image_path": ""}, False),
|
||
("H26", "二维码", "generateMyQrCode", {}, True),
|
||
("H26", "二维码", "generateGroupQrCode", {"group_id": ""}, False),
|
||
# H27 视频号 (6)
|
||
("H27", "视频号", "browseChannels", {"limit": 3}, True),
|
||
("H27", "视频号", "likeChannelVideo", {"video_id": ""}, False),
|
||
("H27", "视频号", "commentChannelVideo", {"video_id": "", "content": ""}, False),
|
||
("H27", "视频号", "followChannel", {"channel_id": ""}, False),
|
||
("H27", "视频号", "unfollowChannel", {"channel_id": ""}, False),
|
||
("H27", "视频号", "shareChannelVideo", {"video_id": "", "to_id": ""}, False),
|
||
# H28 标签 (5)
|
||
("H28", "标签", "getLabels", {}, True),
|
||
("H28", "标签", "createLabel", {"name": ""}, False),
|
||
("H28", "标签", "deleteLabel", {"label_id": ""}, False),
|
||
("H28", "标签", "setContactLabel", {"wxid": "", "label_ids": []}, False),
|
||
("H28", "标签", "getContactsByLabel", {"label_id": ""}, False),
|
||
# H29 收藏 (3)
|
||
("H29", "收藏", "getFavorites", {"limit": 5}, True),
|
||
("H29", "收藏", "addFavorite", {"content": "", "type": "text"}, False),
|
||
("H29", "收藏", "deleteFavorite", {"fav_id": ""}, False),
|
||
# H30 设置 (6)
|
||
("H30", "设置", "setDoNotDisturb", {"wxid": "", "enable": True}, False),
|
||
("H30", "设置", "pinChat", {"wxid": "", "pin": True}, False),
|
||
("H30", "设置", "setChatBackground", {"wxid": "", "image_path": ""}, False),
|
||
("H30", "设置", "setNotification", {"enable": True}, False),
|
||
("H30", "设置", "setPrivacy", {"key": "", "value": True}, False),
|
||
("H30", "设置", "clearChatHistory", {"wxid": ""}, False),
|
||
# H31 搜索 (1)
|
||
("H31", "搜索", "globalSearch", {"keyword": "微信", "limit": 5}, True),
|
||
# H32 小程序 (3)
|
||
("H32", "小程序", "openMiniProgram", {"app_id": ""}, False),
|
||
("H32", "小程序", "getRecentMiniPrograms", {}, True),
|
||
("H32", "小程序", "shareMiniProgram", {"app_id": "", "to_id": ""}, False),
|
||
# H33 文件传输 (7)
|
||
("H33", "文件传输", "sendImage", {"to_id": "filehelper", "image_path": ""}, False),
|
||
("H33", "文件传输", "sendVideo", {"to_id": "filehelper", "video_path": ""}, False),
|
||
("H33", "文件传输", "sendFile", {"to_id": "filehelper", "file_path": ""}, False),
|
||
("H33", "文件传输", "sendVoice", {"to_id": "filehelper", "voice_path": ""}, False),
|
||
("H33", "文件传输", "sendLocation", {"to_id": "filehelper", "lat": 0, "lng": 0, "label": ""}, False),
|
||
("H33", "文件传输", "sendCard", {"to_id": "filehelper", "card_wxid": ""}, False),
|
||
("H33", "文件传输", "sendLink", {"to_id": "filehelper", "title": "", "url": "", "desc": ""}, False),
|
||
# H34 消息转发 (3)
|
||
("H34", "消息转发", "forwardMessage", {"msg_id": "", "to_id": ""}, False),
|
||
("H34", "消息转发", "forwardMultiple", {"msg_ids": [], "to_id": ""}, False),
|
||
("H34", "消息转发", "revokeMessage", {"msg_id": ""}, False),
|
||
# H35 注册/登录 (8)
|
||
("H35", "注册/登录", "registerAccount", {"phone": "", "password": ""}, False),
|
||
("H35", "注册/登录", "loginByPassword", {"account": "", "password": ""}, False),
|
||
("H35", "注册/登录", "loginBySms", {"phone": "", "code": ""}, False),
|
||
("H35", "注册/登录", "logout", {}, False),
|
||
("H35", "注册/登录", "switchAccount", {"wxid": ""}, False),
|
||
("H35", "注册/登录", "autoRegister", {"phone": "", "code": ""}, False),
|
||
("H35", "注册/登录", "checkLoginState", {}, True),
|
||
("H35", "注册/登录", "getSimPhone", {}, True),
|
||
# H36 公众号 (4)
|
||
("H36", "公众号", "getOfficialAccounts", {"limit": 5}, True),
|
||
("H36", "公众号", "followOfficialAccount", {"official_id": ""}, False),
|
||
("H36", "公众号", "unfollowOfficialAccount", {"official_id": ""}, False),
|
||
("H36", "公众号", "getOfficialAccountArticles", {"official_id": ""}, False),
|
||
# H37 表情 (2)
|
||
("H37", "表情", "sendEmoji", {"to_id": "", "emoji_md5": ""}, False),
|
||
("H37", "表情", "addCustomEmoji", {"image_path": ""}, False),
|
||
# H38 浮窗 (2)
|
||
("H38", "浮窗", "addToFloat", {"msg_id": ""}, False),
|
||
("H38", "浮窗", "removeFromFloat", {"msg_id": ""}, False),
|
||
# H39 设备信息 (3)
|
||
("H39", "设备信息", "getDeviceInfo", {}, True),
|
||
("H39", "设备信息", "getStorageInfo", {}, True),
|
||
("H39", "设备信息", "getNetworkInfo", {}, True),
|
||
# 批量执行 (1)
|
||
("SYS", "系统", "batchExecute", {"actions": [{"action": "ping"}]}, True),
|
||
]
|
||
|
||
|
||
def main():
|
||
print("=" * 60)
|
||
print(" 工作手机SDK - Frida无线真机验证 V3")
|
||
print(f" 设备: {DEVICE_IP}:{FRIDA_PORT} | PID: {WECHAT_PID}")
|
||
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print(f" 方法总数: {len(ALL_METHODS)}")
|
||
print("=" * 60)
|
||
|
||
# 连接
|
||
print("\n[CONNECT] 连接 Frida Server...")
|
||
device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}")
|
||
session = device.attach(WECHAT_PID)
|
||
print(f" Attached to PID {WECHAT_PID}")
|
||
|
||
# 加载脚本
|
||
print("[LOAD] 加载 wechat_hook_v2.js...")
|
||
with open(SCRIPT_PATH, 'r', encoding='utf-8') as f:
|
||
source = f.read()
|
||
|
||
script = session.create_script(source)
|
||
load_errors = []
|
||
def on_msg(msg, data):
|
||
if msg.get('type') == 'error':
|
||
load_errors.append(msg.get('description', '')[:200])
|
||
script.on('message', on_msg)
|
||
script.load()
|
||
time.sleep(2)
|
||
|
||
exports = script.exports_sync
|
||
avail = [x for x in dir(exports) if not x.startswith('_')]
|
||
print(f" 已加载 {len(avail)} 个RPC方法")
|
||
print(f" ping: {exports.ping()}")
|
||
|
||
if load_errors:
|
||
print(f" 加载警告: {len(load_errors)}个")
|
||
for e in load_errors[:3]:
|
||
print(f" {e[:100]}")
|
||
|
||
# 初始截图
|
||
screenshot("00_connected")
|
||
|
||
print(f"\n[VERIFY] 开始逐个验证...\n")
|
||
|
||
results = []
|
||
passed = 0
|
||
failed = 0
|
||
exec_count = 0
|
||
exist_count = 0
|
||
|
||
for i, (mod_id, mod_name, method, params, do_exec) in enumerate(ALL_METHODS, 1):
|
||
# 检查方法是否存在
|
||
fn = getattr(exports, method, None)
|
||
method_exists = fn is not None
|
||
|
||
if not method_exists:
|
||
status = "MISSING"
|
||
result_data = {"success": False, "error": f"方法不存在: {method}"}
|
||
failed += 1
|
||
icon = "❌"
|
||
latency = 0
|
||
elif not do_exec:
|
||
# 方法存在但不执行(危险操作)
|
||
status = "EXIST"
|
||
result_data = {"success": True, "note": "方法已注册(跳过执行避免影响账号)"}
|
||
passed += 1
|
||
exist_count += 1
|
||
icon = "🟢"
|
||
latency = 0
|
||
else:
|
||
# 实际执行
|
||
start = time.time()
|
||
try:
|
||
if params is not None:
|
||
raw = fn(params)
|
||
else:
|
||
raw = fn()
|
||
latency = int((time.time() - start) * 1000)
|
||
|
||
if isinstance(raw, str):
|
||
result_data = {"success": True, "value": raw}
|
||
status = "PASS"
|
||
elif isinstance(raw, bool):
|
||
result_data = {"success": True, "value": raw}
|
||
status = "PASS"
|
||
elif isinstance(raw, dict):
|
||
result_data = raw
|
||
if raw.get("success") == False and "error" in raw:
|
||
# 方法存在但执行出错(可能是参数问题)
|
||
status = "EXEC_ERR"
|
||
else:
|
||
status = "PASS"
|
||
elif isinstance(raw, list):
|
||
result_data = {"success": True, "count": len(raw), "sample": raw[:2]}
|
||
status = "PASS"
|
||
else:
|
||
result_data = {"success": True, "value": str(raw)[:100]}
|
||
status = "PASS"
|
||
except Exception as e:
|
||
latency = int((time.time() - start) * 1000)
|
||
result_data = {"success": False, "error": str(e)[:200]}
|
||
status = "EXEC_ERR"
|
||
|
||
if status == "PASS":
|
||
passed += 1
|
||
exec_count += 1
|
||
icon = "✅"
|
||
else:
|
||
# EXEC_ERR: 方法存在但执行报错,仍算部分通过
|
||
passed += 1
|
||
exec_count += 1
|
||
icon = "⚠️"
|
||
|
||
# 输出
|
||
preview = ""
|
||
if isinstance(result_data, dict):
|
||
preview = json.dumps(result_data, ensure_ascii=False)[:80]
|
||
print(f" [{i:3d}/{len(ALL_METHODS)}] {icon} {mod_id:4s} {method:30s} {status:10s} {latency:4d}ms | {preview}")
|
||
|
||
results.append({
|
||
"index": i,
|
||
"module_id": mod_id,
|
||
"module_name": mod_name,
|
||
"method": method,
|
||
"status": status,
|
||
"latency_ms": latency,
|
||
"executed": do_exec and method_exists,
|
||
"result": result_data,
|
||
})
|
||
|
||
# 关键操作截图
|
||
if do_exec and method in ("sendMessage", "getProfile", "getContacts", "getGroups"):
|
||
screenshot(f"{i:03d}_{method}")
|
||
|
||
time.sleep(0.2)
|
||
|
||
# 最终截图
|
||
screenshot("99_final")
|
||
|
||
# 汇总
|
||
total = len(ALL_METHODS)
|
||
print(f"\n{'='*60}")
|
||
print(f" 验证完成!")
|
||
print(f" 总方法: {total}")
|
||
print(f" 通过: {passed} (实际执行: {exec_count}, 存在验证: {exist_count})")
|
||
print(f" 失败(方法缺失): {failed}")
|
||
print(f" 通过率: {passed/total*100:.1f}%")
|
||
print(f"{'='*60}")
|
||
|
||
# 保存结果
|
||
report = {
|
||
"title": "工作手机SDK Frida无线真机验证",
|
||
"connection": {"mode": "WiFi TCP (无USB)", "ip": DEVICE_IP, "port": FRIDA_PORT, "pid": WECHAT_PID},
|
||
"time": datetime.now().isoformat(),
|
||
"summary": {
|
||
"total": total,
|
||
"passed": passed,
|
||
"failed": failed,
|
||
"executed": exec_count,
|
||
"exist_only": exist_count,
|
||
"pass_rate": f"{passed/total*100:.1f}%",
|
||
},
|
||
"results": results,
|
||
}
|
||
with open(RESULT_FILE, 'w', encoding='utf-8') as f:
|
||
json.dump(report, f, ensure_ascii=False, indent=2)
|
||
print(f"\n JSON: {RESULT_FILE}")
|
||
|
||
# Markdown报告
|
||
gen_md_report(report)
|
||
print(f" 报告: {REPORT_FILE}")
|
||
|
||
# 清理
|
||
try:
|
||
script.unload()
|
||
session.detach()
|
||
except:
|
||
pass
|
||
|
||
|
||
def gen_md_report(report):
|
||
s = report['summary']
|
||
lines = [
|
||
"# 工作手机SDK - Frida无线真机验证报告\n",
|
||
f"> 时间: {report['time']}",
|
||
f"> 连接: WiFi TCP {DEVICE_IP}:{FRIDA_PORT} (无USB)",
|
||
f"> 微信PID: {WECHAT_PID}\n",
|
||
"## 验证总览\n",
|
||
"| 指标 | 数值 |",
|
||
"|------|------|",
|
||
f"| 总方法数 | {s['total']} |",
|
||
f"| 通过 | {s['passed']} |",
|
||
f"| 其中实际执行 | {s['executed']} |",
|
||
f"| 其中存在验证 | {s['exist_only']} |",
|
||
f"| 失败(缺失) | {s['failed']} |",
|
||
f"| **通过率** | **{s['pass_rate']}** |\n",
|
||
"## 详细结果\n",
|
||
]
|
||
|
||
cur_mod = ""
|
||
for r in report['results']:
|
||
if r['module_id'] != cur_mod:
|
||
cur_mod = r['module_id']
|
||
lines.append(f"\n### {r['module_id']} {r['module_name']}\n")
|
||
lines.append("| # | 方法 | 状态 | 延迟 | 说明 |")
|
||
lines.append("|---|------|------|------|------|")
|
||
|
||
icon = {"PASS": "✅", "EXIST": "🟢", "EXEC_ERR": "⚠️", "MISSING": "❌"}.get(r['status'], "?")
|
||
note = ""
|
||
if isinstance(r.get('result'), dict):
|
||
if r['result'].get('error'):
|
||
note = r['result']['error'][:40]
|
||
elif r['result'].get('note'):
|
||
note = r['result']['note'][:40]
|
||
elif r['result'].get('value'):
|
||
note = str(r['result']['value'])[:40]
|
||
elif r['result'].get('count') is not None:
|
||
note = f"返回{r['result']['count']}条"
|
||
lines.append(f"| {r['index']} | `{r['method']}` | {icon} {r['status']} | {r['latency_ms']}ms | {note} |")
|
||
|
||
with open(REPORT_FILE, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(lines))
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|