203 lines
7.4 KiB
Python
203 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
工作手机SDK - 截图验证v2
|
||
用ADB截图(可靠),Frida控制微信操作
|
||
"""
|
||
import frida, time, json, os, subprocess
|
||
from datetime import datetime
|
||
|
||
PHONE_IP = "192.168.110.80"
|
||
ADB_SERIAL = "192.168.110.80:5555"
|
||
FRIDA_PORT = 27042
|
||
WECHAT_PID = 16816
|
||
BASE = "/Users/karuo/Documents/开发/2、私域银行/工作手机"
|
||
HOOK_SCRIPT = BASE + "/sdk/agent/hook/wechat_hook_v2.js"
|
||
SCREENSHOT_DIR = BASE + "/开发文档/6、测试/live_verify_20260518/screenshots"
|
||
RESULT_FILE = BASE + "/verification_screenshot_v2_20260518.json"
|
||
|
||
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
|
||
|
||
def adb_screenshot(name, wait=0.5):
|
||
"""用ADB截图并拉取到本地"""
|
||
remote = f"/sdcard/{name}.png"
|
||
local = f"{SCREENSHOT_DIR}/{name}.png"
|
||
time.sleep(wait)
|
||
# 截图
|
||
r1 = subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "screencap", "-p", remote],
|
||
capture_output=True, text=True)
|
||
# 拉取
|
||
r2 = subprocess.run(["adb", "-s", ADB_SERIAL, "pull", remote, local],
|
||
capture_output=True, text=True)
|
||
size = os.path.getsize(local) if os.path.exists(local) else 0
|
||
print(f" [截图] {name}.png ({size} bytes)")
|
||
return local if size > 0 else None
|
||
|
||
print(f"[{datetime.now().strftime('%H:%M:%S')}] 连接Frida: {PHONE_IP}:{FRIDA_PORT}")
|
||
dm = frida.get_device_manager()
|
||
device = dm.add_remote_device(f"{PHONE_IP}:{FRIDA_PORT}")
|
||
session = device.attach(WECHAT_PID)
|
||
print(f"[OK] 附加微信 PID={WECHAT_PID}")
|
||
|
||
with open(HOOK_SCRIPT, encoding="utf-8") as f:
|
||
src = f.read()
|
||
script = session.create_script(src)
|
||
script.on("message", lambda m, d: None)
|
||
script.load()
|
||
print("[OK] Hook已加载,等待3秒...")
|
||
time.sleep(3)
|
||
rpc = script.exports_sync
|
||
|
||
results = {}
|
||
ts = datetime.now().strftime("%H%M%S")
|
||
|
||
# ============================================================
|
||
# 阶段1:真实数据统计
|
||
# ============================================================
|
||
print("\n" + "="*50)
|
||
print("阶段1:真实数据统计")
|
||
print("="*50)
|
||
|
||
r_type3 = rpc.raw_sql({"sql": "SELECT COUNT(*) as cnt FROM rcontact WHERE type=3"})
|
||
type3_count = r_type3.get("rows", [{}])[0].get("cnt", "?")
|
||
|
||
r_groups = rpc.raw_sql({"sql": "SELECT COUNT(*) as cnt FROM chatroom"})
|
||
groups_count = r_groups.get("rows", [{}])[0].get("cnt", "?")
|
||
|
||
r_total = rpc.raw_sql({"sql": "SELECT COUNT(*) as cnt FROM rcontact"})
|
||
total_count = r_total.get("rows", [{}])[0].get("cnt", "?")
|
||
|
||
# 群组名称
|
||
r_named = rpc.raw_sql({"sql": "SELECT chatroomname, chatroomnick FROM chatroom WHERE chatroomnick IS NOT NULL AND chatroomnick != '' LIMIT 10"})
|
||
named_groups = r_named.get("rows", [])
|
||
|
||
# 联系人样本
|
||
r_sample = rpc.raw_sql({"sql": "SELECT username, nickname, conRemark FROM rcontact WHERE type=3 AND username != 'filehelper' LIMIT 10"})
|
||
contact_sample = r_sample.get("rows", [])
|
||
|
||
print(f" rcontact总记录: {total_count}")
|
||
print(f" 真实好友(type=3): {type3_count}")
|
||
print(f" 群组数量: {groups_count}")
|
||
print(f" 有名称的群组: {len(named_groups)}个")
|
||
for g in named_groups[:5]:
|
||
print(f" {g.get('chatroomnick','')} ({g.get('chatroomname','')})")
|
||
print(f" 联系人样本(前5):")
|
||
for c in contact_sample[:5]:
|
||
print(f" {c.get('nickname','')} ({c.get('username','')})")
|
||
|
||
results["data_stats"] = {
|
||
"total_rcontact": total_count,
|
||
"type3_friends": type3_count,
|
||
"groups_count": groups_count,
|
||
"named_groups": named_groups,
|
||
"contact_sample": contact_sample
|
||
}
|
||
|
||
# ============================================================
|
||
# 阶段2:截图验证朋友圈发送
|
||
# ============================================================
|
||
print("\n" + "="*50)
|
||
print("阶段2:朋友圈发送截图验证")
|
||
print("="*50)
|
||
|
||
# 2a. 导航到主界面截图
|
||
print(" 2a. 导航到主界面...")
|
||
rpc.navigate_to_main({})
|
||
adb_screenshot(f"moment_A_main_{ts}", wait=1.5)
|
||
|
||
# 2b. 发送朋友圈
|
||
moment_content = f"[SDK验证{datetime.now().strftime('%m/%d %H:%M')}] 工作手机Frida真机验证-朋友圈功能正常 好友:{type3_count}人 群:{groups_count}个"
|
||
print(f" 2b. 发送朋友圈: {moment_content[:40]}...")
|
||
moment_result = rpc.post_moments({"content": moment_content, "image_urls": []})
|
||
print(f" 发送结果: {json.dumps(moment_result, ensure_ascii=False)}")
|
||
results["moment_send"] = moment_result
|
||
|
||
# 2c. 发送后截图
|
||
adb_screenshot(f"moment_B_after_send_{ts}", wait=2)
|
||
|
||
# 2d. 读取朋友圈验证
|
||
moments = rpc.get_moments({"limit": 10})
|
||
print(f" 朋友圈读取: {moments.get('count', 0)}条")
|
||
results["moments_read"] = moments
|
||
|
||
# ============================================================
|
||
# 阶段3:截图验证消息发送
|
||
# ============================================================
|
||
print("\n" + "="*50)
|
||
print("阶段3:消息发送截图验证")
|
||
print("="*50)
|
||
|
||
# 3a. 导航到文件传输助手
|
||
print(" 3a. 导航到文件传输助手...")
|
||
rpc.navigate_to_chat({"wxid": "filehelper"})
|
||
adb_screenshot(f"msg_A_before_{ts}", wait=1.5)
|
||
|
||
# 3b. 发送消息
|
||
msg1 = f"[SDK验证{datetime.now().strftime('%H:%M:%S')}] 真实数据: 好友{type3_count}人 | 群组{groups_count}个 | 总记录{total_count}条"
|
||
print(f" 3b. 发送消息1: {msg1[:50]}...")
|
||
msg_result1 = rpc.send_message({"to_id": "filehelper", "content": msg1, "msg_type": "text"})
|
||
print(f" 发送结果: {json.dumps(msg_result1, ensure_ascii=False)}")
|
||
results["message_send_1"] = msg_result1
|
||
time.sleep(0.8)
|
||
|
||
# 3c. 发送后截图(消息应出现在聊天界面)
|
||
adb_screenshot(f"msg_B_after_send1_{ts}", wait=1)
|
||
|
||
# 3d. 再发一条确认
|
||
msg2 = f"[SDK验证{datetime.now().strftime('%H:%M:%S')}] 第2条消息 - Frida真机控制正常"
|
||
rpc.send_message({"to_id": "filehelper", "content": msg2, "msg_type": "text"})
|
||
results["message_send_2"] = {"content": msg2}
|
||
adb_screenshot(f"msg_C_after_send2_{ts}", wait=1)
|
||
|
||
# ============================================================
|
||
# 阶段4:群组详情验证
|
||
# ============================================================
|
||
print("\n" + "="*50)
|
||
print("阶段4:群组详情验证")
|
||
print("="*50)
|
||
|
||
groups_full = rpc.get_groups_full({"limit": 100})
|
||
print(f" 群组总数: {groups_full.get('count', 0)} (来源: {groups_full.get('source', '?')})")
|
||
if groups_full.get("groups"):
|
||
for g in groups_full["groups"][:10]:
|
||
print(f" {g.get('group_id','')} | {g.get('name','')} | 成员:{g.get('member_count','?')}")
|
||
results["groups_full"] = groups_full
|
||
|
||
# 获取第一个群的成员
|
||
if groups_full.get("groups"):
|
||
first_group = groups_full["groups"][0]
|
||
group_id = first_group.get("group_id", "")
|
||
if group_id:
|
||
members = rpc.get_group_members({"group_id": group_id})
|
||
print(f" 群 {group_id} 成员数: {members.get('count', 0)}")
|
||
results["first_group_members"] = members
|
||
|
||
# ============================================================
|
||
# 保存结果
|
||
# ============================================================
|
||
report = {
|
||
"title": "工作手机SDK - 截图验证报告v2",
|
||
"time": datetime.now().isoformat(),
|
||
"device": PHONE_IP,
|
||
"real_data": {
|
||
"total_rcontact": total_count,
|
||
"type3_friends": type3_count,
|
||
"groups": groups_count,
|
||
},
|
||
"screenshots": [
|
||
f"moment_A_main_{ts}.png",
|
||
f"moment_B_after_send_{ts}.png",
|
||
f"msg_A_before_{ts}.png",
|
||
f"msg_B_after_send1_{ts}.png",
|
||
f"msg_C_after_send2_{ts}.png",
|
||
],
|
||
"results": results
|
||
}
|
||
with open(RESULT_FILE, "w", encoding="utf-8") as f:
|
||
json.dump(report, f, ensure_ascii=False, indent=2, default=str)
|
||
print(f"\n结果已保存: {RESULT_FILE}")
|
||
|
||
script.unload()
|
||
session.detach()
|
||
print("完成!")
|