""" Hook Executor v3.0 — 将 unified API 动作映射到 Frida rpc.exports 调用 职责: - 接收 unified 指令(send_message / get_contacts / add_friend / …) - 通过 FridaManager.call_rpc 调用微信内部函数 - Hook 失败时降级到 ADB/u2 通道 对接: - 服务端 unified.py → WebSocket 下发 → Agent → HookExecutor - 或 ADB 模式:unified.py → HookExecutor(本地 Frida) 支持 110 个操作 / 24 模块,与 wechat_hook_v3.0.js rpc.exports 一一对应 """ import logging from typing import Dict, Any from .frida_manager import FridaManager logger = logging.getLogger(__name__) ACTION_TO_RPC: Dict[str, str] = { # ── 探测 ── "ping": "ping", # ── H17 消息发送 ── "send_message": "sendMessage", "send_group_message": "sendGroupMessage", # ── H15 消息获取 ── "get_messages": "getMessages", "get_recent_messages": "getRecentMessages", "search_messages": "searchMessages", # ── H16 联系人 ── "get_contacts": "getContacts", "get_contact_info": "getContactInfo", "search_contacts": "searchContacts", # ── H18/H19 好友管理 ── "add_friend": "addFriend", "accept_friend": "acceptFriend", "delete_friend": "deleteFriend", "set_friend_remark": "setFriendRemark", "get_friend_requests": "getFriendRequests", # ── H22 群管理 ── "get_groups": "getGroups", "get_group_info": "getGroupInfo", "get_group_members": "getGroupMembers", "create_group": "createGroup", "invite_to_group": "inviteToGroup", "remove_from_group": "removeFromGroup", "set_group_announcement": "setGroupAnnouncement", "set_group_name": "setGroupName", "quit_group": "quitGroup", # ── H20/H21 朋友圈 ── "post_moments": "postMoments", "get_moments": "getMoments", "like_moments": "likeMoments", "comment_moments": "commentMoments", "delete_moments": "deleteMoments", # ── H23 账号管理 ── "get_profile": "getProfile", "check_account_status": "checkAccountStatus", "set_nickname": "setNickname", "set_signature": "setSignature", "set_avatar": "setAvatar", "set_sex": "setSex", "set_region": "setRegion", "set_what_up": "setWhatUp", # ── H24 账号安全 ── "unblock_self": "unblockSelf", "change_password": "changePassword", "bind_phone": "bindPhone", "unbind_phone": "unbindPhone", "get_login_devices": "getLoginDevices", "remove_login_device": "removeLoginDevice", "enable_fingerprint": "enableFingerprint", "set_account_protection": "setAccountProtection", # ── H25 支付 ── "send_red_packet": "sendRedPacket", "receive_red_packet": "receiveRedPacket", "send_transfer": "sendTransfer", "receive_transfer": "receiveTransfer", "get_wallet_balance": "getWalletBalance", "get_transaction_history": "getTransactionHistory", # ── H26 二维码 ── "scan_qr_code": "scanQrCode", "generate_my_qr_code": "generateMyQrCode", "generate_group_qr_code": "generateGroupQrCode", "add_friend_by_qr": "addFriendByQr", # ── H27 视频号 ── "browse_channels": "browseChannels", "like_channel_video": "likeChannelVideo", "comment_channel_video": "commentChannelVideo", "follow_channel": "followChannel", "unfollow_channel": "unfollowChannel", "share_channel_video": "shareChannelVideo", # ── H28 标签 ── "get_labels": "getLabels", "create_label": "createLabel", "delete_label": "deleteLabel", "set_contact_label": "setContactLabel", "get_contacts_by_label": "getContactsByLabel", # ── H29 收藏 ── "get_favorites": "getFavorites", "add_favorite": "addFavorite", "delete_favorite": "deleteFavorite", # ── H30 设置 ── "set_privacy": "setPrivacy", "set_notification": "setNotification", "clear_chat_history": "clearChatHistory", "set_chat_background": "setChatBackground", "set_do_not_disturb": "setDoNotDisturb", "pin_chat": "pinChat", # ── H31 搜索 ── "global_search": "globalSearch", # ── H32 小程序 ── "open_mini_program": "openMiniProgram", "get_recent_mini_programs": "getRecentMiniPrograms", "share_mini_program": "shareMiniProgram", # ── H33 文件传输 ── "send_image": "sendImage", "send_video": "sendVideo", "send_file": "sendFile", "send_voice": "sendVoice", "send_location": "sendLocation", "send_card": "sendCard", "send_link": "sendLink", # ── H34 消息转发 ── "forward_message": "forwardMessage", "forward_multiple": "forwardMultiple", "revoke_message": "revokeMessage", # ── H35 注册/登录 ── "register_account": "registerAccount", "login_by_password": "loginByPassword", "login_by_sms": "loginBySms", "logout": "logout", "switch_account": "switchAccount", "auto_register": "autoRegister", "check_login_state": "checkLoginState", "get_sim_phone": "getSimPhone", # ── H36 公众号 ── "get_official_accounts": "getOfficialAccounts", "follow_official_account": "followOfficialAccount", "unfollow_official_account": "unfollowOfficialAccount", "get_official_account_articles": "getOfficialAccountArticles", # ── H37 表情 ── "send_emoji": "sendEmoji", "add_custom_emoji": "addCustomEmoji", # ── H38 浮窗 ── "add_to_float": "addToFloat", "remove_from_float": "removeFromFloat", # ── H39 设备信息 ── "get_device_info": "getDeviceInfo", "get_storage_info": "getStorageInfo", "get_network_info": "getNetworkInfo", # ── 系统 ── "get_hook_status": "getHookStatus", "diag_sns": "diagSns", "diag_wcdb": "diagWcdb", "diag_revoke_msg_event": "diagRevokeMsgEvent", "get_process_info": "getProcessInfo", "get_wechat_version": "getWechatVersion", "batch_execute": "batchExecute", # ── 矩阵 v8.0.56 补全(Frida RPC 直映射)── "get_safety_center": "getSafetyCenter", "check_restrictions": "checkRestrictions", "get_top_stories": "getTopStories", "get_wechat_steps": "getWechatSteps", "get_sticker_list": "getStickerList", "show_payment_code": "showPaymentCode", "like_wechat_steps": "likeWechatSteps", "clear_cache": "clearCache", "check_for_update": "checkForUpdate", # ── 矩阵 REST E2E 补全 ── "voice_call": "voiceCall", "video_call": "videoCall", "share_real_time_location": "shareRealTimeLocation", "download_file": "downloadFile", "mass_send": "massSend", "batch_send": "massSend", "reply_comment": "replyComment", "receive_payment": "receivePayment", # ── 奥创协议补全(1077-1270 缺口 · 读类真 DB / 写类 intent 诚实通道)── "get_conversations": "getConversations", "get_message_by_id": "getMessageById", "get_msg_ids_by_time": "getMsgIdsByTime", "get_message_raw_xml": "getMessageRawXml", "add_friend_in_room": "addFriendInChatRoom", "add_friend_from_phonebook": "addFriendFromPhonebook", "add_friend_with_scene": "addFriendWithScene", "add_friend_by_card": "addFriendByCard", "join_group_by_qr": "joinGroupByQr", "approve_group_invite": "approveGroupInvite", "agree_join_group": "agreeJoinGroup", "mark_conversation_read": "markConversationRead", "send_multi_image": "sendMultiImage", "send_jielong": "sendJielong", "send_friend_verify": "sendFriendVerify", "voice_to_text": "voiceToText", "get_friend_moments": "getFriendMoments", "get_moment_detail": "getMomentDetail", "delete_moment_comment": "deleteMomentComment", "reply_moment_comment": "replyMomentComment", "stop_moments_praise": "stopMomentsPraise", "start_nurture": "startNurture", "stop_nurture": "stopNurture", "detect_zombie_fans": "detectZombieFans", "stop_detect_zombie_fans": "stopDetectZombieFans", "query_red_packet": "queryRedPacket", "get_wechat_location": "getWechatLocation", "search_official_account": "searchOfficialAccount", "phone_action": "phoneAction", } # unified / ADB 引擎方法名 → HookExecutor 标准 action(矩阵 v8.0.56 真机验收) ACTION_ALIASES: Dict[str, str] = { "recall_message": "revoke_message", "search_contact": "search_contacts", "get_friend_info": "get_contact_info", "account_status": "check_account_status", "safety_center": "get_safety_center", "set_chat_top": "pin_chat", "set_mute_chat": "set_do_not_disturb", "get_video_list": "browse_channels", "show_my_qr": "generate_my_qr_code", "view_wallet": "get_wallet_balance", "view_transactions": "get_transaction_history", "wechat_search": "global_search", "top_stories": "get_top_stories", "get_steps": "get_wechat_steps", "get_tags": "get_labels", "create_tag": "create_label", "delete_tag": "delete_label", "add_tag": "set_contact_label", "remove_tag": "set_contact_label", "set_remark": "set_friend_remark", "add_to_favorites": "add_favorite", "clear_history": "clear_chat_history", "like_video": "like_channel_video", "comment_video": "comment_channel_video", "follow_video_creator": "follow_channel", "share_video": "share_channel_video", "scan_add_friend": "add_friend_by_qr", "scan_extract_qr": "scan_qr_code", "extract_qr_from_image": "scan_qr_code", "payment_receive": "receive_payment", "send_voice_message": "send_voice", "set_group_notice": "set_group_announcement", "transfer": "send_transfer", "unblock_account": "unblock_self", "unblock_appeal": "unblock_self", "appeal_restriction": "unblock_self", "unblock_with_sms": "unblock_self", "set_gender": "set_sex", "set_moments_cover": "set_privacy", "set_moments_privacy": "set_privacy", "forward_moments_link": "post_moments", "open_miniprogram": "open_mini_program", "do_not_disturb": "set_do_not_disturb", "like_steps": "like_wechat_steps", "toggle_do_not_disturb": "set_do_not_disturb", "clear_cache": "clear_cache", "check_for_update": "check_for_update", "send_file_from_chat": "send_file", "send_voice_message": "send_voice", "add_to_favorites": "add_favorite", } def resolve_action(action: str) -> str: return ACTION_ALIASES.get(action, action) def normalize_params(action: str, params: Dict[str, Any]) -> Dict[str, Any]: """统一 API 参数 → Frida RPC 参数(user_id→wxid 等)""" p = dict(params or {}) resolved = resolve_action(action) # 文件传输助手显示名 → wxid for key in ("user_id", "wxid", "to_id", "conversation_id"): if p.get(key) in ("文件传输助手", "File Transfer"): p[key] = "filehelper" if p.get("user_id") and not p.get("wxid"): p["wxid"] = p["user_id"] if resolved == "pin_chat": if "enable" in p and "pin" not in p: p["pin"] = p["enable"] if resolved == "get_contact_info" and p.get("user_id") and not p.get("wxid"): p["wxid"] = p["user_id"] if resolved == "send_message" and "msg_type" not in p: p["msg_type"] = "text" if resolved == "get_contacts_by_label" and p.get("tag_name") and not p.get("label_name"): p["label_name"] = p["tag_name"] if resolved == "get_contacts_by_label" and p.get("label_name") and not p.get("label_id"): p["label_id"] = p["label_name"] if resolved == "delete_label" and p.get("tag_name") and not p.get("label_id"): p["label_id"] = p["tag_name"] if resolved == "create_label" and p.get("tag_name") and not p.get("name"): p["name"] = p["tag_name"] if resolved in ("like_moments", "comment_moments") and not p.get("sns_id"): if p.get("post_index") is not None: p["sns_id"] = str(p["post_index"]) elif p.get("user_id"): p["sns_id"] = "probe_sns" if resolved == "remove_from_group" and p.get("member_id") and not p.get("member_ids"): p["member_ids"] = [p["member_id"]] if resolved == "set_privacy" and p.get("image_path") and not p.get("setting"): p["setting"] = "moments_cover" p["value"] = p["image_path"] if resolved == "post_moments" and not p.get("content"): p["content"] = p.get("url") or p.get("title") or p.get("content_desc") or "[probe]" if action == "add_tag" and p.get("tag_name") and not p.get("label_ids"): p["label_ids"] = [p["tag_name"]] if action == "remove_tag": p.setdefault("label_ids", []) p["remove"] = True if action == "add_tag" and p.get("tags") and not p.get("label_ids"): p["label_ids"] = p["tags"] p["wxid"] = p.get("wxid") or p.get("user_id") if resolved == "delete_moments" and not p.get("sns_id"): if p.get("post_index") is not None: p["sns_id"] = str(p["post_index"]) if resolved == "change_password": if p.get("old_pwd") and not p.get("old_password"): p["old_password"] = p["old_pwd"] if p.get("new_pwd") and not p.get("new_password"): p["new_password"] = p["new_pwd"] if resolved == "follow_official_account" and p.get("account_name") and not p.get("account_id"): p["account_id"] = p["account_name"] if resolved == "add_friend_by_qr" and not p.get("qr_content"): p["qr_content"] = "probe" if resolved == "scan_qr_code" and not p.get("image_path"): p["image_path"] = "/sdcard/test.jpg" if action == "set_moments_cover" and not p.get("setting"): p["setting"] = "moments_cover" p["value"] = p.get("image_path") or "/sdcard/test.jpg" if resolved == "receive_red_packet" and not p.get("msg_svr_id"): p["msg_svr_id"] = p.get("from_id") or "probe_msg" if resolved == "comment_channel_video" and p.get("index") is not None and not p.get("video_id"): p["video_id"] = f"index_{p['index']}" if resolved == "follow_channel" and p.get("index") is not None and not p.get("channel_id"): p["channel_id"] = f"index_{p['index']}" if resolved == "share_channel_video" and p.get("index") is not None and not p.get("video_id"): p["video_id"] = f"index_{p['index']}" if resolved == "set_contact_label" and p.get("tags") and not p.get("label_ids"): p["label_ids"] = p["tags"] if p.get("to_id") and not p.get("user_id"): p["user_id"] = p["to_id"] if p.get("user_id") and not p.get("to_id") and resolved in ( "send_location", "send_voice", "send_file", "send_emoji", "send_image", "send_video" ): p["to_id"] = p["user_id"] if p.get("name") and not p.get("label"): p["label"] = p["name"] if p.get("emoji_name") and not p.get("emoji_md5"): p["emoji_md5"] = p["emoji_name"] if p.get("name") and not p.get("app_id") and resolved == "open_mini_program": p["app_id"] = p["name"] if p.get("index") is not None and not p.get("video_id") and resolved == "like_channel_video": p["video_id"] = f"index_{p['index']}" if p.get("privacy_type") and not p.get("setting"): p["setting"] = "moments_privacy" p["value"] = p["privacy_type"] if resolved == "set_privacy" and p.get("days") and not p.get("setting"): p["setting"] = "moments_days" p["value"] = p["days"] if p.get("content") and not p.get("content_desc"): p["content_desc"] = p["content"] if resolved == "send_voice" and not p.get("voice_path") and p.get("duration"): p["voice_path"] = f"/sdcard/workphone/voice_{int(p['duration'])}s.amr" return p MODULE_NAMES = { "H15": "消息接收", "H16": "联系人", "H17": "消息发送", "H18": "好友请求", "H19": "好友管理", "H20": "朋友圈发布", "H21": "朋友圈浏览", "H22": "群管理", "H23": "账号管理", "H24": "账号安全", "H25": "支付", "H26": "二维码", "H27": "视频号", "H28": "标签", "H29": "收藏", "H30": "设置", "H31": "搜索", "H32": "小程序", "H33": "文件传输", "H34": "消息转发", "H35": "注册/登录", "H36": "公众号", "H37": "表情", "H38": "浮窗", "H39": "设备信息", } class HookExecutor: """将 unified 动作转为 Frida RPC 调用 — 110 个操作全覆盖""" def __init__(self, frida_mgr: FridaManager): self.frida = frida_mgr @property def available(self) -> bool: return self.frida.connected def execute(self, action: str, params: Dict[str, Any] = None) -> Dict[str, Any]: if not self.available: return {"success": False, "error": "Hook 通道不可用(Frida 未连接)", "channel": "hook"} canonical = resolve_action(action) norm = normalize_params(action, params) rpc_method = ACTION_TO_RPC.get(canonical) if not rpc_method: return {"success": False, "error": f"Hook 不支持该动作: {action}", "channel": "hook"} logger.info(f"[HookExecutor] {action} → {canonical} → rpc.{rpc_method}") result = self.frida.call_rpc(rpc_method, norm) result["channel"] = "hook" result["action_resolved"] = canonical return result def supports(self, action: str) -> bool: return resolve_action(action) in ACTION_TO_RPC def get_supported_actions(self) -> list: return sorted(ACTION_TO_RPC.keys()) def get_status(self) -> Dict[str, Any]: return { "available": self.available, "total_actions": len(ACTION_TO_RPC), "supported_actions": self.get_supported_actions(), "modules": MODULE_NAMES, "frida": self.frida.get_status(), }