Files
workphone-sdk/sdk/agent/hook/wechat_hook_v2.js
卡若 d6bb3f1acc feat: Phase 2 Hook增强 90% | 无Root方案 + 真机SDK通道验证通过 + 54项测试全绿
- 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
2026-03-14 15:27:36 +08:00

1195 lines
44 KiB
JavaScript
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.

/**
* 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 || '',
});
});