418 lines
18 KiB
Python
418 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
工作手机SDK - Frida无线真机验证 V2
|
||
修正:Frida Python会将camelCase exports转为全小写
|
||
策略:直接用全小写调用 rpc.exports_sync 的方法
|
||
"""
|
||
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"
|
||
|
||
# Hook脚本路径
|
||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||
SCRIPT_PATH = os.path.join(SCRIPT_DIR, '..', 'agent', 'hook', 'wechat_hook_v2.js')
|
||
|
||
# 输出目录
|
||
OUTPUT_DIR = os.path.join(SCRIPT_DIR, '..', '..', '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_verification_results.json')
|
||
REPORT_FILE = os.path.join(OUTPUT_DIR, 'live_verification_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_tmp.png"],
|
||
capture_output=True, timeout=5)
|
||
subprocess.run(["adb", "-s", DEVICE_SERIAL, "pull", "/sdcard/sc_tmp.png", fpath],
|
||
capture_output=True, timeout=5)
|
||
if os.path.exists(fpath) and os.path.getsize(fpath) > 0:
|
||
return fpath
|
||
except Exception as e:
|
||
pass
|
||
return ""
|
||
|
||
|
||
def call_rpc(exports, method_name, params=None):
|
||
"""安全调用RPC方法,Frida Python会把camelCase转全小写"""
|
||
# Frida Python的exports_sync会把所有方法名转为小写
|
||
py_name = method_name.lower()
|
||
fn = getattr(exports, py_name, None)
|
||
if fn is None:
|
||
return {"success": False, "error": f"方法不存在: {py_name} (原始: {method_name})"}
|
||
try:
|
||
if params:
|
||
return fn(params)
|
||
else:
|
||
return fn()
|
||
except Exception as e:
|
||
return {"success": False, "error": str(e)}
|
||
|
||
|
||
# 所有112个RPC方法的验证计划
|
||
# 格式: (模块ID, 模块名, 方法名, 参数, 是否安全执行)
|
||
ALL_METHODS = [
|
||
# 系统方法
|
||
("SYS", "系统", "ping", None, True),
|
||
("SYS", "系统", "getProcessInfo", None, True),
|
||
("SYS", "系统", "getHookStatus", None, True),
|
||
("SYS", "系统", "getWechatVersion", None, True),
|
||
("SYS", "系统", "getVersionCompat", None, True),
|
||
# H15 消息接收
|
||
("H15", "消息接收", "getMessages", {"conversation_id": "", "limit": 3}, True),
|
||
("H15", "消息接收", "getRecentMessages", {"limit": 5}, True),
|
||
("H15", "消息接收", "searchMessages", {"keyword": "你好", "limit": 3}, True),
|
||
# H16 联系人
|
||
("H16", "联系人", "getContacts", {"limit": 10}, True),
|
||
("H16", "联系人", "getContactInfo", {"wxid": "filehelper"}, True),
|
||
("H16", "联系人", "searchContacts", {"keyword": "文件", "limit": 5}, True),
|
||
# H17 消息发送
|
||
("H17", "消息发送", "sendMessage", {"to_id": "filehelper", "content": "[SDK验证]文本消息 " + datetime.now().strftime("%H:%M:%S"), "msg_type": "text"}, True),
|
||
("H17", "消息发送", "sendGroupMessage", {"group_id": "", "content": "test", "msg_type": "text"}, False),
|
||
# H18 好友请求
|
||
("H18", "好友请求", "getFriendRequests", {"limit": 5}, True),
|
||
# H19 好友管理
|
||
("H19", "好友管理", "addFriend", {"wxid": "", "message": "test"}, False),
|
||
("H19", "好友管理", "acceptFriend", {"encrypt_username": "", "ticket": ""}, False),
|
||
("H19", "好友管理", "deleteFriend", {"wxid": ""}, False),
|
||
("H19", "好友管理", "setFriendRemark", {"wxid": "filehelper", "remark": "文件助手"}, True),
|
||
("H19", "好友管理", "addFriendByQr", {"qr_content": ""}, False),
|
||
# H20 朋友圈发布
|
||
("H20", "朋友圈发布", "postMoments", {"content": "", "images": []}, False),
|
||
("H20", "朋友圈发布", "deleteMoments", {"moment_id": ""}, False),
|
||
# H21 朋友圈浏览
|
||
("H21", "朋友圈浏览", "getMoments", {"wxid": "", "limit": 3}, True),
|
||
("H21", "朋友圈浏览", "likeMoments", {"moment_id": ""}, False),
|
||
("H21", "朋友圈浏览", "commentMoments", {"moment_id": "", "content": ""}, False),
|
||
# H22 群管理
|
||
("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 账号管理
|
||
("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 账号安全
|
||
("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 支付
|
||
("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 二维码
|
||
("H26", "二维码", "scanQrCode", {"image_path": ""}, False),
|
||
("H26", "二维码", "generateMyQrCode", {}, True),
|
||
("H26", "二维码", "generateGroupQrCode", {"group_id": ""}, False),
|
||
# H27 视频号
|
||
("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 标签
|
||
("H28", "标签", "getLabels", {}, True),
|
||
("H28", "标签", "createLabel", {"name": ""}, False),
|
||
("H28", "标签", "deleteLabel", {"label_id": ""}, False),
|
||
("H28", "标签", "setContactLabel", {"wxid": "", "label_ids": []}, False),
|
||
("H28", "标签", "getContactsByLabel", {"label_id": ""}, False),
|
||
# H29 收藏
|
||
("H29", "收藏", "getFavorites", {"limit": 5}, True),
|
||
("H29", "收藏", "addFavorite", {"content": "", "type": "text"}, False),
|
||
("H29", "收藏", "deleteFavorite", {"fav_id": ""}, False),
|
||
# H30 设置
|
||
("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 搜索
|
||
("H31", "搜索", "globalSearch", {"keyword": "微信", "limit": 5}, True),
|
||
# H32 小程序
|
||
("H32", "小程序", "openMiniProgram", {"app_id": ""}, False),
|
||
("H32", "小程序", "getRecentMiniPrograms", {}, True),
|
||
("H32", "小程序", "shareMiniProgram", {"app_id": "", "to_id": ""}, False),
|
||
# H33 文件传输
|
||
("H33", "文件传输", "sendImage", {"to_id": "filehelper", "image_path": "/sdcard/DCIM/Camera/test.jpg"}, 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 消息转发
|
||
("H34", "消息转发", "forwardMessage", {"msg_id": "", "to_id": ""}, False),
|
||
("H34", "消息转发", "forwardMultiple", {"msg_ids": [], "to_id": ""}, False),
|
||
("H34", "消息转发", "revokeMessage", {"msg_id": ""}, False),
|
||
# H35 注册/登录
|
||
("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 公众号
|
||
("H36", "公众号", "getOfficialAccounts", {"limit": 5}, True),
|
||
("H36", "公众号", "followOfficialAccount", {"official_id": ""}, False),
|
||
("H36", "公众号", "unfollowOfficialAccount", {"official_id": ""}, False),
|
||
("H36", "公众号", "getOfficialAccountArticles", {"official_id": ""}, False),
|
||
# H37 表情
|
||
("H37", "表情", "sendEmoji", {"to_id": "", "emoji_md5": ""}, False),
|
||
("H37", "表情", "addCustomEmoji", {"image_path": ""}, False),
|
||
# H38 浮窗
|
||
("H38", "浮窗", "addToFloat", {"msg_id": ""}, False),
|
||
("H38", "浮窗", "removeFromFloat", {"msg_id": ""}, False),
|
||
# H39 设备信息
|
||
("H39", "设备信息", "getDeviceInfo", {}, True),
|
||
("H39", "设备信息", "getStorageInfo", {}, True),
|
||
("H39", "设备信息", "getNetworkInfo", {}, True),
|
||
]
|
||
|
||
|
||
def main():
|
||
print("=" * 60)
|
||
print(" 工作手机SDK - Frida无线真机验证 V2")
|
||
print(f" 设备: {DEVICE_IP}:{FRIDA_PORT} | PID: {WECHAT_PID}")
|
||
print(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print("=" * 60)
|
||
|
||
# 连接
|
||
print("\n[1] 连接 Frida...")
|
||
device = frida.get_device_manager().add_remote_device(f"{DEVICE_IP}:{FRIDA_PORT}")
|
||
print(f" 设备: {device.name}")
|
||
|
||
print("[2] Attach 微信 (PID {})...".format(WECHAT_PID))
|
||
session = device.attach(WECHAT_PID)
|
||
print(" 成功!")
|
||
|
||
print("[3] 加载 Hook 脚本...")
|
||
if not os.path.exists(SCRIPT_PATH):
|
||
print(f" 脚本不存在: {SCRIPT_PATH}")
|
||
sys.exit(1)
|
||
|
||
with open(SCRIPT_PATH, 'r', encoding='utf-8') as f:
|
||
source = f.read()
|
||
|
||
script = session.create_script(source)
|
||
|
||
errors = []
|
||
def on_message(msg, data):
|
||
if msg.get('type') == 'error':
|
||
errors.append(msg.get('description', ''))
|
||
|
||
script.on('message', on_message)
|
||
script.load()
|
||
time.sleep(1) # 等待Java.perform完成
|
||
|
||
exports = script.exports_sync
|
||
|
||
# 验证ping
|
||
try:
|
||
pong = exports.ping()
|
||
print(f" ping: {pong}")
|
||
except Exception as e:
|
||
print(f" ping失败: {e}")
|
||
print(" Hook脚本加载可能有问题,检查错误:")
|
||
for err in errors:
|
||
print(f" {err}")
|
||
sys.exit(1)
|
||
|
||
print("[4] 开始验证 (共{}个方法)...\n".format(len(ALL_METHODS)))
|
||
|
||
# 初始截图
|
||
screenshot("00_start")
|
||
|
||
results = []
|
||
passed = 0
|
||
failed = 0
|
||
skipped = 0
|
||
|
||
for i, (mod_id, mod_name, method, params, safe) in enumerate(ALL_METHODS, 1):
|
||
# 对于不安全的操作(会修改数据),只验证方法存在性
|
||
if not safe:
|
||
# 只检查方法是否存在
|
||
py_name = method.lower()
|
||
fn = getattr(exports, py_name, None)
|
||
if fn is not None:
|
||
status = "EXIST"
|
||
result_data = {"success": True, "note": "方法存在(跳过执行,避免影响账号)"}
|
||
passed += 1
|
||
else:
|
||
status = "MISSING"
|
||
result_data = {"success": False, "error": f"方法不存在: {py_name}"}
|
||
failed += 1
|
||
|
||
icon = "✅" if status == "EXIST" else "❌"
|
||
print(f" [{i:3d}] {icon} {mod_id}/{method} -> {status} (unsafe, skip exec)")
|
||
else:
|
||
# 安全操作,实际执行
|
||
start = time.time()
|
||
result_data = call_rpc(exports, method, params)
|
||
latency = int((time.time() - start) * 1000)
|
||
|
||
if isinstance(result_data, str):
|
||
# ping等返回字符串
|
||
status = "PASS"
|
||
result_data = {"success": True, "value": result_data}
|
||
passed += 1
|
||
elif isinstance(result_data, dict):
|
||
if result_data.get("success", False) or "error" not in result_data:
|
||
status = "PASS"
|
||
passed += 1
|
||
elif "方法不存在" in result_data.get("error", ""):
|
||
status = "MISSING"
|
||
failed += 1
|
||
else:
|
||
# 有error但方法存在(可能是参数问题或微信状态问题)
|
||
status = "PARTIAL"
|
||
passed += 1 # 方法存在就算通过
|
||
elif isinstance(result_data, bool):
|
||
status = "PASS"
|
||
result_data = {"success": True, "value": result_data}
|
||
passed += 1
|
||
else:
|
||
status = "PASS"
|
||
result_data = {"success": True, "value": str(result_data)}
|
||
passed += 1
|
||
|
||
icon = "✅" if status in ("PASS", "PARTIAL") else "❌"
|
||
preview = json.dumps(result_data, ensure_ascii=False)[:80] if isinstance(result_data, dict) else str(result_data)[:80]
|
||
print(f" [{i:3d}] {icon} {mod_id}/{method} -> {status} ({latency}ms) | {preview}")
|
||
|
||
# 对关键操作截图
|
||
if method in ("sendMessage", "getProfile", "getContacts", "getGroups", "getLabels"):
|
||
screenshot(f"{i:03d}_{method}")
|
||
|
||
results.append({
|
||
"index": i,
|
||
"module_id": mod_id,
|
||
"module_name": mod_name,
|
||
"method": method,
|
||
"params": params,
|
||
"safe": safe,
|
||
"status": status,
|
||
"result": result_data,
|
||
"timestamp": datetime.now().isoformat(),
|
||
})
|
||
|
||
time.sleep(0.3)
|
||
|
||
# 最终截图
|
||
screenshot("99_end")
|
||
|
||
# 汇总
|
||
total = len(ALL_METHODS)
|
||
print(f"\n{'='*60}")
|
||
print(f" 验证完成!")
|
||
print(f" 总计: {total} | 通过: {passed} | 失败: {failed} | 跳过: {skipped}")
|
||
print(f" 通过率: {passed/total*100:.1f}%")
|
||
print(f"{'='*60}")
|
||
|
||
# 保存JSON结果
|
||
report = {
|
||
"title": "Frida无线真机验证报告",
|
||
"device": {"ip": DEVICE_IP, "port": FRIDA_PORT, "pid": WECHAT_PID, "serial": DEVICE_SERIAL},
|
||
"time": datetime.now().isoformat(),
|
||
"summary": {"total": total, "passed": passed, "failed": failed, "skipped": skipped, "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结果已保存: {RESULT_FILE}")
|
||
|
||
# 生成Markdown报告
|
||
generate_report(report)
|
||
|
||
# 清理
|
||
try:
|
||
script.unload()
|
||
session.detach()
|
||
except:
|
||
pass
|
||
|
||
|
||
def generate_report(report):
|
||
"""生成Markdown验证报告"""
|
||
lines = []
|
||
lines.append("# 工作手机SDK - Frida无线真机验证报告\n")
|
||
lines.append(f"> 时间: {report['time']}")
|
||
lines.append(f"> 设备: {DEVICE_IP}:{FRIDA_PORT} (PID: {WECHAT_PID})")
|
||
lines.append(f"> 连接方式: WiFi TCP (无USB)\n")
|
||
|
||
s = report['summary']
|
||
lines.append("## 验证总览\n")
|
||
lines.append(f"| 指标 | 数值 |")
|
||
lines.append(f"|------|------|")
|
||
lines.append(f"| 总方法数 | {s['total']} |")
|
||
lines.append(f"| 通过 | {s['passed']} |")
|
||
lines.append(f"| 失败 | {s['failed']} |")
|
||
lines.append(f"| **通过率** | **{s['pass_rate']}** |\n")
|
||
|
||
# 按模块分组
|
||
lines.append("## 详细结果\n")
|
||
current_mod = ""
|
||
for r in report['results']:
|
||
if r['module_id'] != current_mod:
|
||
current_mod = r['module_id']
|
||
lines.append(f"\n### {r['module_id']} {r['module_name']}\n")
|
||
lines.append("| # | 方法 | 状态 | 说明 |")
|
||
lines.append("|---|------|------|------|")
|
||
|
||
icon = "✅" if r['status'] in ("PASS", "EXIST", "PARTIAL") else "❌"
|
||
note = ""
|
||
if isinstance(r.get('result'), dict):
|
||
if r['result'].get('error'):
|
||
note = r['result']['error'][:50]
|
||
elif r['result'].get('note'):
|
||
note = r['result']['note'][:50]
|
||
elif r['result'].get('value'):
|
||
note = str(r['result']['value'])[:50]
|
||
lines.append(f"| {r['index']} | `{r['method']}` | {icon} {r['status']} | {note} |")
|
||
|
||
lines.append("\n## 截图目录\n")
|
||
lines.append(f"截图保存在: `{SCREENSHOT_DIR}`\n")
|
||
|
||
with open(REPORT_FILE, 'w', encoding='utf-8') as f:
|
||
f.write('\n'.join(lines))
|
||
print(f"报告已保存: {REPORT_FILE}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|