Files
workphone-sdk/hook_find_send.py

269 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 = 9462
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("完成!")