diff --git a/hook_find_send.py b/hook_find_send.py new file mode 100644 index 0000000000..933d5900d8 --- /dev/null +++ b/hook_find_send.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +精准Hook方式找到微信8.0.56的真实消息发送类 +通过监听实际发送行为来捕获正确的类名和方法 +""" +import frida, time, json +from datetime import datetime + +PHONE_IP = "192.168.110.80" +FRIDA_PORT = 27042 +WECHAT_PID = 16816 +BASE = "/Users/karuo/Documents/开发/2、私域银行/工作手机" +RESULT_FILE = BASE + "/hook_find_send_20260518.json" + +# 这个脚本通过Hook SQLite的insert操作来捕获消息发送 +# 当微信发送消息时,会往message表insert一条记录 +HOOK_SCRIPT = """ +'use strict'; + +var _foundClasses = []; +var _messages = []; + +// 方法1: Hook SQLite insert,捕获消息写入 +function hookSQLiteInsert() { + try { + var SQLiteDatabase = Java.use('net.sqlcipher.database.SQLiteDatabase'); + SQLiteDatabase.insert.overload('java.lang.String', 'java.lang.String', 'android.content.ContentValues').implementation = function(table, nullColumnHack, values) { + if (table === 'message') { + var content = values.getAsString('content'); + var talker = values.getAsString('talker'); + var isSend = values.getAsInteger('isSend'); + if (isSend && parseInt(isSend) === 1) { + _messages.push({ + table: table, + talker: talker, + content: content ? content.substring(0, 100) : '', + timestamp: Date.now() + }); + send({ type: 'message_insert', talker: talker, content: content ? content.substring(0, 50) : '' }); + } + } + return this.insert(table, nullColumnHack, values); + }; + send({ type: 'hook_status', msg: 'SQLite insert hook OK' }); + } catch(e) { + send({ type: 'hook_status', msg: 'SQLite insert hook failed: ' + e }); + } +} + +// 方法2: 检查候选类是否存在(不扫描全量) +function checkCandidateClasses() { + var candidates = [ + "com.tencent.mm.modelmulti.h", + "com.tencent.mm.modelmulti.g", + "com.tencent.mm.modelmulti.f", + "com.tencent.mm.modelmulti.e", + "com.tencent.mm.modelmulti.d", + "com.tencent.mm.modelmulti.c", + "com.tencent.mm.modelmulti.b", + "com.tencent.mm.modelmulti.a", + "com.tencent.mm.plugin.messenger.foundation.a.k", + "com.tencent.mm.plugin.messenger.foundation.a.j", + "com.tencent.mm.plugin.messenger.foundation.a.i", + "com.tencent.mm.plugin.messenger.foundation.a.h", + "com.tencent.mm.plugin.messenger.foundation.a.g", + "com.tencent.mm.plugin.messenger.foundation.a.f", + "com.tencent.mm.plugin.messenger.foundation.a.e", + "com.tencent.mm.plugin.messenger.foundation.a.d", + "com.tencent.mm.plugin.messenger.foundation.a.c", + "com.tencent.mm.plugin.messenger.foundation.a.b", + "com.tencent.mm.plugin.messenger.foundation.a.a", + ]; + var results = {}; + for (var i = 0; i < candidates.length; i++) { + try { + var cls = Java.use(candidates[i]); + results[candidates[i]] = 'exists'; + // 获取方法列表 + var methods = cls.class.getDeclaredMethods(); + var methodList = []; + for (var j = 0; j < methods.length; j++) { + var m = methods[j]; + var params = m.getParameterTypes().map(function(p) { return p.getName(); }); + methodList.push(m.getName() + '(' + params.join(',') + ')'); + } + results[candidates[i] + '_methods'] = methodList; + } catch(e) { + results[candidates[i]] = 'not_found'; + } + } + return results; +} + +// 方法3: 通过AccessibilityService方式发送(UIAutomator) +function sendViaADB(toId, content) { + try { + // 用am start打开聊天界面 + var Runtime = Java.use('java.lang.Runtime'); + var cmd = 'am start -n com.tencent.mm/.ui.LauncherUI --es username ' + toId; + var proc = Runtime.getRuntime().exec(['sh', '-c', cmd]); + proc.waitFor(); + return { success: true, method: 'am_start', cmd: cmd }; + } catch(e) { + return { success: false, error: String(e) }; + } +} + +// 方法4: 通过ClipboardManager+UIAutomator发送 +function sendViaClipboard(toId, content) { + try { + return Java.performNow(function() { + var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); + var ClipboardManager = Java.use('android.content.ClipboardManager'); + var ClipData = Java.use('android.content.ClipData'); + var cm = Java.cast(ctx.getSystemService('clipboard'), ClipboardManager); + var clip = ClipData.newPlainText('msg', content); + cm.setPrimaryClip(clip); + return { success: true, method: 'clipboard', content: content }; + }); + } catch(e) { + return { success: false, error: String(e) }; + } +} + +Java.perform(function() { + hookSQLiteInsert(); +}); + +rpc.exports = { + checkCandidates: function() { + return Java.performNow(function() { + return checkCandidateClasses(); + }); + }, + getMessages: function() { + return { messages: _messages, count: _messages.length }; + }, + sendViaClipboard: function(params) { + return sendViaClipboard(params.to_id, params.content); + }, + // 尝试通过微信的ShareToTimeline方式发朋友圈 + testSnsPost: function(content) { + try { + return Java.performNow(function() { + var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); + var Intent = Java.use('android.content.Intent'); + + // 方式1: 通过SnsUploadUI + var intent1 = Intent.$new(); + intent1.setClassName('com.tencent.mm', 'com.tencent.mm.plugin.sns.ui.SnsUploadUI'); + intent1.putExtra('Ksnsupload_type', 1); + intent1.putExtra('Ksnsupload_content', content); + intent1.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK + ctx.startActivity(intent1); + return { success: true, method: 'SnsUploadUI_intent', content: content }; + }); + } catch(e) { + return { success: false, error: String(e) }; + } + }, + // 直接写入message表(绕过发送逻辑) + insertMessage: function(params) { + try { + return Java.performNow(function() { + var toId = params.to_id; + var content = params.content; + + // 找DB路径 + var File = Java.use('java.io.File'); + var base = File.$new('/data/data/com.tencent.mm/MicroMsg/'); + var dirs = base.listFiles(); + var dbPath = null; + if (dirs) { + for (var i = 0; i < dirs.length; i++) { + if (dirs[i].isDirectory() && dirs[i].getName().length === 32) { + var candidate = dirs[i].getAbsolutePath() + '/EnMicroMsg.db'; + if (File.$new(candidate).exists()) { dbPath = candidate; break; } + } + } + } + if (!dbPath) return { success: false, error: 'DB not found' }; + + var SQLiteDatabase = Java.use('net.sqlcipher.database.SQLiteDatabase'); + var db = SQLiteDatabase.openDatabase(dbPath, '00000000000000000000000000000000', null, 0); + + var ContentValues = Java.use('android.content.ContentValues'); + var cv = ContentValues.$new(); + cv.put('msgId', Java.use('java.lang.Long').$new(Date.now())); + cv.put('type', Java.use('java.lang.Integer').$new(1)); + cv.put('isSend', Java.use('java.lang.Integer').$new(1)); + cv.put('talker', toId); + cv.put('content', content); + cv.put('createTime', Java.use('java.lang.Long').$new(Date.now())); + cv.put('status', Java.use('java.lang.Integer').$new(2)); + + var rowId = db.insert('message', null, cv); + db.close(); + + return { success: rowId > 0, row_id: rowId.toString(), method: 'direct_db_insert' }; + }); + } catch(e) { + return { success: false, error: String(e) }; + } + } +}; +""" + +print(f"[{datetime.now().strftime('%H:%M:%S')}] 连接Frida...") +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}") + +messages_received = [] +def on_message(m, d): + if m.get("type") == "send": + payload = m.get("payload", {}) + print(f" [EVENT] {payload}") + messages_received.append(payload) + +script = session.create_script(HOOK_SCRIPT) +script.on("message", on_message) +script.load() +print("[OK] Hook脚本已加载,等待2秒...") +time.sleep(2) +rpc = script.exports_sync + +results = {} + +# 1. 检查候选类 +print("\n=== 检查候选类 ===") +candidates_result = rpc.check_candidates() +for cls, status in candidates_result.items(): + if not cls.endswith('_methods'): + print(f" {status.upper()} {cls}") + if status == 'exists' and (cls + '_methods') in candidates_result: + methods = candidates_result[cls + '_methods'] + print(f" 方法: {methods[:5]}") +results["candidates"] = candidates_result + +# 2. 测试直接DB插入 +print("\n=== 测试直接DB插入消息 ===") +insert_result = rpc.insert_message({"to_id": "filehelper", "content": f"[DB直接插入测试{datetime.now().strftime('%H:%M:%S')}]"}) +print(f" 结果: {json.dumps(insert_result, ensure_ascii=False)}") +results["db_insert"] = insert_result +time.sleep(1) + +# 3. 截图验证DB插入是否在界面显示 +import subprocess +subprocess.run(["adb", "-s", "192.168.110.80:5555", "shell", "screencap", "-p", "/sdcard/db_insert_test.png"]) +subprocess.run(["adb", "-s", "192.168.110.80:5555", "pull", "/sdcard/db_insert_test.png", + BASE + "/开发文档/6、测试/live_verify_20260518/screenshots/db_insert_test.png"]) +print(" 截图已保存") + +# 4. 检查消息是否在DB中 +print("\n=== 验证DB中的消息 ===") +# 用主Hook脚本查询(需要重新attach) +print(" (通过rawSql查询最新消息)") + +# 5. 保存结果 +with open(RESULT_FILE, "w", encoding="utf-8") as f: + json.dump(results, f, ensure_ascii=False, indent=2, default=str) +print(f"\n结果已保存: {RESULT_FILE}") + +script.unload() +session.detach() +print("完成!")