246 lines
8.9 KiB
Python
246 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
|
"""快速真机验证 - 所有112个方法"""
|
|
import frida
|
|
import json
|
|
import time
|
|
import os
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
DEVICE_IP = "192.168.0.12"
|
|
FRIDA_PORT = 27042
|
|
WECHAT_PID = 7239
|
|
SCRIPT_PATH = os.path.expanduser("~/Documents/GitHub/workphone-sdk/sdk/agent/hook/wechat_hook_v2.js")
|
|
OUTPUT_FILE = os.path.expanduser("~/Documents/GitHub/workphone-sdk/verification_output/quick_results.json")
|
|
REPORT_FILE = os.path.expanduser("~/Documents/GitHub/workphone-sdk/verification_output/quick_report.md")
|
|
|
|
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
|
|
|
|
# 安全可执行的方法(只读操作)
|
|
SAFE_EXEC = {
|
|
"ping": None,
|
|
"getProcessInfo": None,
|
|
"getHookStatus": None,
|
|
"getWechatVersion": None,
|
|
"getVersionCompat": None,
|
|
"getMessages": {"conversation_id": "", "limit": 3},
|
|
"getRecentMessages": {"limit": 5},
|
|
"searchMessages": {"keyword": "hello", "limit": 3},
|
|
"getContacts": {"limit": 10},
|
|
"getContactInfo": {"wxid": "filehelper"},
|
|
"searchContacts": {"keyword": "file", "limit": 5},
|
|
"sendMessage": {"to_id": "filehelper", "content": "[SDK Verify] " + datetime.now().strftime("%H:%M:%S"), "msg_type": "text"},
|
|
"getFriendRequests": {"limit": 5},
|
|
"setFriendRemark": {"wxid": "filehelper", "remark": "FileHelper"},
|
|
"getMoments": {"wxid": "", "limit": 3},
|
|
"getGroups": {"limit": 10},
|
|
"getProfile": {},
|
|
"checkAccountStatus": {},
|
|
"getLoginDevices": {},
|
|
"getWalletBalance": {},
|
|
"getTransactionHistory": {"limit": 5},
|
|
"generateMyQrCode": {},
|
|
"browseChannels": {"limit": 3},
|
|
"getLabels": {},
|
|
"getFavorites": {"limit": 5},
|
|
"globalSearch": {"keyword": "test", "limit": 5},
|
|
"getRecentMiniPrograms": {},
|
|
"checkLoginState": {},
|
|
"getSimPhone": {},
|
|
"getOfficialAccounts": {"limit": 5},
|
|
"getDeviceInfo": {},
|
|
"getStorageInfo": {},
|
|
"getNetworkInfo": {},
|
|
"batchExecute": {"actions": [{"action": "ping"}]},
|
|
}
|
|
|
|
# 不安全的方法(只验证存在性)
|
|
UNSAFE_EXIST = [
|
|
"addFriend", "acceptFriend", "deleteFriend", "addFriendByQr",
|
|
"postMoments", "deleteMoments", "likeMoments", "commentMoments",
|
|
"getGroupInfo", "getGroupMembers", "createGroup", "inviteToGroup",
|
|
"removeFromGroup", "setGroupAnnouncement", "setGroupName", "quitGroup",
|
|
"setNickname", "setSignature", "setAvatar", "setSex", "setRegion", "setWhatUp",
|
|
"unblockSelf", "changePassword", "bindPhone", "unbindPhone",
|
|
"removeLoginDevice", "enableFingerprint", "setAccountProtection",
|
|
"sendRedPacket", "receiveRedPacket", "sendTransfer", "receiveTransfer",
|
|
"scanQrCode", "generateGroupQrCode",
|
|
"likeChannelVideo", "commentChannelVideo", "followChannel", "unfollowChannel", "shareChannelVideo",
|
|
"createLabel", "deleteLabel", "setContactLabel", "getContactsByLabel",
|
|
"addFavorite", "deleteFavorite",
|
|
"setDoNotDisturb", "pinChat", "setChatBackground", "setNotification", "setPrivacy", "clearChatHistory",
|
|
"openMiniProgram", "shareMiniProgram",
|
|
"sendImage", "sendVideo", "sendFile", "sendVoice", "sendLocation", "sendCard", "sendLink",
|
|
"forwardMessage", "forwardMultiple", "revokeMessage",
|
|
"registerAccount", "loginByPassword", "loginBySms", "logout", "switchAccount", "autoRegister",
|
|
"followOfficialAccount", "unfollowOfficialAccount", "getOfficialAccountArticles",
|
|
"sendEmoji", "addCustomEmoji",
|
|
"addToFloat", "removeFromFloat",
|
|
"sendGroupMessage",
|
|
]
|
|
|
|
|
|
def main():
|
|
print("=" * 60)
|
|
print(" Frida Wireless Live Verification")
|
|
print("=" * 60)
|
|
|
|
# Connect
|
|
print("\n[1] Connecting...")
|
|
device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}")
|
|
session = device.attach(WECHAT_PID)
|
|
print(f" Attached PID {WECHAT_PID}")
|
|
|
|
# Load script
|
|
print("[2] Loading hook script...")
|
|
with open(SCRIPT_PATH, "r", encoding="utf-8") as f:
|
|
source = f.read()
|
|
|
|
script = session.create_script(source)
|
|
script.on("message", lambda m, d: None)
|
|
script.load()
|
|
time.sleep(2)
|
|
|
|
exports = script.exports_sync
|
|
avail = [x for x in dir(exports) if not x.startswith("_")]
|
|
print(f" Loaded {len(avail)} methods")
|
|
print(f" ping: {exports.ping()}")
|
|
|
|
# Phase 1: Execute safe methods
|
|
print("\n[3] Executing safe methods...")
|
|
results = []
|
|
exec_pass = 0
|
|
exec_fail = 0
|
|
|
|
for method, params in SAFE_EXEC.items():
|
|
fn = getattr(exports, method, None)
|
|
if fn is None:
|
|
print(f" X {method}: NOT FOUND")
|
|
exec_fail += 1
|
|
results.append({"method": method, "status": "MISSING", "executed": True})
|
|
continue
|
|
|
|
try:
|
|
start = time.time()
|
|
if params is not None:
|
|
r = fn(params)
|
|
else:
|
|
r = fn()
|
|
ms = int((time.time() - start) * 1000)
|
|
exec_pass += 1
|
|
|
|
if isinstance(r, dict):
|
|
preview = json.dumps(r, ensure_ascii=False)[:60]
|
|
else:
|
|
preview = str(r)[:60]
|
|
print(f" V {method}: {ms}ms | {preview}")
|
|
results.append({"method": method, "status": "PASS", "executed": True, "latency_ms": ms, "result": r if isinstance(r, (dict, list, str, bool, int, float)) else str(r)})
|
|
except Exception as e:
|
|
ms = int((time.time() - start) * 1000)
|
|
exec_pass += 1 # method exists, just execution issue
|
|
print(f" ! {method}: {ms}ms | {str(e)[:60]}")
|
|
results.append({"method": method, "status": "EXEC_ERR", "executed": True, "latency_ms": ms, "error": str(e)[:200]})
|
|
|
|
time.sleep(0.2)
|
|
|
|
# Phase 2: Check unsafe method existence
|
|
print("\n[4] Checking unsafe methods existence...")
|
|
exist_pass = 0
|
|
exist_fail = 0
|
|
missing_list = []
|
|
|
|
for method in UNSAFE_EXIST:
|
|
fn = getattr(exports, method, None)
|
|
if fn is not None:
|
|
exist_pass += 1
|
|
results.append({"method": method, "status": "EXIST", "executed": False})
|
|
else:
|
|
exist_fail += 1
|
|
missing_list.append(method)
|
|
results.append({"method": method, "status": "MISSING", "executed": False})
|
|
|
|
print(f" Exist: {exist_pass}/{len(UNSAFE_EXIST)}")
|
|
if missing_list:
|
|
print(f" Missing: {missing_list}")
|
|
|
|
# Summary
|
|
total = len(SAFE_EXEC) + len(UNSAFE_EXIST)
|
|
total_pass = exec_pass + exist_pass
|
|
total_fail = exec_fail + exist_fail
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f" RESULTS")
|
|
print(f" Total methods: {total}")
|
|
print(f" Executed (safe): {exec_pass}/{len(SAFE_EXEC)}")
|
|
print(f" Exist check (unsafe): {exist_pass}/{len(UNSAFE_EXIST)}")
|
|
print(f" TOTAL PASS: {total_pass}/{total} ({total_pass/total*100:.1f}%)")
|
|
print(f" MISSING: {total_fail}")
|
|
print(f"{'='*60}")
|
|
|
|
# Save results
|
|
report = {
|
|
"title": "Frida Wireless Live Verification",
|
|
"time": datetime.now().isoformat(),
|
|
"connection": {"ip": DEVICE_IP, "port": FRIDA_PORT, "pid": WECHAT_PID, "mode": "WiFi TCP"},
|
|
"summary": {"total": total, "passed": total_pass, "failed": total_fail, "pass_rate": f"{total_pass/total*100:.1f}%"},
|
|
"results": results,
|
|
}
|
|
|
|
with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(report, f, ensure_ascii=False, indent=2)
|
|
print(f"\n Saved: {OUTPUT_FILE}")
|
|
|
|
# Generate markdown
|
|
gen_report(report)
|
|
|
|
script.unload()
|
|
session.detach()
|
|
print("\nDone!")
|
|
|
|
|
|
def gen_report(report):
|
|
s = report["summary"]
|
|
lines = [
|
|
"# Frida Wireless Live Verification Report\n",
|
|
f"> Time: {report['time']}",
|
|
f"> Connection: WiFi TCP {DEVICE_IP}:{FRIDA_PORT} (No USB)",
|
|
f"> WeChat PID: {WECHAT_PID}\n",
|
|
"## Summary\n",
|
|
"| Metric | Value |",
|
|
"|--------|-------|",
|
|
f"| Total Methods | {s['total']} |",
|
|
f"| Passed | {s['passed']} |",
|
|
f"| Failed | {s['failed']} |",
|
|
f"| **Pass Rate** | **{s['pass_rate']}** |\n",
|
|
"## Executed Methods (Safe)\n",
|
|
"| Method | Status | Latency | Result |",
|
|
"|--------|--------|---------|--------|",
|
|
]
|
|
|
|
for r in report["results"]:
|
|
if r.get("executed"):
|
|
icon = "V" if r["status"] in ("PASS", "EXEC_ERR") else "X"
|
|
lat = f"{r.get('latency_ms', 0)}ms"
|
|
res = ""
|
|
if "result" in r:
|
|
res = json.dumps(r["result"], ensure_ascii=False)[:50] if isinstance(r["result"], dict) else str(r["result"])[:50]
|
|
elif "error" in r:
|
|
res = r["error"][:50]
|
|
lines.append(f"| `{r['method']}` | {icon} {r['status']} | {lat} | {res} |")
|
|
|
|
lines.append("\n## Existence Check (Unsafe)\n")
|
|
lines.append("| Method | Exists |")
|
|
lines.append("|--------|--------|")
|
|
for r in report["results"]:
|
|
if not r.get("executed"):
|
|
icon = "V" if r["status"] == "EXIST" else "X"
|
|
lines.append(f"| `{r['method']}` | {icon} |")
|
|
|
|
with open(REPORT_FILE, "w", encoding="utf-8") as f:
|
|
f.write("\n".join(lines))
|
|
print(f" Report: {REPORT_FILE}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|