feat: 添加精准Hook脚本 20260518

This commit is contained in:
2026-05-18 20:25:25 +08:00
parent ccc26cf0ea
commit 69c5ca4bb6

268
hook_find_send.py Normal file
View File

@@ -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("完成!")