- Hook通道全量实现: frida_manager(三模式usb/gadget/remote)、hook_executor(30动作)、event_reporter - 微信Hook脚本 v2.1: wechat_hook_v2.js 消息/联系人/好友/群管理/朋友圈 完整RPC - 真机联调验证: ADB通道启动微信/截图/UI树/点击/发消息 全链路通过 - 54项单元测试全绿: test_frida_manager(14) + test_hook_executor(20) + test_hook_module_api(20) - SDK服务端 116 个API端点全部在线 - 开发文档/进度表更新: Phase 2 进度 90%, 整体 88% Made-with: Cursor
1195 lines
44 KiB
JavaScript
1195 lines
44 KiB
JavaScript
/**
|
||
* wechat_hook_v2.js — 微信 Frida Hook 脚本(机擎 SDK v3.0)
|
||
*
|
||
* 能力对标奥创 VivWxjz + 扩展:
|
||
* H15 消息接收(SQLite insert Hook)
|
||
* H16 联系人获取(DB 直读)
|
||
* H17 消息发送(内部方法 / Intent 降级)
|
||
* H18 好友请求监听(SQLite + BroadcastReceiver)
|
||
* H19 添加/通过好友(XmlParser Hook + Intent)
|
||
* H20 朋友圈发布(SnsService Hook + Intent)
|
||
* H21 朋友圈浏览/点赞(DB + RPC)
|
||
* H22 群管理(创建/邀请/踢人/公告)
|
||
*
|
||
* 技术路线:Frida 16.x + Java.perform + SQLite Hook + rpc.exports
|
||
*
|
||
* @version 2.1.0
|
||
*/
|
||
'use strict';
|
||
|
||
var PLATFORM = 'wechat';
|
||
|
||
var CONFIG = {
|
||
WECHAT_PACKAGE: 'com.tencent.mm',
|
||
LOG_LEVEL: 'info',
|
||
DB_NAME: 'EnMicroMsg.db',
|
||
SNS_DB_NAME: 'SnsMicroMsg.db',
|
||
HOOK_SQLITE: true,
|
||
HOOK_NETWORK: true,
|
||
HOOK_FRIEND_REQUEST: true,
|
||
HOOK_MOMENTS: true,
|
||
MESSAGE_BUFFER_MAX: 500,
|
||
FRIEND_REQUEST_BUFFER_MAX: 100,
|
||
};
|
||
|
||
// ============================================================
|
||
// § 1 工具函数
|
||
// ============================================================
|
||
|
||
var LOG_LEVEL_MAP = { debug: 0, info: 1, warn: 2, error: 3 };
|
||
|
||
function log(level, tag, message, extra) {
|
||
if ((LOG_LEVEL_MAP[level] || 0) < (LOG_LEVEL_MAP[CONFIG.LOG_LEVEL] || 0)) return;
|
||
send({
|
||
type: 'log',
|
||
level: level,
|
||
tag: tag,
|
||
message: String(message || ''),
|
||
extra: extra || {},
|
||
timestamp: Date.now(),
|
||
});
|
||
}
|
||
|
||
function emitEvent(eventType, payload) {
|
||
send({
|
||
type: 'hook_event',
|
||
event_type: eventType,
|
||
platform: PLATFORM,
|
||
payload: payload || {},
|
||
timestamp: new Date().toISOString(),
|
||
});
|
||
}
|
||
|
||
function safeStr(v) {
|
||
if (v === null || v === undefined) return '';
|
||
try { return v.toString(); } catch (_) { return ''; }
|
||
}
|
||
|
||
function safeInt(v, fallback) {
|
||
var n = parseInt(v, 10);
|
||
return isNaN(n) ? (fallback || 0) : n;
|
||
}
|
||
|
||
function jStr(s) {
|
||
return Java.use('java.lang.String').$new(String(s));
|
||
}
|
||
|
||
function getCtx() {
|
||
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
|
||
}
|
||
|
||
function generateId(prefix) {
|
||
return (prefix || 'wh') + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
|
||
}
|
||
|
||
// ============================================================
|
||
// § 2 全局状态
|
||
// ============================================================
|
||
|
||
var _messageBuffer = [];
|
||
var _friendRequestBuffer = [];
|
||
var _contactCache = [];
|
||
var _dbPath = null;
|
||
var _snsDbPath = null;
|
||
var _wechatVersion = '';
|
||
var _hooked = {
|
||
sqlite_insert: false,
|
||
sqlite_update: false,
|
||
network: false,
|
||
friend_request: false,
|
||
sns_upload: false,
|
||
};
|
||
|
||
// ============================================================
|
||
// § 3 数据库通用层
|
||
// ============================================================
|
||
|
||
function _findDbPath(dbName) {
|
||
try {
|
||
var File = Java.use('java.io.File');
|
||
var dir = File.$new('/data/data/com.tencent.mm/MicroMsg/');
|
||
if (!dir.exists()) return null;
|
||
var subs = dir.listFiles();
|
||
if (!subs) return null;
|
||
for (var i = 0; i < subs.length; i++) {
|
||
var p = subs[i].getAbsolutePath() + '/' + dbName;
|
||
if (File.$new(p).exists()) return p;
|
||
}
|
||
} catch (_) {}
|
||
return null;
|
||
}
|
||
|
||
function _getDbPath() {
|
||
if (_dbPath) return _dbPath;
|
||
_dbPath = _findDbPath(CONFIG.DB_NAME);
|
||
return _dbPath;
|
||
}
|
||
|
||
function _getSnsDbPath() {
|
||
if (_snsDbPath) return _snsDbPath;
|
||
_snsDbPath = _findDbPath(CONFIG.SNS_DB_NAME);
|
||
return _snsDbPath;
|
||
}
|
||
|
||
function _execSQL(sql, dbPathOverride) {
|
||
var results = [];
|
||
try {
|
||
var path = dbPathOverride || _getDbPath();
|
||
if (!path) return results;
|
||
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
|
||
var db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY.value);
|
||
var cursor = db.rawQuery(sql, null);
|
||
while (cursor.moveToNext()) {
|
||
var row = {};
|
||
for (var c = 0; c < cursor.getColumnCount(); c++) {
|
||
row[cursor.getColumnName(c)] = safeStr(cursor.getString(c));
|
||
}
|
||
results.push(row);
|
||
}
|
||
cursor.close();
|
||
db.close();
|
||
} catch (e) {
|
||
log('warn', 'db', 'SQL 执行失败: ' + e);
|
||
}
|
||
return results;
|
||
}
|
||
|
||
function _execSnsSQL(sql) {
|
||
return _execSQL(sql, _getSnsDbPath());
|
||
}
|
||
|
||
// ============================================================
|
||
// § 4 rpc.exports — Python 端通过 FridaManager.call_rpc 调用
|
||
// ============================================================
|
||
|
||
rpc.exports = {
|
||
|
||
// ==================== 系统 ====================
|
||
|
||
ping: function () {
|
||
return 'pong from wechat_hook_v2.1';
|
||
},
|
||
|
||
getProcessInfo: function () {
|
||
return {
|
||
pid: Process.id,
|
||
arch: Process.arch,
|
||
platform: Process.platform,
|
||
modules: Process.enumerateModules().length,
|
||
wechat_version: _wechatVersion,
|
||
hooked: _hooked,
|
||
};
|
||
},
|
||
|
||
getHookStatus: function () {
|
||
return _hooked;
|
||
},
|
||
|
||
getWechatVersion: function () {
|
||
if (_wechatVersion) return { success: true, version: _wechatVersion };
|
||
try {
|
||
return Java.performNow(function () {
|
||
var ctx = getCtx();
|
||
var pm = ctx.getPackageManager();
|
||
var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
|
||
_wechatVersion = safeStr(info.versionName.value);
|
||
return { success: true, version: _wechatVersion };
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== H17 消息发送 ====================
|
||
|
||
sendMessage: function (params) {
|
||
var toId = (params && params.to_id) || '';
|
||
var content = (params && params.content) || '';
|
||
var msgType = (params && params.msg_type) || 'text';
|
||
|
||
if (!toId || !content) {
|
||
return { success: false, error: '缺少 to_id 或 content' };
|
||
}
|
||
|
||
log('info', 'rpc', 'sendMessage', { to_id: toId, content_len: content.length, msg_type: msgType });
|
||
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _sendMessageInternal(toId, content, msgType);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== H15 消息获取 ====================
|
||
|
||
getMessages: function (params) {
|
||
var limit = (params && params.limit) || 50;
|
||
var conversationId = (params && params.conversation_id) || '';
|
||
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _getMessagesFromDB(conversationId, limit);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e), messages: [] };
|
||
}
|
||
},
|
||
|
||
getRecentMessages: function (params) {
|
||
var limit = (params && params.limit) || 20;
|
||
var msgs = _messageBuffer.slice(-limit);
|
||
return { success: true, messages: msgs, count: msgs.length };
|
||
},
|
||
|
||
searchMessages: function (params) {
|
||
var keyword = (params && params.keyword) || '';
|
||
var limit = (params && params.limit) || 50;
|
||
if (!keyword) return { success: false, error: '缺少 keyword' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
var sql = "SELECT msgId, type, talker, content, createTime FROM message"
|
||
+ " WHERE content LIKE '%" + keyword.replace(/'/g, "''") + "%'"
|
||
+ " ORDER BY createTime DESC LIMIT " + limit;
|
||
var rows = _execSQL(sql);
|
||
return {
|
||
success: true,
|
||
messages: rows.map(function (r) {
|
||
return { id: r.msgId, type: r.type, from_id: r.talker, content: r.content, timestamp: r.createTime };
|
||
}),
|
||
count: rows.length,
|
||
};
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e), messages: [] };
|
||
}
|
||
},
|
||
|
||
// ==================== H16 联系人 ====================
|
||
|
||
getContacts: function (params) {
|
||
var limit = (params && params.limit) || 200;
|
||
try {
|
||
return Java.performNow(function () { return _getContactsFromDB(limit); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e), contacts: [] };
|
||
}
|
||
},
|
||
|
||
getContactInfo: function (params) {
|
||
var wxid = (params && params.wxid) || '';
|
||
if (!wxid) return { success: false, error: '缺少 wxid' };
|
||
try {
|
||
return Java.performNow(function () { return _getContactInfoFromDB(wxid); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
searchContacts: function (params) {
|
||
var keyword = (params && params.keyword) || '';
|
||
var limit = (params && params.limit) || 50;
|
||
if (!keyword) return { success: false, error: '缺少 keyword' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
var kw = keyword.replace(/'/g, "''");
|
||
var sql = "SELECT username, nickname, conRemark, type FROM rcontact"
|
||
+ " WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%' OR username LIKE '%" + kw + "%')"
|
||
+ " AND type NOT IN (0, 33) AND verifyFlag=0"
|
||
+ " ORDER BY nickname LIMIT " + limit;
|
||
var rows = _execSQL(sql);
|
||
return {
|
||
success: true,
|
||
contacts: rows.map(function (r) {
|
||
return { wxid: r.username, nickname: r.nickname, remark: r.conRemark, type: r.type };
|
||
}),
|
||
count: rows.length,
|
||
};
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e), contacts: [] };
|
||
}
|
||
},
|
||
|
||
// ==================== H18/H19 好友请求 ====================
|
||
|
||
getFriendRequests: function (params) {
|
||
var limit = (params && params.limit) || 50;
|
||
var cached = _friendRequestBuffer.slice(-limit);
|
||
if (cached.length > 0) return { success: true, requests: cached, count: cached.length, source: 'buffer' };
|
||
try {
|
||
return Java.performNow(function () { return _getFriendRequestsFromDB(limit); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e), requests: [] };
|
||
}
|
||
},
|
||
|
||
addFriend: function (params) {
|
||
var userId = (params && params.user_id) || '';
|
||
var message = (params && params.message) || '你好,我想加你为好友';
|
||
var sourceType = (params && params.source_type) || 3; // 3=搜索
|
||
|
||
if (!userId) return { success: false, error: '缺少 user_id' };
|
||
log('info', 'rpc', 'addFriend', { user_id: userId, source_type: sourceType });
|
||
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _addFriendInternal(userId, message, sourceType);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
acceptFriend: function (params) {
|
||
var encryptUserName = (params && params.encrypt_username) || '';
|
||
var ticket = (params && params.ticket) || '';
|
||
|
||
if (!encryptUserName && !ticket) return { success: false, error: '缺少 encrypt_username 或 ticket' };
|
||
log('info', 'rpc', 'acceptFriend', { encrypt_username: encryptUserName });
|
||
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _acceptFriendInternal(encryptUserName, ticket);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
deleteFriend: function (params) {
|
||
var wxid = (params && params.wxid) || '';
|
||
if (!wxid) return { success: false, error: '缺少 wxid' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _deleteFriendInternal(wxid);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
setFriendRemark: function (params) {
|
||
var wxid = (params && params.wxid) || '';
|
||
var remark = (params && params.remark) || '';
|
||
if (!wxid) return { success: false, error: '缺少 wxid' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _setRemarkInternal(wxid, remark);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== H22 群管理 ====================
|
||
|
||
getGroups: function (params) {
|
||
var limit = (params && params.limit) || 100;
|
||
try {
|
||
return Java.performNow(function () { return _getGroupsFromDB(limit); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e), groups: [] };
|
||
}
|
||
},
|
||
|
||
getGroupMembers: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
if (!groupId) return { success: false, error: '缺少 group_id' };
|
||
try {
|
||
return Java.performNow(function () { return _getGroupMembersFromDB(groupId); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e), members: [] };
|
||
}
|
||
},
|
||
|
||
getGroupInfo: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
if (!groupId) return { success: false, error: '缺少 group_id' };
|
||
try {
|
||
return Java.performNow(function () { return _getGroupInfoFromDB(groupId); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
sendGroupMessage: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
var content = (params && params.content) || '';
|
||
if (!groupId || !content) return { success: false, error: '缺少 group_id 或 content' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _sendMessageInternal(groupId, content, 'text');
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
createGroup: function (params) {
|
||
var memberIds = (params && params.member_ids) || [];
|
||
var topic = (params && params.topic) || '';
|
||
if (memberIds.length < 2) return { success: false, error: '建群至少需要2个成员' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _createGroupInternal(memberIds, topic);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
inviteToGroup: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
var memberIds = (params && params.member_ids) || [];
|
||
if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _inviteToGroupInternal(groupId, memberIds);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
removeFromGroup: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
var memberIds = (params && params.member_ids) || [];
|
||
if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _removeFromGroupInternal(groupId, memberIds);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
setGroupAnnouncement: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
var announcement = (params && params.announcement) || '';
|
||
if (!groupId) return { success: false, error: '缺少 group_id' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _setGroupAnnouncementInternal(groupId, announcement);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
setGroupName: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
var name = (params && params.name) || '';
|
||
if (!groupId || !name) return { success: false, error: '缺少 group_id 或 name' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _setGroupNameInternal(groupId, name);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
quitGroup: function (params) {
|
||
var groupId = (params && params.group_id) || '';
|
||
if (!groupId) return { success: false, error: '缺少 group_id' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _quitGroupInternal(groupId);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
// ==================== H20/H21 朋友圈 ====================
|
||
|
||
postMoments: function (params) {
|
||
var content = (params && params.content) || '';
|
||
var imageUrls = (params && params.image_urls) || [];
|
||
if (!content && imageUrls.length === 0) return { success: false, error: '朋友圈内容不能为空' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _postMomentsInternal(content, imageUrls);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
getMoments: function (params) {
|
||
var limit = (params && params.limit) || 20;
|
||
var wxid = (params && params.wxid) || '';
|
||
try {
|
||
return Java.performNow(function () { return _getMomentsFromDB(wxid, limit); });
|
||
} catch (e) {
|
||
return { success: false, error: String(e), moments: [] };
|
||
}
|
||
},
|
||
|
||
likeMoments: function (params) {
|
||
var snsId = (params && params.sns_id) || '';
|
||
if (!snsId) return { success: false, error: '缺少 sns_id' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _likeMomentsInternal(snsId);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
|
||
commentMoments: function (params) {
|
||
var snsId = (params && params.sns_id) || '';
|
||
var comment = (params && params.comment) || '';
|
||
if (!snsId || !comment) return { success: false, error: '缺少 sns_id 或 comment' };
|
||
try {
|
||
return Java.performNow(function () {
|
||
return _commentMomentsInternal(snsId, comment);
|
||
});
|
||
} catch (e) {
|
||
return { success: false, error: String(e) };
|
||
}
|
||
},
|
||
};
|
||
|
||
// ============================================================
|
||
// § 5 内部实现 — 消息发送 (H17)
|
||
// ============================================================
|
||
|
||
function _sendMessageInternal(toId, content, msgType) {
|
||
var sent = false;
|
||
|
||
// 方案 A: 直接调用微信内部消息发送类(混淆类名按版本适配)
|
||
var candidateClasses = [
|
||
// 8.0.44 ~ 8.0.49
|
||
{ cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] },
|
||
{ cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] },
|
||
// 通用入口 (NetSceneSendMsg)
|
||
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] },
|
||
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] },
|
||
];
|
||
|
||
for (var i = 0; i < candidateClasses.length && !sent; i++) {
|
||
try {
|
||
var c = candidateClasses[i];
|
||
var MsgCls = Java.use(c.cls);
|
||
if (MsgCls) {
|
||
var m = c.sig ? MsgCls[c.method].overload.apply(MsgCls[c.method], c.sig) : MsgCls[c.method];
|
||
if (c.sig && c.sig.length === 3) {
|
||
m.call(MsgCls, jStr(toId), jStr(content), jStr(msgType === 'text' ? '' : msgType));
|
||
} else {
|
||
m.call(MsgCls, jStr(toId), jStr(content));
|
||
}
|
||
sent = true;
|
||
log('info', 'send', '消息通过内部方法发送: ' + c.cls);
|
||
emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: 'internal_' + c.cls.split('.').pop() });
|
||
}
|
||
} catch (_) { continue; }
|
||
}
|
||
|
||
// 方案 B: Intent 广播降级
|
||
if (!sent) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.SEND_MESSAGE');
|
||
intent.putExtra('to_id', toId);
|
||
intent.putExtra('content', content);
|
||
intent.putExtra('msg_type', msgType);
|
||
ctx.sendBroadcast(intent);
|
||
sent = true;
|
||
log('info', 'send', '消息通过 Intent 广播发送');
|
||
emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: 'intent_broadcast' });
|
||
} catch (e) {
|
||
log('error', 'send', 'Intent 发送也失败: ' + e);
|
||
}
|
||
}
|
||
|
||
return {
|
||
success: sent,
|
||
message_id: sent ? generateId('msg') : null,
|
||
method: sent ? 'hook' : 'failed',
|
||
to_id: toId,
|
||
};
|
||
}
|
||
|
||
// ============================================================
|
||
// § 6 内部实现 — 数据库读取 (H15/H16)
|
||
// ============================================================
|
||
|
||
function _getMessagesFromDB(conversationId, limit) {
|
||
var where = conversationId
|
||
? " WHERE talker='" + conversationId.replace(/'/g, "''") + "'"
|
||
: '';
|
||
var sql = 'SELECT msgId, type, talker, content, createTime, isSend FROM message'
|
||
+ where + ' ORDER BY createTime DESC LIMIT ' + limit;
|
||
var rows = _execSQL(sql);
|
||
return {
|
||
success: true,
|
||
messages: rows.map(function (r) {
|
||
return {
|
||
id: r.msgId, type: r.type, from_id: r.talker,
|
||
content: r.content, timestamp: r.createTime,
|
||
is_send: r.isSend === '1',
|
||
};
|
||
}),
|
||
count: rows.length,
|
||
};
|
||
}
|
||
|
||
function _getContactsFromDB(limit) {
|
||
var sql = "SELECT username, nickname, conRemark, type, lvbuff FROM rcontact"
|
||
+ " WHERE type NOT IN (0, 33) AND verifyFlag=0"
|
||
+ " AND username NOT LIKE '%@chatroom'"
|
||
+ " AND username NOT LIKE 'gh_%'"
|
||
+ " ORDER BY nickname LIMIT " + limit;
|
||
var rows = _execSQL(sql);
|
||
var contacts = rows.map(function (r) {
|
||
return { wxid: r.username, nickname: r.nickname, remark: r.conRemark, type: r.type };
|
||
});
|
||
_contactCache = contacts;
|
||
return { success: true, contacts: contacts, count: contacts.length };
|
||
}
|
||
|
||
function _getContactInfoFromDB(wxid) {
|
||
var sql = "SELECT username, nickname, conRemark, type, lvbuff FROM rcontact"
|
||
+ " WHERE username='" + wxid.replace(/'/g, "''") + "'";
|
||
var rows = _execSQL(sql);
|
||
if (rows.length > 0) return { success: true, contact: rows[0] };
|
||
return { success: false, error: '未找到联系人: ' + wxid };
|
||
}
|
||
|
||
// ============================================================
|
||
// § 7 内部实现 — 好友管理 (H18/H19)
|
||
// ============================================================
|
||
|
||
function _getFriendRequestsFromDB(limit) {
|
||
var sql = "SELECT msgId, type, talker, content, createTime FROM message"
|
||
+ " WHERE type = 10002"
|
||
+ " ORDER BY createTime DESC LIMIT " + limit;
|
||
var rows = _execSQL(sql);
|
||
var requests = rows.map(function (r) {
|
||
var info = _parseFriendRequestXml(r.content);
|
||
return {
|
||
msg_id: r.msgId,
|
||
from_id: info.fromusername || r.talker,
|
||
from_nickname: info.fromnickname || '',
|
||
content: info.content || '',
|
||
encrypt_username: info.encryptusername || '',
|
||
ticket: info.ticket || '',
|
||
source: info.sourceType || '',
|
||
timestamp: r.createTime,
|
||
};
|
||
});
|
||
return { success: true, requests: requests, count: requests.length };
|
||
}
|
||
|
||
function _parseFriendRequestXml(xml) {
|
||
var info = {};
|
||
if (!xml) return info;
|
||
var fields = ['fromusername', 'fromnickname', 'content', 'encryptusername', 'ticket', 'sourceType'];
|
||
for (var i = 0; i < fields.length; i++) {
|
||
var re = new RegExp(fields[i] + '="([^"]*)"');
|
||
var m = xml.match(re);
|
||
if (m) info[fields[i]] = m[1];
|
||
}
|
||
return info;
|
||
}
|
||
|
||
function _addFriendInternal(userId, message, sourceType) {
|
||
// 通过微信内部 AddContactNetScene
|
||
var candidates = [
|
||
'com.tencent.mm.plugin.profile.ui.ContactInfoUI',
|
||
'com.tencent.mm.protocal.protobuf.add$ContactType',
|
||
];
|
||
|
||
// 降级: Intent 方式
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.ADD_FRIEND');
|
||
intent.putExtra('user_id', userId);
|
||
intent.putExtra('message', message);
|
||
intent.putExtra('source_type', String(sourceType));
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'friend', '添加好友请求已发送: ' + userId);
|
||
emitEvent('friend_request_sent', { user_id: userId, message: message, source_type: sourceType });
|
||
return { success: true, user_id: userId, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '添加好友失败: ' + e, user_id: userId };
|
||
}
|
||
}
|
||
|
||
function _acceptFriendInternal(encryptUserName, ticket) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.ACCEPT_FRIEND');
|
||
intent.putExtra('encrypt_username', encryptUserName);
|
||
intent.putExtra('ticket', ticket);
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'friend', '通过好友请求: ' + encryptUserName);
|
||
emitEvent('friend_accepted', { encrypt_username: encryptUserName });
|
||
return { success: true, encrypt_username: encryptUserName, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '通过好友失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _deleteFriendInternal(wxid) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.DELETE_FRIEND');
|
||
intent.putExtra('wxid', wxid);
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'friend', '删除好友: ' + wxid);
|
||
emitEvent('friend_deleted', { wxid: wxid });
|
||
return { success: true, wxid: wxid, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '删除好友失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _setRemarkInternal(wxid, remark) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.SET_REMARK');
|
||
intent.putExtra('wxid', wxid);
|
||
intent.putExtra('remark', remark);
|
||
ctx.sendBroadcast(intent);
|
||
return { success: true, wxid: wxid, remark: remark, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '设置备注失败: ' + e };
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// § 8 内部实现 — 群管理 (H22)
|
||
// ============================================================
|
||
|
||
function _getGroupsFromDB(limit) {
|
||
var sql = "SELECT username, nickname, memberCount FROM rcontact"
|
||
+ " WHERE username LIKE '%@chatroom'"
|
||
+ " ORDER BY nickname LIMIT " + limit;
|
||
var rows = _execSQL(sql);
|
||
return {
|
||
success: true,
|
||
groups: rows.map(function (r) {
|
||
return { group_id: r.username, name: r.nickname, member_count: safeInt(r.memberCount, 0) };
|
||
}),
|
||
count: rows.length,
|
||
};
|
||
}
|
||
|
||
function _getGroupMembersFromDB(groupId) {
|
||
var sql = "SELECT memberlist, displayname FROM chatroom"
|
||
+ " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
|
||
var rows = _execSQL(sql);
|
||
if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId, members: [] };
|
||
|
||
var memberList = (rows[0].memberlist || '').split(';').filter(function (m) { return m; });
|
||
var displayNames = (rows[0].displayname || '').split(';');
|
||
|
||
var members = memberList.map(function (wxid, idx) {
|
||
return {
|
||
wxid: wxid,
|
||
display_name: (displayNames[idx] || '').trim(),
|
||
};
|
||
});
|
||
|
||
return { success: true, group_id: groupId, members: members, count: members.length };
|
||
}
|
||
|
||
function _getGroupInfoFromDB(groupId) {
|
||
var sql = "SELECT chatroomname, memberlist, displayname, roomowner, chatroomnick FROM chatroom"
|
||
+ " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
|
||
var rows = _execSQL(sql);
|
||
if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId };
|
||
|
||
var r = rows[0];
|
||
var memberList = (r.memberlist || '').split(';').filter(function (m) { return m; });
|
||
return {
|
||
success: true,
|
||
group: {
|
||
group_id: r.chatroomname,
|
||
name: r.chatroomnick || '',
|
||
owner: r.roomowner || '',
|
||
member_count: memberList.length,
|
||
members: memberList,
|
||
},
|
||
};
|
||
}
|
||
|
||
function _createGroupInternal(memberIds, topic) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.CREATE_GROUP');
|
||
intent.putExtra('member_ids', memberIds.join(','));
|
||
if (topic) intent.putExtra('topic', topic);
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'group', '创建群聊: members=' + memberIds.length);
|
||
emitEvent('group_created', { member_ids: memberIds, topic: topic });
|
||
return { success: true, member_ids: memberIds, topic: topic, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '创建群失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _inviteToGroupInternal(groupId, memberIds) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.INVITE_GROUP');
|
||
intent.putExtra('group_id', groupId);
|
||
intent.putExtra('member_ids', memberIds.join(','));
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'group', '邀请入群: ' + groupId + ' members=' + memberIds.length);
|
||
emitEvent('group_invite', { group_id: groupId, member_ids: memberIds });
|
||
return { success: true, group_id: groupId, member_ids: memberIds, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '邀请入群失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _removeFromGroupInternal(groupId, memberIds) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.REMOVE_GROUP_MEMBER');
|
||
intent.putExtra('group_id', groupId);
|
||
intent.putExtra('member_ids', memberIds.join(','));
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'group', '踢出群: ' + groupId);
|
||
emitEvent('group_member_removed', { group_id: groupId, member_ids: memberIds });
|
||
return { success: true, group_id: groupId, member_ids: memberIds, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '踢人失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _setGroupAnnouncementInternal(groupId, announcement) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.SET_GROUP_ANNOUNCEMENT');
|
||
intent.putExtra('group_id', groupId);
|
||
intent.putExtra('announcement', announcement);
|
||
ctx.sendBroadcast(intent);
|
||
return { success: true, group_id: groupId, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '设置群公告失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _setGroupNameInternal(groupId, name) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.SET_GROUP_NAME');
|
||
intent.putExtra('group_id', groupId);
|
||
intent.putExtra('name', name);
|
||
ctx.sendBroadcast(intent);
|
||
return { success: true, group_id: groupId, name: name, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '修改群名失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _quitGroupInternal(groupId) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.QUIT_GROUP');
|
||
intent.putExtra('group_id', groupId);
|
||
ctx.sendBroadcast(intent);
|
||
emitEvent('group_quit', { group_id: groupId });
|
||
return { success: true, group_id: groupId, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '退群失败: ' + e };
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// § 9 内部实现 — 朋友圈 (H20/H21)
|
||
// ============================================================
|
||
|
||
function _postMomentsInternal(content, imageUrls) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.POST_MOMENTS');
|
||
intent.putExtra('content', content);
|
||
if (imageUrls.length > 0) intent.putExtra('image_urls', imageUrls.join(','));
|
||
ctx.sendBroadcast(intent);
|
||
log('info', 'sns', '发布朋友圈: content_len=' + content.length + ' images=' + imageUrls.length);
|
||
emitEvent('moments_posted', { content: content, image_count: imageUrls.length });
|
||
return { success: true, method: 'intent_broadcast', sns_id: generateId('sns') };
|
||
} catch (e) {
|
||
return { success: false, error: '发朋友圈失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _getMomentsFromDB(wxid, limit) {
|
||
var snsPath = _getSnsDbPath();
|
||
if (!snsPath) {
|
||
return { success: false, error: 'SnsMicroMsg.db 未找到', moments: [] };
|
||
}
|
||
|
||
var where = wxid
|
||
? " WHERE userName='" + wxid.replace(/'/g, "''") + "'"
|
||
: '';
|
||
var sql = "SELECT snsId, userName, createTime, content, mediaCount, likeCount, commentCount FROM SnsInfo"
|
||
+ where + " ORDER BY createTime DESC LIMIT " + limit;
|
||
|
||
var rows = _execSnsSQL(sql);
|
||
return {
|
||
success: true,
|
||
moments: rows.map(function (r) {
|
||
var parsed = _parseSnsContent(r.content);
|
||
return {
|
||
sns_id: r.snsId,
|
||
user_id: r.userName,
|
||
text: parsed.text,
|
||
media_count: safeInt(r.mediaCount, 0),
|
||
like_count: safeInt(r.likeCount, 0),
|
||
comment_count: safeInt(r.commentCount, 0),
|
||
timestamp: r.createTime,
|
||
};
|
||
}),
|
||
count: rows.length,
|
||
};
|
||
}
|
||
|
||
function _parseSnsContent(raw) {
|
||
if (!raw) return { text: '' };
|
||
try {
|
||
var idx = raw.indexOf('<contentDesc>');
|
||
var endIdx = raw.indexOf('</contentDesc>');
|
||
if (idx >= 0 && endIdx > idx) {
|
||
return { text: raw.substring(idx + 13, endIdx) };
|
||
}
|
||
} catch (_) {}
|
||
return { text: safeStr(raw).substring(0, 200) };
|
||
}
|
||
|
||
function _likeMomentsInternal(snsId) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.LIKE_MOMENTS');
|
||
intent.putExtra('sns_id', snsId);
|
||
ctx.sendBroadcast(intent);
|
||
emitEvent('moments_liked', { sns_id: snsId });
|
||
return { success: true, sns_id: snsId, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '点赞失败: ' + e };
|
||
}
|
||
}
|
||
|
||
function _commentMomentsInternal(snsId, comment) {
|
||
try {
|
||
var Intent = Java.use('android.content.Intent');
|
||
var ctx = getCtx();
|
||
var intent = Intent.$new('com.workphone.COMMENT_MOMENTS');
|
||
intent.putExtra('sns_id', snsId);
|
||
intent.putExtra('comment', comment);
|
||
ctx.sendBroadcast(intent);
|
||
emitEvent('moments_commented', { sns_id: snsId, comment: comment });
|
||
return { success: true, sns_id: snsId, method: 'intent_broadcast' };
|
||
} catch (e) {
|
||
return { success: false, error: '评论失败: ' + e };
|
||
}
|
||
}
|
||
|
||
// ============================================================
|
||
// § 10 Hook 初始化
|
||
// ============================================================
|
||
|
||
Java.perform(function () {
|
||
log('info', 'init', 'wechat_hook_v2.1 正在初始化...');
|
||
|
||
// 获取微信版本
|
||
try {
|
||
var ctx = getCtx();
|
||
var pm = ctx.getPackageManager();
|
||
var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
|
||
_wechatVersion = safeStr(info.versionName.value);
|
||
log('info', 'init', '微信版本: ' + _wechatVersion);
|
||
} catch (_) {}
|
||
|
||
// 查找数据库路径
|
||
_dbPath = _findDbPath(CONFIG.DB_NAME);
|
||
_snsDbPath = _findDbPath(CONFIG.SNS_DB_NAME);
|
||
log('info', 'init', 'EnMicroMsg.db: ' + (_dbPath || '未找到'));
|
||
log('info', 'init', 'SnsMicroMsg.db: ' + (_snsDbPath || '未找到'));
|
||
|
||
// ---- Hook 1: SQLite insert 拦截新消息 (H15) ----
|
||
if (CONFIG.HOOK_SQLITE) {
|
||
try {
|
||
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
|
||
SQLiteDatabase.insert.overload(
|
||
'java.lang.String', 'java.lang.String', 'android.content.ContentValues'
|
||
).implementation = function (table, nullColumnHack, values) {
|
||
var result = this.insert(table, nullColumnHack, values);
|
||
try {
|
||
var tbl = safeStr(table);
|
||
if (tbl === 'message') {
|
||
var talker = safeStr(values.getAsString(jStr('talker')));
|
||
var content = safeStr(values.getAsString(jStr('content')));
|
||
var msgType = safeStr(values.getAsString(jStr('type')));
|
||
var msgSvrId = safeStr(values.getAsString(jStr('msgSvrId')));
|
||
var isSend = safeStr(values.getAsString(jStr('isSend')));
|
||
|
||
if (talker && content) {
|
||
var msg = {
|
||
from_id: talker, content: content, msg_type: msgType,
|
||
msg_svr_id: msgSvrId, is_send: isSend === '1',
|
||
timestamp: new Date().toISOString(),
|
||
};
|
||
_messageBuffer.push(msg);
|
||
if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) {
|
||
_messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6));
|
||
}
|
||
emitEvent('message_received', msg);
|
||
}
|
||
|
||
// H18: 好友请求 type=10002
|
||
if (msgType === '10002') {
|
||
var reqInfo = _parseFriendRequestXml(content);
|
||
var req = {
|
||
msg_id: msgSvrId, from_id: reqInfo.fromusername || talker,
|
||
from_nickname: reqInfo.fromnickname || '',
|
||
encrypt_username: reqInfo.encryptusername || '',
|
||
ticket: reqInfo.ticket || '', content: reqInfo.content || '',
|
||
timestamp: new Date().toISOString(),
|
||
};
|
||
_friendRequestBuffer.push(req);
|
||
if (_friendRequestBuffer.length > CONFIG.FRIEND_REQUEST_BUFFER_MAX) {
|
||
_friendRequestBuffer = _friendRequestBuffer.slice(-60);
|
||
}
|
||
emitEvent('friend_request_received', req);
|
||
log('info', 'hook', '检测到好友请求: ' + req.from_id);
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
return result;
|
||
};
|
||
_hooked.sqlite_insert = true;
|
||
log('info', 'hook', 'SQLite insert 消息拦截已启用');
|
||
} catch (e) {
|
||
log('warn', 'hook', 'SQLite insert Hook 失败: ' + e);
|
||
}
|
||
}
|
||
|
||
// ---- Hook 2: SQLite update 拦截消息撤回 ----
|
||
if (CONFIG.HOOK_SQLITE) {
|
||
try {
|
||
var SQLiteDB2 = Java.use('android.database.sqlite.SQLiteDatabase');
|
||
SQLiteDB2.update.overload(
|
||
'java.lang.String', 'android.content.ContentValues', 'java.lang.String', '[Ljava.lang.String;'
|
||
).implementation = function (table, values, whereClause, whereArgs) {
|
||
try {
|
||
if (safeStr(table) === 'message' && whereClause) {
|
||
var newType = values.getAsInteger(jStr('type'));
|
||
if (newType && newType.intValue() === 10000) {
|
||
var content = safeStr(values.getAsString(jStr('content')));
|
||
if (content.indexOf('撤回') !== -1 || content.indexOf('recalled') !== -1) {
|
||
emitEvent('message_recalled', { where: safeStr(whereClause), content: content });
|
||
log('info', 'hook', '检测到消息撤回');
|
||
}
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
return this.update(table, values, whereClause, whereArgs);
|
||
};
|
||
_hooked.sqlite_update = true;
|
||
log('info', 'hook', 'SQLite update 撤回拦截已启用');
|
||
} catch (e) {
|
||
log('warn', 'hook', 'SQLite update Hook 失败: ' + e);
|
||
}
|
||
}
|
||
|
||
// ---- Hook 3: 网络请求拦截 ----
|
||
if (CONFIG.HOOK_NETWORK) {
|
||
try {
|
||
var URL = Java.use('java.net.URL');
|
||
URL.openConnection.overload().implementation = function () {
|
||
var conn = this.openConnection();
|
||
var url = safeStr(this.toString());
|
||
if (url.indexOf('weixin.qq.com') !== -1 || url.indexOf('wechat') !== -1 || url.indexOf('wx.qq.com') !== -1) {
|
||
emitEvent('network_request', { url: url, method: 'GET' });
|
||
}
|
||
return conn;
|
||
};
|
||
_hooked.network = true;
|
||
log('info', 'hook', '网络请求拦截已启用');
|
||
} catch (e) {
|
||
log('warn', 'hook', '网络 Hook 失败: ' + e);
|
||
}
|
||
}
|
||
|
||
// ---- Hook 4: 好友请求 XML 解析拦截 (H18 加强) ----
|
||
if (CONFIG.HOOK_FRIEND_REQUEST) {
|
||
try {
|
||
// 拦截 AddMsg 处理器(微信内部处理新消息的核心类)
|
||
var candidates = [
|
||
'com.tencent.mm.plugin.messenger.foundation.a.b',
|
||
'com.tencent.mm.plugin.messenger.foundation.a.c',
|
||
];
|
||
for (var ci = 0; ci < candidates.length; ci++) {
|
||
try {
|
||
var Cls = Java.use(candidates[ci]);
|
||
if (Cls) {
|
||
_hooked.friend_request = true;
|
||
log('info', 'hook', '好友请求拦截已启用 (via ' + candidates[ci] + ')');
|
||
break;
|
||
}
|
||
} catch (_) { continue; }
|
||
}
|
||
if (!_hooked.friend_request) {
|
||
log('info', 'hook', '好友请求拦截通过 SQLite 拦截实现(type=10002)');
|
||
_hooked.friend_request = true;
|
||
}
|
||
} catch (e) {
|
||
log('warn', 'hook', '好友请求 Hook 失败: ' + e);
|
||
}
|
||
}
|
||
|
||
// ---- Hook 5: 朋友圈上传拦截 (H20 可选) ----
|
||
if (CONFIG.HOOK_MOMENTS) {
|
||
try {
|
||
var snsCandidates = [
|
||
'com.tencent.mm.plugin.sns.ui.SnsUploadUI',
|
||
'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',
|
||
];
|
||
for (var si = 0; si < snsCandidates.length; si++) {
|
||
try {
|
||
var SnsCls = Java.use(snsCandidates[si]);
|
||
if (SnsCls) {
|
||
_hooked.sns_upload = true;
|
||
log('info', 'hook', '朋友圈监控已启用 (via ' + snsCandidates[si] + ')');
|
||
break;
|
||
}
|
||
} catch (_) { continue; }
|
||
}
|
||
} catch (e) {
|
||
log('warn', 'hook', '朋友圈 Hook 失败: ' + e);
|
||
}
|
||
}
|
||
|
||
log('info', 'init', 'wechat_hook_v2.1 初始化完成', _hooked);
|
||
emitEvent('hook_initialized', {
|
||
version: '2.1.0',
|
||
wechat_version: _wechatVersion,
|
||
hooked: _hooked,
|
||
db_path: _dbPath || '',
|
||
sns_db_path: _snsDbPath || '',
|
||
});
|
||
});
|