470 lines
21 KiB
Python
470 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
v6 真实操作脚本 v2 - 修复列名问题
|
||
先查真实列名,再正确写入
|
||
"""
|
||
import frida, time, json, subprocess
|
||
from datetime import datetime
|
||
|
||
PHONE_IP = "192.168.110.80"
|
||
FRIDA_PORT = 27042
|
||
ADB_SERIAL = "192.168.110.80:5555"
|
||
WECHAT_PID = 1452
|
||
BASE = "/Users/karuo/Documents/开发/2、私域银行/工作手机"
|
||
|
||
dm = frida.get_device_manager()
|
||
device = dm.add_remote_device(f"{PHONE_IP}:{FRIDA_PORT}")
|
||
session = device.attach(WECHAT_PID)
|
||
|
||
events = []
|
||
def on_msg(m, d):
|
||
if m.get("type") == "send":
|
||
p = m["payload"]
|
||
events.append(p)
|
||
t = p.get("type", "")
|
||
if t not in ("log",):
|
||
print(f" [EVENT:{t}] {json.dumps(p, ensure_ascii=False)[:100]}")
|
||
|
||
JS = r"""
|
||
'use strict';
|
||
Java.perform(function() {
|
||
function getDb(dbName) {
|
||
var found = null;
|
||
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
|
||
onMatch: function(instance) {
|
||
try {
|
||
var p = instance.getPath();
|
||
if (p && p.indexOf(dbName) !== -1) found = instance;
|
||
} catch(e) {}
|
||
},
|
||
onComplete: function() {}
|
||
});
|
||
return found;
|
||
}
|
||
|
||
function query(db, sql) {
|
||
try {
|
||
var cursor = db.rawQuery(sql, null);
|
||
var rows = [];
|
||
if (cursor && cursor.moveToFirst()) {
|
||
var cols = cursor.getColumnCount();
|
||
do {
|
||
var row = {};
|
||
for (var i = 0; i < cols; i++) {
|
||
try { row[cursor.getColumnName(i)] = cursor.getString(i); } catch(e) {}
|
||
}
|
||
rows.push(row);
|
||
} while (cursor.moveToNext() && rows.length < 20);
|
||
cursor.close();
|
||
}
|
||
return rows;
|
||
} catch(e) { return []; }
|
||
}
|
||
|
||
rpc.exports = {
|
||
// 查message表真实列名
|
||
getMsgTableSchema: function() {
|
||
var db = getDb('EnMicroMsg.db');
|
||
if (!db) return { success: false, error: 'db not found' };
|
||
var cols = query(db, 'PRAGMA table_info(message)');
|
||
var sample = query(db, 'SELECT * FROM message ORDER BY rowid DESC LIMIT 1');
|
||
return { success: true, columns: cols.map(function(c){return c.name;}), sample: sample };
|
||
},
|
||
|
||
// 查StarSnsInfo表真实列名
|
||
getStarSnsSchema: function() {
|
||
var db = getDb('sns_star_info.db');
|
||
if (!db) return { success: false, error: 'db not found' };
|
||
var cols = query(db, 'PRAGMA table_info(StarSnsInfo)');
|
||
var sample = query(db, 'SELECT * FROM StarSnsInfo ORDER BY rowid DESC LIMIT 2');
|
||
return { success: true, columns: cols, sample: sample };
|
||
},
|
||
|
||
// 操作1:发送消息(动态获取列名后写入)
|
||
sendMessageFixed: function(params) {
|
||
var toId = params.to_id || 'filehelper';
|
||
var content = params.content || 'test';
|
||
var db = getDb('EnMicroMsg.db');
|
||
if (!db) return { success: false, error: 'db not found' };
|
||
|
||
try {
|
||
// 查真实列名
|
||
var cols = query(db, 'PRAGMA table_info(message)');
|
||
var colNames = cols.map(function(c) { return c.name; });
|
||
|
||
var ts = Math.floor(Date.now() / 1000);
|
||
var msgId = String(Date.now());
|
||
|
||
// 构造最小必要字段的INSERT
|
||
var requiredCols = ['msgSvrId','type','status','isSend','isShowTimer','createTime','talker','content'];
|
||
var validCols = requiredCols.filter(function(c) { return colNames.indexOf(c) !== -1; });
|
||
|
||
var colStr = validCols.join(',');
|
||
var valMap = {
|
||
'msgSvrId': msgId,
|
||
'type': '1',
|
||
'status': '3',
|
||
'isSend': '1',
|
||
'isShowTimer': '0',
|
||
'createTime': String(ts),
|
||
'talker': '"' + toId + '"',
|
||
'content': '"' + content.replace(/"/g, '\\"') + '"'
|
||
};
|
||
var valStr = validCols.map(function(c) { return valMap[c]; }).join(',');
|
||
|
||
var sql = 'INSERT INTO message (' + colStr + ') VALUES (' + valStr + ')';
|
||
db.execSQL(sql);
|
||
|
||
// 验证
|
||
var verify = query(db, 'SELECT msgSvrId,content,createTime,talker FROM message WHERE msgSvrId=' + msgId);
|
||
send({ type: 'message_sent_v2', to_id: toId, content: content, msg_id: msgId, verified: verify.length > 0 });
|
||
return { success: true, method: 'wcdb_insert_v2', message_id: msgId, to_id: toId, content: content, verified: verify.length > 0, sql_used: sql.substring(0, 100) };
|
||
} catch(e) {
|
||
return { success: false, error: e.toString() };
|
||
}
|
||
},
|
||
|
||
// 操作2:发布朋友圈(根据真实列名写入StarSnsInfo)
|
||
postMomentFixed: function(params) {
|
||
var content = params.content || '测试朋友圈';
|
||
var myWxid = params.my_wxid || 'wxid_5g37snchpv8e22';
|
||
var db = getDb('sns_star_info.db');
|
||
if (!db) return { success: false, error: 'sns_star_info.db not found' };
|
||
|
||
try {
|
||
// 查真实列名
|
||
var cols = query(db, 'PRAGMA table_info(StarSnsInfo)');
|
||
var colNames = cols.map(function(c) { return c.name; });
|
||
|
||
var ts = Math.floor(Date.now() / 1000);
|
||
// snsId格式:负数长整型(参考现有数据)
|
||
var snsId = String(-(Math.floor(Math.random() * 9000000000000000000) + 1000000000000000000));
|
||
|
||
// 根据真实列名构造INSERT
|
||
// 已知列:snsId, userName, localFlag, createTime, head, localPrivate, type, sourceType, likeFlag, pravited, stringSeq, withTa, withTaHasOther, postBuf, subType, serverExtFlag
|
||
var insertData = {
|
||
'snsId': '"' + snsId + '"',
|
||
'userName': '"' + myWxid + '"',
|
||
'localFlag': '1', // 1=本人发布
|
||
'createTime': String(ts),
|
||
'head': '"' + new Date().toISOString().slice(0,10).replace(/-/g,'') + '"',
|
||
'localPrivate': '0',
|
||
'type': '1', // 1=纯文字
|
||
'sourceType': '1',
|
||
'likeFlag': '0',
|
||
'pravited': '0',
|
||
'stringSeq': '"0000000000000000000000000"',
|
||
'withTa': 'null',
|
||
'withTaHasOther': '"0"',
|
||
'postBuf': 'null',
|
||
'subType': '"0"',
|
||
'serverExtFlag': '"0"'
|
||
};
|
||
|
||
var validCols = colNames.filter(function(c) { return insertData[c] !== undefined; });
|
||
var colStr = validCols.join(',');
|
||
var valStr = validCols.map(function(c) { return insertData[c]; }).join(',');
|
||
|
||
var sql = 'INSERT OR REPLACE INTO StarSnsInfo (' + colStr + ') VALUES (' + valStr + ')';
|
||
db.execSQL(sql);
|
||
|
||
// 验证
|
||
var verify = query(db, 'SELECT * FROM StarSnsInfo WHERE snsId="' + snsId + '"');
|
||
|
||
// 同时在EnMicroMsg.db的SnsInfo表尝试写入(如果存在)
|
||
var mainDb = getDb('EnMicroMsg.db');
|
||
var mainSnsResult = null;
|
||
if (mainDb) {
|
||
var mainSnsCols = query(mainDb, 'PRAGMA table_info(SnsInfo)');
|
||
if (mainSnsCols.length > 0) {
|
||
mainSnsResult = { columns: mainSnsCols.map(function(c){return c.name;}) };
|
||
}
|
||
}
|
||
|
||
send({ type: 'moment_posted_v2', sns_id: snsId, content: content, verified: verify.length > 0 });
|
||
return {
|
||
success: true,
|
||
method: 'star_sns_insert_v2',
|
||
sns_id: snsId,
|
||
content: content,
|
||
columns_used: validCols,
|
||
verified: verify.length > 0,
|
||
verified_data: verify,
|
||
main_sns_info: mainSnsResult,
|
||
note: 'StarSnsInfo是朋友圈缓存表(存储已同步的朋友圈),写入后需要触发网络同步才能真正发布'
|
||
};
|
||
} catch(e) {
|
||
return { success: false, error: e.toString() };
|
||
}
|
||
},
|
||
|
||
// 操作3:添加好友(搜索外部联系人)
|
||
addFriendExternal: function(params) {
|
||
var targetWxid = params.wxid || 'gh_test123'; // 用公众号测试
|
||
var msg = params.msg || '你好,我是游条姐,希望加你为好友';
|
||
|
||
try {
|
||
var mainDb = getDb('EnMicroMsg.db');
|
||
|
||
// 搜索联系人
|
||
var existing = mainDb ? query(mainDb, 'SELECT username,nickname,type,verifyFlag FROM rcontact WHERE username="' + targetWxid + '"') : [];
|
||
|
||
// 查找一个type=3的真实好友用于测试
|
||
var realFriend = mainDb ? query(mainDb, 'SELECT username,nickname,type FROM rcontact WHERE type=3 AND username NOT LIKE "%@chatroom" LIMIT 1') : [];
|
||
|
||
// 通过ContentValues写入addContactScene(记录添加好友操作)
|
||
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
var ContentValues = Java.use('android.content.ContentValues');
|
||
var cv = ContentValues.$new();
|
||
cv.put('username', targetWxid);
|
||
cv.put('addMsg', msg);
|
||
cv.put('scene', Java.use('java.lang.Integer').valueOf(3));
|
||
|
||
send({ type: 'add_friend_v2', target: targetWxid, msg: msg });
|
||
|
||
return {
|
||
success: true,
|
||
method: 'add_friend_request',
|
||
target_wxid: targetWxid,
|
||
add_message: msg,
|
||
already_exists: existing.length > 0,
|
||
existing_info: existing,
|
||
sample_real_friend: realFriend,
|
||
note: '添加好友请求已构造。真正发送需要调用微信网络层(已混淆),或通过AccessibilityService触发。建议用真实wxid测试。'
|
||
};
|
||
} catch(e) {
|
||
return { success: false, error: e.toString() };
|
||
}
|
||
},
|
||
|
||
// 操作4:付款(查WalletCoreDB + 构造转账记录)
|
||
initiatePaymentFixed: function(params) {
|
||
var toId = params.to_id || 'filehelper';
|
||
var amount = params.amount || '0.01';
|
||
var note = params.note || 'SDK测试转账';
|
||
|
||
try {
|
||
var walletDb = getDb('WalletCoreDB-2.db');
|
||
|
||
// 查钱包用户信息
|
||
var userInfo = walletDb ? query(walletDb, 'SELECT * FROM WalletUserInfo LIMIT 1') : [];
|
||
|
||
// 查钱包账单表结构
|
||
var ledgerCols = walletDb ? query(walletDb, 'PRAGMA table_info(WalletLedgerRecord)') : [];
|
||
var ledgerColNames = ledgerCols.map(function(c){return c.name;});
|
||
|
||
// 查现有账单记录
|
||
var ledgerSample = walletDb ? query(walletDb, 'SELECT * FROM WalletLedgerRecord LIMIT 3') : [];
|
||
|
||
// 查WalletPayUI的所有方法(找转账相关)
|
||
var payMethods = [];
|
||
try {
|
||
var PayUI = Java.use('com.tencent.mm.plugin.wallet.pay.ui.WalletPayUI');
|
||
var methods = PayUI.class.getDeclaredMethods();
|
||
for (var i = 0; i < methods.length; i++) {
|
||
var m = methods[i];
|
||
payMethods.push({ name: m.getName(), params: m.getParameterCount() });
|
||
}
|
||
} catch(e) {}
|
||
|
||
// 查TransferHelper
|
||
var transferMethods = [];
|
||
try {
|
||
var TransferHelper = Java.use('com.tencent.mm.plugin.wallet.transfer.TransferHelper');
|
||
var tmethods = TransferHelper.class.getDeclaredMethods();
|
||
for (var j = 0; j < tmethods.length; j++) {
|
||
transferMethods.push(tmethods[j].toString().substring(0, 100));
|
||
}
|
||
} catch(e) {}
|
||
|
||
send({ type: 'payment_v2', to_id: toId, amount: amount });
|
||
|
||
return {
|
||
success: true,
|
||
method: 'wallet_analysis',
|
||
to_id: toId,
|
||
amount: amount,
|
||
note: note,
|
||
wallet_user: userInfo.length > 0 ? userInfo[0] : null,
|
||
ledger_columns: ledgerColNames,
|
||
ledger_sample: ledgerSample,
|
||
pay_ui_methods: payMethods,
|
||
transfer_methods: transferMethods,
|
||
security_note: '微信支付需要支付密码/指纹验证,Frida无法绕过TEE安全区。可以读取账单记录,但无法在后台完成付款。这是微信的安全设计,保护用户资金安全。'
|
||
};
|
||
} catch(e) {
|
||
return { success: false, error: e.toString() };
|
||
}
|
||
},
|
||
|
||
// 验证消息是否写入
|
||
verifyMessage: function(params) {
|
||
var db = getDb('EnMicroMsg.db');
|
||
if (!db) return { success: false };
|
||
var rows = query(db, 'SELECT msgSvrId,content,createTime,talker FROM message WHERE talker="' + (params.talker || 'filehelper') + '" ORDER BY createTime DESC LIMIT 5');
|
||
return { success: true, messages: rows };
|
||
},
|
||
|
||
// 验证朋友圈写入
|
||
verifyMoment: function() {
|
||
var db = getDb('sns_star_info.db');
|
||
if (!db) return { success: false };
|
||
var rows = query(db, 'SELECT snsId,userName,createTime,type,localFlag FROM StarSnsInfo ORDER BY rowid DESC LIMIT 5');
|
||
return { success: true, moments: rows };
|
||
}
|
||
};
|
||
|
||
send({ type: 'ready', msg: 'v6 fixed actions loaded' });
|
||
});
|
||
"""
|
||
|
||
script = session.create_script(JS)
|
||
script.on("message", on_msg)
|
||
script.load()
|
||
time.sleep(3)
|
||
rpc = script.exports_sync
|
||
|
||
print("\n" + "="*60)
|
||
print("v6 真实操作验证 v2(修复列名)")
|
||
print("="*60)
|
||
|
||
results = {}
|
||
|
||
# ===== 查真实列名 =====
|
||
print("\n【message表真实列名】")
|
||
r_msg_schema = rpc.get_msg_table_schema()
|
||
print(f" 列数: {len(r_msg_schema.get('columns', []))}")
|
||
print(f" 列名: {r_msg_schema.get('columns', [])}")
|
||
|
||
print("\n【StarSnsInfo表真实列名】")
|
||
r_sns_schema = rpc.get_star_sns_schema()
|
||
print(f" 列数: {len(r_sns_schema.get('columns', []))}")
|
||
print(f" 列名: {[c['name'] for c in r_sns_schema.get('columns', [])]}")
|
||
|
||
# ===== 操作1:发送消息 =====
|
||
print("\n" + "="*40)
|
||
print("操作1:发送消息(纯后端WCDB写入)")
|
||
print("="*40)
|
||
msg_content = f"工作手机SDK v6 真实发送 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
||
r_msg = rpc.send_message_fixed({"to_id": "filehelper", "content": msg_content})
|
||
print(f" 成功: {r_msg.get('success')}")
|
||
print(f" 消息ID: {r_msg.get('message_id')}")
|
||
print(f" 验证: {r_msg.get('verified')}")
|
||
print(f" 内容: {r_msg.get('content', '')}")
|
||
if not r_msg.get('success'):
|
||
print(f" 错误: {r_msg.get('error', '')}")
|
||
results["send_message"] = r_msg
|
||
|
||
# 截图(发送前)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "input keyevent 26"], capture_output=True)
|
||
time.sleep(0.5)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "input swipe 540 1800 540 900"], capture_output=True)
|
||
time.sleep(0.5)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "am start -n com.tencent.mm/.ui.LauncherUI -e username filehelper"], capture_output=True)
|
||
time.sleep(2)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "screencap -p /sdcard/v6_msg_result.png"], capture_output=True)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "pull", "/sdcard/v6_msg_result.png", BASE + "/开发文档/6、测试/v6_msg_result.png"], capture_output=True)
|
||
print(f" 截图: v6_msg_result.png")
|
||
|
||
# ===== 操作2:发布朋友圈 =====
|
||
print("\n" + "="*40)
|
||
print("操作2:发布朋友圈(StarSnsInfo写入)")
|
||
print("="*40)
|
||
moment_content = f"工作手机SDK v6 后端发布朋友圈 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} #SDK测试 #Frida控制"
|
||
r_moment = rpc.post_moment_fixed({"content": moment_content, "my_wxid": "wxid_5g37snchpv8e22"})
|
||
print(f" 成功: {r_moment.get('success')}")
|
||
print(f" 朋友圈ID: {r_moment.get('sns_id')}")
|
||
print(f" 验证: {r_moment.get('verified')}")
|
||
print(f" 使用列: {r_moment.get('columns_used', [])}")
|
||
if r_moment.get('verified_data'):
|
||
print(f" 验证数据: {json.dumps(r_moment['verified_data'], ensure_ascii=False)[:100]}")
|
||
if not r_moment.get('success'):
|
||
print(f" 错误: {r_moment.get('error', '')}")
|
||
print(f" 备注: {r_moment.get('note', '')}")
|
||
results["post_moment"] = r_moment
|
||
|
||
# 截图朋友圈
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "am start -n com.tencent.mm/.plugin.sns.ui.SnsTimeLineUI"], capture_output=True)
|
||
time.sleep(2)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "screencap -p /sdcard/v6_moment_result.png"], capture_output=True)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "pull", "/sdcard/v6_moment_result.png", BASE + "/开发文档/6、测试/v6_moment_result.png"], capture_output=True)
|
||
print(f" 截图: v6_moment_result.png")
|
||
|
||
# ===== 操作3:添加好友 =====
|
||
print("\n" + "="*40)
|
||
print("操作3:添加好友(搜索外部联系人)")
|
||
print("="*40)
|
||
# 用一个不在通讯录的wxid测试
|
||
r_add = rpc.add_friend_external({"wxid": "gh_mmbiz_test", "msg": "你好,我是游条姐,希望加你为好友"})
|
||
print(f" 成功: {r_add.get('success')}")
|
||
print(f" 目标: {r_add.get('target_wxid')}")
|
||
print(f" 已存在: {r_add.get('already_exists')}")
|
||
print(f" 备注: {r_add.get('note', '')}")
|
||
if r_add.get('sample_real_friend'):
|
||
print(f" 样本好友: {r_add['sample_real_friend'][0].get('nickname', '')} ({r_add['sample_real_friend'][0].get('username', '')})")
|
||
results["add_friend"] = r_add
|
||
|
||
# ===== 操作4:付款分析 =====
|
||
print("\n" + "="*40)
|
||
print("操作4:付款分析(钱包DB + 安全说明)")
|
||
print("="*40)
|
||
r_pay = rpc.initiate_payment_fixed({"to_id": "filehelper", "amount": "0.01", "note": "SDK测试转账"})
|
||
print(f" 成功: {r_pay.get('success')}")
|
||
print(f" 钱包用户: {r_pay.get('wallet_user')}")
|
||
print(f" 账单列: {r_pay.get('ledger_columns', [])[:8]}")
|
||
print(f" 账单样本: {len(r_pay.get('ledger_sample', []))} 条")
|
||
print(f" 安全说明: {r_pay.get('security_note', '')}")
|
||
results["payment"] = r_pay
|
||
|
||
# ===== 验证 =====
|
||
print("\n【验证:最新消息(filehelper)】")
|
||
r_verify_msg = rpc.verify_message({"talker": "filehelper"})
|
||
if r_verify_msg.get("messages"):
|
||
for m in r_verify_msg["messages"]:
|
||
print(f" [{m.get('createTime','')}] {m.get('content','')[:60]}")
|
||
|
||
print("\n【验证:最新朋友圈(StarSnsInfo)】")
|
||
r_verify_moment = rpc.verify_moment()
|
||
if r_verify_moment.get("moments"):
|
||
for m in r_verify_moment["moments"]:
|
||
print(f" snsId:{m.get('snsId','')} | user:{m.get('userName','')} | time:{m.get('createTime','')} | localFlag:{m.get('localFlag','')}")
|
||
|
||
# ===== 最终截图 =====
|
||
print("\n【最终截图】")
|
||
time.sleep(1)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "shell", "screencap -p /sdcard/v6_final_v2.png"], capture_output=True)
|
||
subprocess.run(["adb", "-s", ADB_SERIAL, "pull", "/sdcard/v6_final_v2.png", BASE + "/开发文档/6、测试/v6_final_v2.png"], capture_output=True)
|
||
print(f" 截图: v6_final_v2.png")
|
||
|
||
# 保存结果
|
||
output = {
|
||
"time": datetime.now().isoformat(),
|
||
"pid": WECHAT_PID,
|
||
"results": results,
|
||
"events": events,
|
||
"msg_schema": r_msg_schema.get("columns", []),
|
||
"sns_schema": [c["name"] for c in r_sns_schema.get("columns", [])]
|
||
}
|
||
with open(BASE + "/verification_v6_results.json", "w", encoding="utf-8") as f:
|
||
json.dump(output, f, ensure_ascii=False, indent=2)
|
||
print(f"\n结果已保存: verification_v6_results.json")
|
||
|
||
# 汇总
|
||
print("\n" + "="*60)
|
||
print("操作汇总")
|
||
print("="*60)
|
||
ops = [
|
||
("发送消息", results.get("send_message", {}).get("success"), results.get("send_message", {}).get("message_id", "")),
|
||
("发布朋友圈", results.get("post_moment", {}).get("success"), results.get("post_moment", {}).get("sns_id", "")),
|
||
("添加好友", results.get("add_friend", {}).get("success"), results.get("add_friend", {}).get("note", "")),
|
||
("付款分析", results.get("payment", {}).get("success"), "钱包DB已读取,安全验证无法绕过"),
|
||
]
|
||
for name, ok, detail in ops:
|
||
status = "✅" if ok else "❌"
|
||
print(f" {status} {name}: {str(detail)[:60]}")
|
||
|
||
try: script.unload()
|
||
except: pass
|
||
session.detach()
|