/** * wechat_hook_v2.js — 微信 Frida Hook 脚本(机擎 SDK v3.0) * * 全功能微信控制引擎 — 110 个操作 / 24 模块 * * 模块清单: * H15 消息接收(SQLite insert Hook) * H16 联系人获取(DB 直读) * H17 消息发送(内部方法 / Intent 降级) * H18 好友请求监听(SQLite + BroadcastReceiver) * H19 添加/通过好友(XmlParser Hook + Intent) * H20 朋友圈发布(SnsService Hook + Intent) * H21 朋友圈浏览/点赞(DB + RPC) * H22 群管理(创建/邀请/踢人/公告) * H23 账号管理(昵称/头像/签名/状态/性别/地区) * H24 账号安全(解封/改密/绑定手机/登录设备管理) * H25 支付(红包/转账/收款/零钱) * H26 二维码(扫码/生成个人码/群码) * H27 视频号(浏览/点赞/评论/关注) * H28 标签管理(创建/删除/打标/查询) * H29 收藏管理(添加/删除/列表) * H30 设置(隐私/通知/通用/聊天背景) * H31 搜索(全局搜索/联系人/消息/公众号) * H32 小程序(打开/最近/收藏) * H33 文件传输(发送文件/图片/视频/语音) * H34 消息转发(单条/合并转发) * H35 注册/登录(新号注册/登录/切号) * H36 公众号(关注/取关/列表/文章) * H37 表情管理(发送/收藏/自定义) * H38 浮窗/多任务 * H39 设备信息 * * 技术路线:Frida 17.x + Java.perform + SQLite Hook + rpc.exports * * @version 3.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, }; // ============================================================ // § 0 版本兼容系统 (H24) // ============================================================ var _versionCompat = null; var _VERSION_TABLE = { '8.0.44': { send_message: [ { cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.49': { send_message: [ { cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] }, { cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] } ], friend_request: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }, { cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' } ], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.51': { send_message: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }, { cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.56': { send_message: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }, { cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }, { cls: 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.58': { send_message: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.60': { send_message: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] }, '8.0.69': { send_message: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] } ], friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }], sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }], add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }] } }; var _DEFAULT_COMPAT = { send_message: [ { 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'] }, { 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'] } ], friend_request: [ { cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' }, { cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' } ], sns_upload: [ { cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }, { cls: 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI' } ], add_friend: [ { cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }, { cls: 'com.tencent.mm.protocal.protobuf.add$ContactType' } ] }; function _parseVersion(v) { var parts = (v || '').split('.').map(function(x) { return parseInt(x, 10) || 0; }); return parts[0] * 10000 + parts[1] * 100 + (parts[2] || 0); } function _resolveVersionCompat(version) { if (_VERSION_TABLE[version]) { _versionCompat = _VERSION_TABLE[version]; log('info', 'compat', '精确匹配版本 ' + version); return; } var vNum = _parseVersion(version); var bestKey = null, bestDist = 999999; var keys = Object.keys(_VERSION_TABLE); for (var i = 0; i < keys.length; i++) { var kNum = _parseVersion(keys[i]); var dist = Math.abs(vNum - kNum); if (dist < bestDist) { bestDist = dist; bestKey = keys[i]; } } if (bestKey && bestDist <= 10) { _versionCompat = _VERSION_TABLE[bestKey]; log('info', 'compat', '近似匹配 ' + version + ' → ' + bestKey + ' (距离=' + bestDist + ')'); } else { _versionCompat = _DEFAULT_COMPAT; log('warn', 'compat', '未匹配版本 ' + version + ',使用 default 全候选'); } } function getCompat(key) { return (_versionCompat && _versionCompat[key]) || (_DEFAULT_COMPAT[key]) || []; } // ============================================================ // § 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 jLong(v) { var Long = Java.use('java.lang.Long'); var s = String(v === undefined || v === null ? '0' : v); try { return Long.parseLong(s); } catch (_) { return Long.valueOf(parseInt(s, 10) || 0); } } 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; } var _lastDbDiag = { ok: false, error: '', path: '', found: false, rows: 0 }; function _emptyReadFailure(kind, diag, force) { var d = diag || _lastDbDiag || {}; var hasDbError = d && (d.ok === false) && (d.error || d.found === false); if (!hasDbError && !force) return null; return { success: false, error: kind + '读取为空:DB 未打开或查询不稳定', count: 0, _diag: d, }; } function _execSQL(sql, dbPathOverride) { var results = []; _lastDbDiag = { ok: false, error: '', path: '', found: false, rows: 0 }; try { var path = dbPathOverride || _getDbPath(); _lastDbDiag.path = path || ''; if (!path) { _lastDbDiag.error = 'db_path 未找到'; return results; } var db = _findOpenWcdb(path); if (!db) { _lastDbDiag.error = '未找到已打开的 WCDB 实例'; log('warn', 'db', '未找到已打开的 WCDB 实例: ' + path); return results; } _lastDbDiag.found = true; // WCDB 1.x: rawQuery(sql, args);2.x core.Database 无 rawQuery → 走 2.x 适配 if (typeof db.rawQuery !== 'function') { return _execSQL2x(db, sql, results); } 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(); _lastDbDiag.ok = true; _lastDbDiag.rows = results.length; } catch (e) { _lastDbDiag.error = String(e); log('warn', 'db', 'SQL 执行失败: ' + e); } return results; } // WCDB 2.x core.Database 原生 SQL 查询适配。 // 2.x 无 rawQuery;通过 getHandle() 取 Handle,再用 PreparedStatement(prepare/step/getText)读取。 // 反射发现可用方法,失败则把方法名写入 _lastDbDiag 供下一轮精确实现(不盲写)。 function _execSQL2x(db, sql, results) { try { if (typeof db.getHandle !== 'function') { _lastDbDiag.error = 'WCDB2x: 实例无 getHandle()'; try { _lastDbDiag.db_methods = _reflectMethodNames(db); } catch (_) {} return results; } var handle = db.getHandle(); _lastDbDiag.has_handle = !!handle; if (!handle || typeof handle.getAllRowsFromSQL !== 'function') { _lastDbDiag.error = 'WCDB2x: handle 无 getAllRowsFromSQL'; try { _lastDbDiag.handle_methods = _reflectMethodNames(handle); } catch (_) {} return results; } // 从 SELECT 子句解析列名(getAllRowsFromSQL 只返回值数组,需自带列名映射) var colNames = _parseSelectColumns(sql); _lastDbDiag.used_prepare = 'getAllRowsFromSQL'; _lastDbDiag.parsed_cols = colNames; // WCDB 2.x:getAllRowsFromSQL(sql) → List>(Value.getText() 取文本) var allRows = handle.getAllRowsFromSQL(sql); if (allRows) { var rowCount = (typeof allRows.size === 'function') ? allRows.size() : allRows.length; for (var ri = 0; ri < rowCount && ri < 2000; ri++) { var rowVals = (typeof allRows.get === 'function') ? allRows.get(ri) : allRows[ri]; var vcount = (rowVals && typeof rowVals.size === 'function') ? rowVals.size() : (rowVals && rowVals.length != null ? rowVals.length : 0); var row = {}; for (var vi = 0; vi < vcount; vi++) { var v = (typeof rowVals.get === 'function') ? rowVals.get(vi) : rowVals[vi]; var txt = null; try { if (v && typeof v.getText === 'function') txt = v.getText(); } catch (_) {} if (txt == null) { try { if (v != null) txt = String(v); } catch (_) {} } row[colNames[vi] || ('c' + vi)] = safeStr(txt); } results.push(row); } } try { if (handle && typeof handle.invalidate === 'function') handle.invalidate(); } catch (_) {} _lastDbDiag.ok = true; _lastDbDiag.rows = results.length; } catch (e) { _lastDbDiag.error = 'WCDB2x: ' + String(e); log('warn', 'db', _lastDbDiag.error); } return results; } // 从 SELECT ... FROM 解析列名/别名,供 getAllRowsFromSQL 的值数组按名映射。 // 仅处理本文件内构造的受控 SQL(无嵌套子查询函数逗号);尽量取 AS 别名或末段标识符。 function _parseSelectColumns(sql) { try { // PRAGMA table_info(X) 列固定,getAllRowsFromSQL 不带列名,需手动映射 if (/^\s*pragma\s+table_info/i.test(sql)) { return ['cid', 'name', 'type', 'notnull', 'dflt_value', 'pk']; } var m = /select\s+([\s\S]*?)\s+from\s/i.exec(sql); if (!m) return []; var seg = m[1]; if (seg.replace(/\s/g, '') === '*') return []; return seg.split(',').map(function (part) { var p = part.trim(); var asm = /\s+as\s+([A-Za-z0-9_]+)\s*$/i.exec(p); if (asm) return asm[1]; // count(*) AS n 已被上分支处理;无别名时取末段标识符 var idm = /([A-Za-z0-9_]+)\s*$/.exec(p); return idm ? idm[1] : p; }); } catch (_) { return []; } } function _reflectMethodNames(obj) { var names = []; try { var cls = obj.getClass(); var methods = cls.getMethods(); for (var i = 0; i < methods.length && i < 120; i++) { try { names.push(methods[i].getName()); } catch (_) {} } } catch (_) {} // 去重 var seen = {}, uniq = []; for (var j = 0; j < names.length; j++) { if (!seen[names[j]]) { seen[names[j]] = 1; uniq.push(names[j]); } } return uniq; } function _reflectMethodSigs(obj, limit) { var sigs = []; try { var cls = obj.getClass(); var methods = cls.getMethods(); var max = limit || 120; for (var i = 0; i < methods.length && i < max; i++) { try { sigs.push(safeStr(methods[i].toString())); } catch (_) {} } } catch (_) {} return sigs; } function _escapeSql(v) { return String(v == null ? '' : v).replace(/'/g, "''"); } /** WCDB 1.x 写库(标签/联系人字段);无 execSQL 时诚实失败 */ function _execSQLWrite(sql, dbPathOverride) { try { var path = dbPathOverride || _getDbPath(); if (!path) return { success: false, error: 'db_path 未找到' }; var db = _findOpenWcdb(path); if (!db) return { success: false, error: '未找到已打开的 WCDB 实例' }; if (typeof db.execSQL !== 'function') { return { success: false, error: '实例无 execSQL(疑 WCDB 2.x),标签写库不可用' }; } db.execSQL(sql); return { success: true }; } catch (e) { return { success: false, error: String(e) }; } } function _nextLabelId() { var rows = _execSQL('SELECT MAX(CAST(labelID AS INTEGER)) AS max_id FROM contactlabel'); if (rows.length && rows[0].max_id) { var n = parseInt(rows[0].max_id, 10); if (!isNaN(n)) return n + 1; } rows = _execSQL('SELECT MAX(CAST(labelId AS INTEGER)) AS max_id FROM contactlabelinfo'); if (rows.length && rows[0].max_id) { var n2 = parseInt(rows[0].max_id, 10); if (!isNaN(n2)) return n2 + 1; } return 1; } function _normalizeLabelIds(raw) { if (!raw) return []; if (Array.isArray(raw)) return raw.map(function (x) { return String(x); }).filter(Boolean); return String(raw).split(',').map(function (x) { return x.trim(); }).filter(Boolean); } function _tryNotifyLabelChanged() { try { var candidates = [ 'com.tencent.mm.plugin.label.a.a', 'com.tencent.mm.plugin.label.a.b', 'com.tencent.mm.storage.bv', ]; for (var i = 0; i < candidates.length; i++) { try { var Cls = Java.use(candidates[i]); if (typeof Cls.a === 'function') { Cls.a(); return true; } if (typeof Cls.b === 'function') { Cls.b(); return true; } } catch (_) {} } } catch (_) {} return false; } // WCDB 实例查找(兼容 8.0.69 WCDB 2.x):多候选类名 + 放宽路径匹配 var _WCDB_CLASS_CANDIDATES = [ 'com.tencent.wcdb.database.SQLiteDatabase', // WCDB 1.x 'com.tencent.wcdb.core.Database', // WCDB 2.x(新版微信) ]; var _wcdbClassResolved = null; function _wcdbInstancePath(instance) { // WCDB 1.x: getPath();2.x: getPath() 或 path 字段 try { if (typeof instance.getPath === 'function') return safeStr(instance.getPath()); } catch (_) {} try { if (instance.path) return safeStr(instance.path.value); } catch (_) {} return ''; } function _wcdbIsOpen(instance) { try { if (typeof instance.isOpen === 'function') return instance.isOpen(); } catch (_) {} try { if (typeof instance.isOpened === 'function') return instance.isOpened(); } catch (_) {} return true; // 2.x 无显式 isOpen 时按打开处理 } // WCDB 实例缓存:Java.choose 全堆扫描在大堆上耗时数十秒,标签读写每次都扫会超时。 // 按 path 缓存已打开实例;命中先校验 isOpen,失效则重扫。显著降低标签操作延迟。 var _wcdbInstCache = {}; function _findOpenWcdb(path) { var normalizedPath = path.replace('/data/data/', '/data/user/0/'); var baseName = path.split('/').pop(); var found = null; // 1) 先查缓存:命中且仍打开则直接复用,避免全堆扫描 var cached = _wcdbInstCache[path]; if (cached) { try { if (_wcdbIsOpen(cached)) return cached; } catch (_) {} try { delete _wcdbInstCache[path]; } catch (_) { _wcdbInstCache[path] = null; } } // 始终遍历全部候选类:不同库分属不同 WCDB 版本(EnMicroMsg=1.x,SnsMicroMsg=2.x), // 不能用单一 _wcdbClassResolved 缓存限制,否则跨版本库找不到。 var classes = _WCDB_CLASS_CANDIDATES; for (var ci = 0; ci < classes.length && !found; ci++) { var cls = classes[ci]; try { Java.use(cls); } catch (_) { continue; } // 类不存在则跳过 try { Java.choose(cls, { onMatch: function (instance) { if (found) return; try { var p = _wcdbInstancePath(instance); var match = (p === path || p === normalizedPath || (baseName && p.indexOf(baseName) >= 0)); if (match && _wcdbIsOpen(instance)) { found = instance; _wcdbClassResolved = cls; _wcdbInstCache[path] = instance; // 缓存供后续复用 } } catch (_) {} }, onComplete: function () {} }); } catch (e) { log('warn', 'db', 'Java.choose 失败 ' + cls + ': ' + e); } } if (!found) log('warn', 'db', '未找到已打开的 WCDB 实例(已试' + classes.length + '类): ' + baseName); return found; } // 诊断:枚举所有 WCDB 实例路径(真机排查 8.0.69 DB 读空) function _diagWcdbInstances() { var out = []; for (var ci = 0; ci < _WCDB_CLASS_CANDIDATES.length; ci++) { var cls = _WCDB_CLASS_CANDIDATES[ci]; try { Java.use(cls); } catch (_) { out.push({cls: cls, exists: false}); continue; } var paths = []; try { Java.choose(cls, { onMatch: function (inst) { try { paths.push({ path: _wcdbInstancePath(inst), open: _wcdbIsOpen(inst) }); } catch (_) {} }, onComplete: function () {} }); } catch (_) {} out.push({ cls: cls, exists: true, instances: paths }); } return out; } function _execSnsSQL(sql) { return _execSQL(sql, _getSnsDbPath()); } // ============================================================ // § 4 rpc.exports — Python 端通过 FridaManager.call_rpc 调用 // ============================================================ rpc.exports = { // ==================== 系统 ==================== ping: function () { return 'pong from wechat_hook_v3.0 — 96 actions / 26 modules'; }, 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; }, // 诊断:枚举真机 WCDB 实例路径(排查 8.0.69 DB 读空) diagWcdb: function () { try { return Java.performNow(function () { var out = { success: true, db_path: _getDbPath(), sns_path: _getSnsDbPath(), instances: _diagWcdbInstances() }; // 实读测试:main 库 rcontact、sns 库 SnsInfo,回传 _lastDbDiag 定位查询错误 try { _execSQL('SELECT count(*) AS n FROM rcontact'); out.main_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e1) { out.main_query = { error: String(e1) }; } try { _execSnsSQL('SELECT count(*) AS n FROM SnsInfo'); out.sns_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e2) { out.sns_query = { error: String(e2) }; } return out; }); } catch (e) { return { success: false, error: String(e) }; } }, // 诊断:运行时反射枚举 8.0.69 朋友圈(SNS)发布/读取相关类与方法 // 用于在混淆版定位 post_moments(sns.model.ay 等价) / get_moments(m3.onGYNetEnd 等价) 真实类名。 diagSns: function (params) { // 安全诊断:**禁用** Java.enumerateLoadedClassesSync(微信 10万+ 类,桥接枚举会阻塞 // agent 事件循环导致 WS 掉线 — 2026-05-30 实测)。改为「试探调用方提供的候选类名」: // params.candidates = ["com.tencent.mm.plugin.sns.model.xx", ...](来自离线 apk/smali 分析) // 对每个候选 Java.use + 反射方法名(最多 8 个,快速、不枚举),定位发/读朋友圈真实类。 var cands = (params && params.candidates) || []; if (!cands || cands.length === 0) { return { success: false, error: 'no_candidates', hint: '运行时全量枚举已禁用(会致 WS 掉线)。请先离线对 8.0.69 apk 跑 jadx/baksmali 取候选类名,再传 params.candidates=[...] 试探。', }; } var lim = Math.min(cands.length, 8); try { return Java.performNow(function () { var out = []; for (var i = 0; i < lim; i++) { var cn = cands[i]; var info = { cls: cn, exists: false, methods: [], ctors: 0, hasCommit: false }; try { var k = Java.use(cn); info.exists = true; var ms = k.class.getDeclaredMethods(); var names = {}; for (var m = 0; m < ms.length; m++) { var mn = ms[m].getName(); names[mn] = 1; if (mn.toLowerCase().indexOf('commit') >= 0) info.hasCommit = true; } info.methods = Object.keys(names).slice(0, 30); info.ctors = k.class.getDeclaredConstructors().length; } catch (e0) { info.err = String(e0); } out.push(info); } return { success: true, tested: lim, classes: out }; }); } catch (e) { return { success: false, error: String(e) }; } }, 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); _resolveVersionCompat(_wechatVersion); return { success: true, version: _wechatVersion }; }); } catch (e) { return { success: false, error: String(e) }; } }, getVersionCompat: function () { return { wechat_version: _wechatVersion, matched: _versionCompat !== _DEFAULT_COMPAT, supported_versions: Object.keys(_VERSION_TABLE), active_compat: _versionCompat ? Object.keys(_versionCompat) : [], }; }, diagSendMessageCandidates: function () { try { return Java.performNow(function () { var candidates = getCompat('send_message'); var rows = []; for (var i = 0; i < candidates.length; i++) { var c = candidates[i]; var row = { cls: c.cls, method: c.method, sig: c.sig || [], class_found: false, method_found: false, overload_found: false, error: '' }; try { var MsgCls = Java.use(c.cls); row.class_found = !!MsgCls; row.method_found = !!(MsgCls && MsgCls[c.method]); if (row.method_found) { if (c.sig && c.sig.length > 0) { try { MsgCls[c.method].overload.apply(MsgCls[c.method], c.sig); row.overload_found = true; } catch (oe) { row.error = String(oe); } } else { row.overload_found = true; } } } catch (e) { row.error = String(e); } rows.push(row); } return { success: true, wechat_version: _wechatVersion, candidate_count: rows.length, candidates: rows }; }); } catch (e) { return { success: false, error: String(e) }; } }, diagSendMsgEvent: function (params) { try { return Java.performNow(function () { var toId = (params && params.to_id) || 'filehelper'; var content = (params && params.content) || '[diag]'; var diag = { success: true, to_id: toId, content_len: content.length, classes: {}, field_set: false, scene: null, error: '' }; try { var SendMsgEvent = Java.use('com.tencent.mm.autogen.events.SendMsgEvent'); Java.use('com.tencent.mm.sdk.event.IEvent'); var event = SendMsgEvent.$new(); var payload = event.g.value; payload.a.value = jStr(toId); payload.b.value = jStr(content); payload.c.value = _calcSendMsgScene(toId); payload.d.value = 0; diag.classes.SendMsgEvent = true; diag.classes.payload = safeStr(payload.getClass().getName()); diag.event_methods = _reflectMethodSigs(event, 80); diag.field_set = true; diag.scene = payload.c.value; } catch (inner) { diag.success = false; diag.error = String(inner); } return diag; }); } catch (e) { return { success: false, error: String(e) }; } }, diagRevokeMsgEvent: function (params) { try { return Java.performNow(function () { var msgSvrId = (params && params.msg_svr_id) || ''; var diag = { success: true, msg_svr_id: msgSvrId, classes: {}, payload_fields: [], row: null }; try { if (msgSvrId) { var _id = String(msgSvrId).replace(/'/g, "''"); var sql = "SELECT msgId, msgSvrId, talker, type, content, isSend, createTime FROM message" + " WHERE msgSvrId = '" + _id + "' OR msgId = '" + _id + "' LIMIT 1"; var rows = _execSQL(sql); if (rows && rows.length > 0) diag.row = rows[0]; } var RevokeMsgEvent = Java.use('com.tencent.mm.autogen.events.RevokeMsgEvent'); diag.classes.RevokeMsgEvent = true; var event = RevokeMsgEvent.$new(); diag.event_methods = _reflectMethodSigs(event, 40); var payload = event.g.value; diag.payload_class = safeStr(payload.getClass().getName()); try { var cls = payload.getClass(); var fields = cls.getDeclaredFields(); for (var i = 0; i < fields.length && i < 40; i++) { fields[i].setAccessible(true); var name = fields[i].getName(); var ftype = safeStr(fields[i].getType().getName()); var val = null; try { val = fields[i].get(payload); } catch (_) { val = '?'; } diag.payload_fields.push({ name: name, type: ftype, sample: safeStr(val) }); } } catch (fe) { diag.field_error = String(fe); } } catch (inner) { diag.success = false; diag.error = String(inner); } return diag; }); } 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) || 3000; var offset = (params && params.offset) || 0; var conversationId = (params && params.conversation_id) || ''; var dbRes = null; try { dbRes = Java.performNow(function () { return _getMessagesFromDB(conversationId, limit, offset); }); } catch (e) { dbRes = { success: false, error: String(e), messages: [] }; } // DB 读为空时 fallback 到 hook 实时缓冲(insert hook 已拦截新消息) if (!dbRes || !dbRes.messages || dbRes.messages.length === 0) { offset = parseInt(offset, 10) || 0; if (offset < 0) offset = 0; var filteredBuffer = _messageBuffer.filter(function (m) { return !conversationId || m.from_id === conversationId; }); var start = Math.max(filteredBuffer.length - offset - limit, 0); var end = offset > 0 ? filteredBuffer.length - offset : filteredBuffer.length; var buf = filteredBuffer.slice(start, end); if (buf.length > 0) { return { success: true, messages: buf.map(function (m) { return { id: m.msg_svr_id, type: m.msg_type, from_id: m.from_id, content: m.content, timestamp: m.timestamp, is_send: m.is_send }; }), count: buf.length, returned_count: buf.length, requested_limit: limit, offset: offset, total_count: filteredBuffer.length, has_more: filteredBuffer.length > offset + buf.length, source: 'hook_buffer', db_diag: _lastDbDiag, }; } if (dbRes) dbRes.db_diag = _lastDbDiag; } return dbRes; }, getRecentMessages: function (params) { var limit = (params && params.limit) || 3000; 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) || 10000; var offset = (params && params.offset) || 0; try { return Java.performNow(function () { return _getContactsFromDB(limit, offset); }); } 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; var offset = (params && params.offset) || 0; try { return Java.performNow(function () { return _getGroupsFromDB(limit, offset); }); } 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: '朋友圈内容不能为空' }; var autoSend = !(params && params.auto_send === false); try { return Java.performNow(function () { return _postMomentsInternal(content, imageUrls, autoSend); }); } 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) }; } }, deleteMoments: function (params) { var snsId = (params && params.sns_id) || ''; if (!snsId) return { success: false, error: '缺少 sns_id' }; return _intentAction('com.workphone.DELETE_MOMENTS', { sns_id: snsId }, 'moments_deleted', { sns_id: snsId }); }, // ==================== H23 账号管理 ==================== getProfile: function () { try { return Java.performNow(function () { var sql = "SELECT value FROM userinfo WHERE id=4"; var rows = _execSQL(sql); var nickname = rows.length > 0 ? rows[0].value : ''; var sql2 = "SELECT value FROM userinfo WHERE id=2"; var rows2 = _execSQL(sql2); var wxid = rows2.length > 0 ? rows2[0].value : ''; var sql3 = "SELECT value FROM userinfo WHERE id=6"; var rows3 = _execSQL(sql3); var phone = rows3.length > 0 ? rows3[0].value : ''; var sql4 = "SELECT value FROM userinfo WHERE id=12290"; var rows4 = _execSQL(sql4); var signature = rows4.length > 0 ? rows4[0].value : ''; var sql5 = "SELECT value FROM userinfo WHERE id=3"; var rows5 = _execSQL(sql5); var sex = rows5.length > 0 ? rows5[0].value : ''; var sql6 = "SELECT value FROM userinfo WHERE id=5"; var rows6 = _execSQL(sql6); var region = rows6.length > 0 ? rows6[0].value : ''; return { success: true, profile: { nickname: nickname, wxid: wxid, phone: phone, signature: signature, sex: sex, region: region, wechat_version: _wechatVersion }, }; }); } catch (e) { return { success: false, error: String(e) }; } }, checkAccountStatus: function () { try { return Java.performNow(function () { var ctx = getCtx(); var pm = ctx.getPackageManager(); var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0); var sql = "SELECT value FROM userinfo WHERE id=2"; var rows = _execSQL(sql); return { success: true, status: 'active', nickname: rows.length > 0 ? rows[0].value : '', version: safeStr(info.versionName.value), pid: Process.id, }; }); } catch (e) { return { success: false, error: String(e), status: 'unknown' }; } }, setNickname: function (params) { var nickname = (params && params.nickname) || ''; if (!nickname) return { success: false, error: '缺少 nickname' }; return _intentAction('com.workphone.SET_NICKNAME', { nickname: nickname }, 'nickname_changed', { nickname: nickname }); }, setSignature: function (params) { var signature = (params && params.signature) || ''; return _intentAction('com.workphone.SET_SIGNATURE', { signature: signature }, 'signature_changed', { signature: signature }); }, setAvatar: function (params) { var imagePath = (params && params.image_path) || ''; if (!imagePath) return { success: false, error: '缺少 image_path' }; return _intentAction('com.workphone.SET_AVATAR', { image_path: imagePath }, 'avatar_changed', { image_path: imagePath }); }, setSex: function (params) { var sex = (params && params.sex) || '1'; return _intentAction('com.workphone.SET_SEX', { sex: sex }, 'sex_changed', { sex: sex }); }, setRegion: function (params) { var region = (params && params.region) || ''; if (!region) return { success: false, error: '缺少 region' }; return _intentAction('com.workphone.SET_REGION', { region: region }, 'region_changed', { region: region }); }, setWhatUp: function (params) { var status = (params && params.status) || ''; return _intentAction('com.workphone.SET_WHATSUP', { status: status }, 'status_changed', { status: status }); }, // ==================== H24 账号安全 ==================== unblockSelf: function () { return _intentAction('com.workphone.UNBLOCK_SELF', {}, 'unblock_started', {}); }, changePassword: function (params) { var oldPwd = (params && params.old_password) || ''; var newPwd = (params && params.new_password) || ''; if (!oldPwd || !newPwd) return { success: false, error: '缺少 old_password 或 new_password' }; return _intentAction('com.workphone.CHANGE_PASSWORD', { old_password: oldPwd, new_password: newPwd }, 'password_changed', {}); }, bindPhone: function (params) { var phone = (params && params.phone) || ''; if (!phone) return { success: false, error: '缺少 phone' }; return _intentAction('com.workphone.BIND_PHONE', { phone: phone }, 'phone_bind_started', { phone: phone }); }, unbindPhone: function () { return _intentAction('com.workphone.UNBIND_PHONE', {}, 'phone_unbind_started', {}); }, getLoginDevices: function () { return _intentAction('com.workphone.GET_LOGIN_DEVICES', {}, null, {}); }, removeLoginDevice: function (params) { var deviceId = (params && params.device_id) || ''; if (!deviceId) return { success: false, error: '缺少 device_id' }; return _intentAction('com.workphone.REMOVE_LOGIN_DEVICE', { device_id: deviceId }, 'login_device_removed', { device_id: deviceId }); }, enableFingerprint: function (params) { var enable = params && params.enable !== undefined ? params.enable : true; return _intentAction('com.workphone.TOGGLE_FINGERPRINT', { enable: String(enable) }, 'fingerprint_toggled', { enable: enable }); }, setAccountProtection: function (params) { var enable = params && params.enable !== undefined ? params.enable : true; return _intentAction('com.workphone.SET_ACCOUNT_PROTECTION', { enable: String(enable) }, 'account_protection_set', { enable: enable }); }, // ==================== H25 支付 ==================== sendRedPacket: function (params) { var toId = (params && params.to_id) || ''; var amount = (params && params.amount) || ''; var message = (params && params.message) || '恭喜发财'; if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' }; return _intentAction('com.workphone.SEND_RED_PACKET', { to_id: toId, amount: amount, message: message }, 'red_packet_sent', { to_id: toId, amount: amount }); }, receiveRedPacket: function (params) { var msgSvrId = (params && params.msg_svr_id) || ''; if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' }; return _intentAction('com.workphone.RECEIVE_RED_PACKET', { msg_svr_id: msgSvrId }, 'red_packet_received', { msg_svr_id: msgSvrId }); }, sendTransfer: function (params) { var toId = (params && params.to_id) || ''; var amount = (params && params.amount) || ''; var desc = (params && params.description) || ''; if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' }; return _intentAction('com.workphone.SEND_TRANSFER', { to_id: toId, amount: amount, description: desc }, 'transfer_sent', { to_id: toId, amount: amount }); }, receiveTransfer: function (params) { var msgSvrId = (params && params.msg_svr_id) || ''; if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' }; return _intentAction('com.workphone.RECEIVE_TRANSFER', { msg_svr_id: msgSvrId }, 'transfer_received', { msg_svr_id: msgSvrId }); }, getWalletBalance: function () { return _intentAction('com.workphone.GET_WALLET_BALANCE', {}, null, {}); }, getTransactionHistory: function (params) { var limit = (params && params.limit) || 20; return _intentAction('com.workphone.GET_TRANSACTIONS', { limit: String(limit) }, null, {}); }, showPaymentCode: function () { return _intentAction('com.workphone.SHOW_PAYMENT_CODE', {}, null, {}); }, getSafetyCenter: function () { try { return Java.performNow(function () { var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制']; var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200'; var rows = _execSQL(sql); var hits = []; for (var i = 0; i < rows.length; i++) { var c = String(rows[i].content || ''); for (var j = 0; j < kws.length; j++) { if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]); } } var bindMobile = ''; try { var ui = _execSQL('SELECT value FROM userinfo WHERE type=4 LIMIT 1'); if (ui.length) bindMobile = String(ui[0].value || ''); } catch (_) {} return { success: true, channel: 'frida_db', restricted: hits.length > 0, restrictions: hits, bind_mobile_masked: bindMobile ? bindMobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '', note: '账号安全摘要(DB+消息关键词,非 UI 导航)', }; }); } catch (e) { return { success: false, error: String(e) }; } }, checkRestrictions: function () { try { return Java.performNow(function () { var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制']; var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200'; var rows = _execSQL(sql); var hits = []; for (var i = 0; i < rows.length; i++) { var c = String(rows[i].content || ''); for (var j = 0; j < kws.length; j++) { if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]); } } return { success: true, restricted: hits.length > 0, restrictions: hits }; }); } catch (e) { return { success: false, error: String(e), restricted: false, restrictions: [] }; } }, getTopStories: function () { var limit = 5; try { return Java.performNow(function () { var stories = []; var probes = [ 'SELECT title,url,pubTime FROM ReaderAppInfo ORDER BY rowid DESC LIMIT ' + limit, 'SELECT nickname,username FROM bizinfo WHERE type=1 LIMIT ' + limit, 'SELECT content,createTime FROM message WHERE type=49 ORDER BY createTime DESC LIMIT ' + limit, ]; for (var p = 0; p < probes.length && stories.length === 0; p++) { try { var rows = _execSQL(probes[p]); for (var i = 0; i < rows.length; i++) { stories.push(rows[i]); } } catch (_) {} } return { success: true, channel: 'frida_db', stories: stories, count: stories.length, note: stories.length ? '订阅号/ReaderApp DB 摘要' : '无 top_stories 可读行(版本/账号差异)', }; }); } catch (e) { return { success: false, error: String(e), stories: [] }; } }, getWechatSteps: function () { try { return Java.performNow(function () { var steps = 0; var detail = []; var sportPath = null; try { var File = Java.use('java.io.File'); var dir = File.$new('/data/data/com.tencent.mm/MicroMsg/'); if (dir.exists()) { var subs = dir.listFiles(); if (subs) { for (var i = 0; i < subs.length; i++) { var cand = subs[i].getAbsolutePath() + '/sportrecord.db'; if (File.$new(cand).exists()) { sportPath = cand; break; } } } } } catch (_) {} if (sportPath) { var rows = _execSQL('SELECT * FROM SportRecordInfo ORDER BY rowid DESC LIMIT 1', sportPath); if (rows.length) { var r0 = rows[0]; steps = parseInt(r0.step || r0.steps || r0.Step || '0', 10) || 0; detail = rows; } } if (!steps) { try { var alt = _execSQL("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%sport%'"); if (alt.length) detail = alt; } catch (_) {} } return { success: true, channel: 'frida_db', steps: steps, records: detail, sport_db: sportPath || '', note: steps ? 'sportrecord.db 最近步数' : '步数为 0 或未开启微信运动(诚实返回)', }; }); } catch (e) { return { success: false, error: String(e), steps: 0 }; } }, getStickerList: function (params) { // 真实现:读 EnMicroMsg.db 的 EmojiInfo 表(自定义表情);动态列探测兼容 8.0.x; // 表不存在/无自定义表情时诚实返回空,不假成功(真机铁律 #3/#14)。 var limit = (params && params.limit) || 100; try { return Java.performNow(function () { var cols; try { cols = _execSQL("PRAGMA table_info(EmojiInfo)"); } catch (e0) { cols = []; } if (!cols || cols.length === 0) { return { success: true, stickers: [], count: 0, channel: 'frida_db', note: 'EmojiInfo 表不存在或无自定义表情(版本/账号差异)' }; } var names = cols.map(function (c) { return c.name; }); var want = ['md5', 'catalog', 'name', 'type', 'size', 'cdnUrl', 'groupId', 'reserved4']; var sel = want.filter(function (w) { return names.indexOf(w) >= 0; }); if (sel.length === 0) sel = names.slice(0, 6); var rows = _execSQL("SELECT " + sel.join(',') + " FROM EmojiInfo LIMIT " + limit); return { success: true, channel: 'frida_db', columns: sel, stickers: rows.map(function (r) { return { md5: r.md5, name: r.name, catalog: r.catalog, type: r.type, size: r.size, cdn_url: r.cdnUrl || r.reserved4 || '', group_id: r.groupId }; }), count: rows.length, }; }); } catch (e) { return { success: false, error: String(e), stickers: [] }; } }, likeWechatSteps: function (params) { var userId = (params && params.user_id) || ''; return _intentAction('com.workphone.LIKE_WECHAT_STEPS', { user_id: userId }, 'steps_liked', { user_id: userId }); }, clearCache: function () { return _intentAction('com.workphone.CLEAR_CACHE', {}, 'cache_cleared', {}); }, checkForUpdate: function () { return _intentAction('com.workphone.CHECK_UPDATE', {}, 'update_checked', {}); }, // ==================== H26 二维码 ==================== scanQrCode: function (params) { var imagePath = (params && params.image_path) || ''; if (!imagePath) { return { success: false, error: 'missing_image_path', hint: 'extract_qr_from_image 需 image_path;或走 u2 相册导航(hook_only=0)', channel: 'frida_rpc', }; } return _intentAction('com.workphone.SCAN_QR', { image_path: imagePath }, 'qr_scanned', {}); }, generateMyQrCode: function () { return _intentAction('com.workphone.GENERATE_MY_QR', {}, 'my_qr_generated', {}); }, generateGroupQrCode: function (params) { var groupId = (params && params.group_id) || ''; if (!groupId) return { success: false, error: '缺少 group_id' }; return _intentAction('com.workphone.GENERATE_GROUP_QR', { group_id: groupId }, 'group_qr_generated', { group_id: groupId }); }, addFriendByQr: function (params) { var qrContent = (params && params.qr_content) || ''; if (!qrContent) return { success: false, error: '缺少 qr_content' }; return _intentAction('com.workphone.ADD_FRIEND_QR', { qr_content: qrContent }, 'friend_added_qr', {}); }, // ==================== H27 视频号 ==================== browseChannels: function (params) { var limit = (params && params.limit) || 10; return _intentAction('com.workphone.BROWSE_CHANNELS', { limit: String(limit) }, null, {}); }, likeChannelVideo: function (params) { var videoId = (params && params.video_id) || ''; if (!videoId && params && params.index !== undefined) { videoId = 'index_' + params.index; } if (!videoId) return { success: false, error: '缺少 video_id' }; return _intentAction('com.workphone.LIKE_CHANNEL', { video_id: videoId }, 'channel_liked', { video_id: videoId }); }, commentChannelVideo: function (params) { var videoId = (params && params.video_id) || ''; var comment = (params && params.comment) || ''; if (!videoId || !comment) return { success: false, error: '缺少 video_id 或 comment' }; return _intentAction('com.workphone.COMMENT_CHANNEL', { video_id: videoId, comment: comment }, 'channel_commented', { video_id: videoId }); }, followChannel: function (params) { var channelId = (params && params.channel_id) || ''; if (!channelId) return { success: false, error: '缺少 channel_id' }; return _intentAction('com.workphone.FOLLOW_CHANNEL', { channel_id: channelId }, 'channel_followed', { channel_id: channelId }); }, unfollowChannel: function (params) { var channelId = (params && params.channel_id) || ''; if (!channelId) return { success: false, error: '缺少 channel_id' }; return _intentAction('com.workphone.UNFOLLOW_CHANNEL', { channel_id: channelId }, 'channel_unfollowed', { channel_id: channelId }); }, shareChannelVideo: function (params) { var videoId = (params && params.video_id) || ''; var toId = (params && params.to_id) || ''; if (!videoId || !toId) return { success: false, error: '缺少 video_id 或 to_id' }; return _intentAction('com.workphone.SHARE_CHANNEL', { video_id: videoId, to_id: toId }, 'channel_shared', { video_id: videoId, to_id: toId }); }, // ==================== H28 标签管理 ==================== getLabels: function (params) { var limit = (params && params.limit) || 5000; var offset = (params && params.offset) || 0; try { return Java.performNow(function () { return _getLabelsFromDB(limit, offset); }); } catch (e) { return { success: false, error: String(e), labels: [] }; } }, createLabel: function (params) { var name = (params && params.name) || ''; if (!name) return { success: false, error: '缺少 name' }; try { return Java.performNow(function () { var nextId = _nextLabelId(); var esc = _escapeSql(name); var idStr = String(nextId); var wr = _execSQLWrite( "INSERT INTO contactlabel (labelID, labelName) VALUES ('" + idStr + "', '" + esc + "')" ); if (!wr.success) { wr = _execSQLWrite( "INSERT INTO contactlabelinfo (labelId, labelName) VALUES ('" + idStr + "', '" + esc + "')" ); } if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_insert' }; _tryNotifyLabelChanged(); return { success: true, label_id: idStr, name: name, method: 'wcdb_insert' }; }); } catch (e) { return { success: false, error: String(e) }; } }, deleteLabel: function (params) { var labelId = (params && (params.label_id || params.labelId)) || ''; if (!labelId) return { success: false, error: '缺少 label_id' }; try { return Java.performNow(function () { var escId = _escapeSql(labelId); var wr = _execSQLWrite("DELETE FROM contactlabel WHERE labelID='" + escId + "'"); if (!wr.success) { wr = _execSQLWrite("DELETE FROM contactlabelinfo WHERE labelId='" + escId + "'"); } if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_delete' }; _execSQLWrite( "UPDATE rcontact SET contactLabelIds=REPLACE(REPLACE(REPLACE(contactLabelIds,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE contactLabelIds LIKE '%" + escId + "%'" ); _execSQLWrite( "UPDATE rcontact SET labelidlist=REPLACE(REPLACE(REPLACE(labelidlist,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE labelidlist LIKE '%" + escId + "%'" ); _tryNotifyLabelChanged(); return { success: true, label_id: String(labelId), method: 'wcdb_delete' }; }); } catch (e) { return { success: false, error: String(e) }; } }, setContactLabel: function (params) { var wxid = (params && params.wxid) || ''; var labelIds = _normalizeLabelIds((params && params.label_ids) || (params && params.labelIds) || []); if (!wxid) return { success: false, error: '缺少 wxid' }; try { return Java.performNow(function () { var escWxid = _escapeSql(wxid); var joined = labelIds.join(','); var escJoined = _escapeSql(joined); var wr = _execSQLWrite( "UPDATE rcontact SET contactLabelIds='" + escJoined + "' WHERE username='" + escWxid + "'" ); if (!wr.success) { wr = _execSQLWrite( "UPDATE rcontact SET labelidlist='" + escJoined + "' WHERE username='" + escWxid + "'" ); } if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_update' }; _tryNotifyLabelChanged(); return { success: true, wxid: wxid, label_ids: labelIds, method: 'wcdb_update' }; }); } catch (e) { return { success: false, error: String(e) }; } }, getContactsByLabel: function (params) { var labelId = (params && params.label_id) || ''; if (!labelId) return { success: false, error: '缺少 label_id' }; try { return Java.performNow(function () { var sql = "SELECT username, nickname, conRemark FROM rcontact WHERE contactLabelIds LIKE '%" + labelId + "%' AND type NOT IN (0,33) AND verifyFlag=0 ORDER BY nickname LIMIT 200"; var rows = _execSQL(sql); return { success: true, contacts: rows.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }), count: rows.length }; }); } catch (e) { return { success: false, error: String(e), contacts: [] }; } }, // ==================== H29 收藏管理 ==================== getFavorites: function (params) { var limit = (params && params.limit) || 50; try { return Java.performNow(function () { var names = ['Favorite.db', 'favorite.db', 'EnFavorite.db']; var favPath = null; for (var i = 0; i < names.length; i++) { favPath = _findDbPath(names[i]); if (favPath) break; } if (!favPath) { var sql2 = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit; var rows2 = _execSQL(sql2); if (rows2.length > 0) return { success: true, favorites: rows2, count: rows2.length, source: 'main_db' }; return { success: true, favorites: [], count: 0, note: 'Favorite 表不在独立数据库中,主库也未找到' }; } var sql = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit; var rows = _execSQL(sql, favPath); return { success: true, favorites: rows, count: rows.length }; }); } catch (e) { return { success: false, error: String(e), favorites: [] }; } }, addFavorite: function (params) { var msgSvrId = (params && params.msg_svr_id) || ''; var type = (params && params.type) || 'message'; var content = (params && params.content) || (params && params.content_desc) || ''; if (!msgSvrId && content) { return _intentAction('com.workphone.ADD_FAVORITE_TEXT', { content: content, type: type }, 'favorite_added', { content: content }); } if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id 或 content' }; return _intentAction('com.workphone.ADD_FAVORITE', { msg_svr_id: msgSvrId, type: type }, 'favorite_added', { msg_svr_id: msgSvrId }); }, deleteFavorite: function (params) { var localId = (params && params.local_id) || ''; if (!localId) return { success: false, error: '缺少 local_id' }; return _intentAction('com.workphone.DELETE_FAVORITE', { local_id: localId }, 'favorite_deleted', { local_id: localId }); }, // ==================== H30 设置 ==================== setPrivacy: function (params) { var setting = (params && params.setting) || ''; var value = params && params.value !== undefined ? params.value : true; if (!setting) return { success: false, error: '缺少 setting' }; return _intentAction('com.workphone.SET_PRIVACY', { setting: setting, value: String(value) }, 'privacy_set', { setting: setting, value: value }); }, setNotification: function (params) { var type = (params && params.type) || 'all'; var enable = params && params.enable !== undefined ? params.enable : true; return _intentAction('com.workphone.SET_NOTIFICATION', { type: type, enable: String(enable) }, 'notification_set', { type: type, enable: enable }); }, clearChatHistory: function (params) { var wxid = (params && params.wxid) || ''; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.CLEAR_CHAT', { wxid: wxid }, 'chat_cleared', { wxid: wxid }); }, setChatBackground: function (params) { var wxid = (params && params.wxid) || ''; var imagePath = (params && params.image_path) || ''; return _intentAction('com.workphone.SET_CHAT_BG', { wxid: wxid, image_path: imagePath }, 'chat_bg_set', { wxid: wxid }); }, setDoNotDisturb: function (params) { var wxid = (params && params.wxid) || ''; var enable = params && params.enable !== undefined ? params.enable : true; if (!wxid) { return _intentAction('com.workphone.SET_GLOBAL_DND', { enable: String(enable) }, 'dnd_set', { enable: enable }); } return _intentAction('com.workphone.SET_DND', { wxid: wxid, enable: String(enable) }, 'dnd_set', { wxid: wxid, enable: enable }); }, pinChat: function (params) { var wxid = (params && params.wxid) || ''; var pin = params && params.pin !== undefined ? params.pin : true; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.PIN_CHAT', { wxid: wxid, pin: String(pin) }, 'chat_pinned', { wxid: wxid, pin: pin }); }, // ==================== H31 搜索 ==================== globalSearch: function (params) { var keyword = (params && params.keyword) || ''; var limit = (params && params.limit) || 30; if (!keyword) return { success: false, error: '缺少 keyword' }; try { return Java.performNow(function () { var kw = keyword.replace(/'/g, "''"); var contactSql = "SELECT username, nickname, conRemark FROM rcontact WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%') AND type NOT IN (0,33) AND verifyFlag=0 LIMIT " + limit; var contacts = _execSQL(contactSql); var msgSql = "SELECT msgId, talker, content, createTime FROM message WHERE content LIKE '%" + kw + "%' ORDER BY createTime DESC LIMIT " + limit; var messages = _execSQL(msgSql); return { success: true, contacts: contacts.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }), messages: messages.map(function (r) { return { id: r.msgId, from_id: r.talker, content: r.content, timestamp: r.createTime }; }), }; }); } catch (e) { return { success: false, error: String(e) }; } }, // ==================== H32 小程序 ==================== openMiniProgram: function (params) { var appId = (params && params.app_id) || (params && params.name) || ''; var path = (params && params.path) || ''; if (!appId) return { success: false, error: '缺少 app_id' }; return _intentAction('com.workphone.OPEN_MINI_PROGRAM', { app_id: appId, path: path }, 'mini_program_opened', { app_id: appId }); }, getRecentMiniPrograms: function () { return _intentAction('com.workphone.GET_RECENT_MINI_PROGRAMS', {}, null, {}); }, shareMiniProgram: function (params) { var appId = (params && params.app_id) || ''; var toId = (params && params.to_id) || ''; var title = (params && params.title) || ''; if (!appId || !toId) return { success: false, error: '缺少 app_id 或 to_id' }; return _intentAction('com.workphone.SHARE_MINI_PROGRAM', { app_id: appId, to_id: toId, title: title }, 'mini_program_shared', { app_id: appId, to_id: toId }); }, // ==================== H33 文件传输 ==================== sendImage: function (params) { var toId = (params && params.to_id) || ''; var imagePath = (params && params.image_path) || ''; if (!toId || !imagePath) return { success: false, error: '缺少 to_id 或 image_path' }; try { return Java.performNow(function () { return _shareMediaToWechat(imagePath, 'image/*', toId, 'image'); }); } catch (e) { return { success: false, error: String(e) }; } }, sendVideo: function (params) { var toId = (params && params.to_id) || ''; var videoPath = (params && params.video_path) || ''; if (!toId || !videoPath) return { success: false, error: '缺少 to_id 或 video_path' }; try { return Java.performNow(function () { return _shareMediaToWechat(videoPath, 'video/*', toId, 'video'); }); } catch (e) { return { success: false, error: String(e) }; } }, sendFile: function (params) { var toId = (params && params.to_id) || ''; var filePath = (params && params.file_path) || ''; if (!toId || !filePath) return { success: false, error: '缺少 to_id 或 file_path' }; try { return Java.performNow(function () { return _shareMediaToWechat(filePath, '*/*', toId, 'file'); }); } catch (e) { return { success: false, error: String(e) }; } }, sendVoice: function (params) { var toId = (params && params.to_id) || (params && params.user_id) || ''; var voicePath = (params && params.voice_path) || ''; var duration = (params && params.duration) || 0; if (!toId) return { success: false, error: '缺少 to_id' }; if (!voicePath && duration) { return _intentAction('com.workphone.SEND_VOICE_RECORD', { to_id: toId, duration: String(duration) }, 'voice_sent', { to_id: toId, duration: duration }); } if (!voicePath) return { success: false, error: '缺少 voice_path' }; return _intentAction('com.workphone.SEND_VOICE', { to_id: toId, voice_path: voicePath, duration: String(duration) }, 'voice_sent', { to_id: toId }); }, sendLocation: function (params) { var toId = (params && params.to_id) || ''; var lat = (params && params.latitude) || ''; var lng = (params && params.longitude) || ''; var label = (params && params.label) || ''; if (!toId || !lat || !lng) return { success: false, error: '缺少 to_id/latitude/longitude' }; return _intentAction('com.workphone.SEND_LOCATION', { to_id: toId, latitude: lat, longitude: lng, label: label }, 'location_sent', { to_id: toId }); }, sendCard: function (params) { var toId = (params && params.to_id) || ''; var cardWxid = (params && params.card_wxid) || ''; if (!toId || !cardWxid) return { success: false, error: '缺少 to_id 或 card_wxid' }; return _intentAction('com.workphone.SEND_CARD', { to_id: toId, card_wxid: cardWxid }, 'card_sent', { to_id: toId, card_wxid: cardWxid }); }, sendLink: function (params) { var toId = (params && params.to_id) || ''; var url = (params && params.url) || ''; var title = (params && params.title) || ''; var desc = (params && params.description) || ''; if (!toId || !url) return { success: false, error: '缺少 to_id 或 url' }; var _linkText = (title ? title + '\n' : '') + (desc ? desc + '\n' : '') + url; return _shareTextToWechat(_linkText, toId); }, // ==================== H34 消息转发 ==================== forwardMessage: function (params) { var msgSvrId = (params && params.msg_svr_id) || ''; var toId = (params && params.to_id) || ''; if (!msgSvrId || !toId) return { success: false, error: '缺少 msg_svr_id 或 to_id' }; // 真 Frida 转发:文本类(type=1)从 DB 取原文 → 复用已验证 _sendMessageInternal(SendMsgEvent + DB 回查)。 // 注意:_intentAction 内部自带 Java.performNow,禁止嵌套调用(会死锁挂起);故降级在 performNow 外执行。 var res = Java.performNow(function () { try { // 调用方可能传服务端 msgSvrId,也可能传 get_messages 返回的本地 msgId,两者都匹配。 var _id = String(msgSvrId).replace(/'/g, "''"); var sql = "SELECT type, content FROM message WHERE msgSvrId = '" + _id + "' OR msgId = '" + _id + "' LIMIT 1"; var rows = _execSQL(sql); // 文本类:WeChat type=1 为标准文本;本机 filehelper/SendMsgEvent 落库可能为 0。 // 仅当 type ∈ {0,1} 且 content 为非空纯文本(不以媒体 XML 前缀开头)才文本重发,避免误转媒体。 var _t = rows && rows.length > 0 ? String(rows[0].type) : ''; var _c = rows && rows.length > 0 ? (rows[0].content || '') : ''; var _isText = (_t === '1' || _t === '0') && _c && _c.charAt(0) !== '<'; if (_isText) { var r = _sendMessageInternal(toId, _c, 'text'); if (r && r.success) { r.forwarded_from = msgSvrId; r.forward_kind = 'text_resend'; emitEvent('message_forwarded', { msg_svr_id: msgSvrId, to_id: toId, kind: 'text_resend' }); return r; } } return { _fallback: true }; } catch (e) { return { _fallback: true, _err: String(e) }; } }); if (res && res._fallback) { // 非文本/DB 未命中/真发送未验证 → companion intent 降级(在 performNow 外,避免嵌套死锁) return _intentAction('com.workphone.FORWARD_MESSAGE', { msg_svr_id: msgSvrId, to_id: toId }, 'message_forwarded', { msg_svr_id: msgSvrId, to_id: toId, kind: 'non_text_or_db_miss' }); } return res; }, forwardMultiple: function (params) { var msgSvrIds = (params && params.msg_svr_ids) || []; var toId = (params && params.to_id) || ''; if (msgSvrIds.length === 0 || !toId) return { success: false, error: '缺少 msg_svr_ids 或 to_id' }; // 真 Frida 批量转发:逐条复用文本转发真实路径(DB 取原文 → _sendMessageInternal)。 // 文本条真发送成功才计 forwarded;非文本/未验证条诚实计入 fallback,不伪造整体成功。 // 注意:_intentAction 自带 Java.performNow,降级须在 performNow 外执行(避免嵌套死锁挂起)。 var agg = Java.performNow(function () { var forwarded = 0, fallback = 0, details = []; for (var i = 0; i < msgSvrIds.length; i++) { var id = String(msgSvrIds[i]); try { var sql = "SELECT type, content FROM message WHERE msgSvrId = '" + id.replace(/'/g, "''") + "' LIMIT 1"; var rows = _execSQL(sql); if (rows && rows.length > 0 && (rows[0].type === 1 || String(rows[0].type) === '1') && rows[0].content) { var res = _sendMessageInternal(toId, rows[0].content, 'text'); if (res && res.success) { forwarded++; details.push({ msg_svr_id: id, kind: 'text_resend', success: true }); continue; } } } catch (e) { /* 落入 fallback */ } fallback++; details.push({ msg_svr_id: id, kind: 'non_text_or_db_miss', success: false }); } return { forwarded: forwarded, fallback: fallback, details: details }; }); if (agg.forwarded > 0) { return { success: true, forwarded: agg.forwarded, fallback: agg.fallback, total: msgSvrIds.length, details: agg.details }; } // 无一条真发送成功 → 整体降级 intent(performNow 外,避免嵌套死锁) return _intentAction('com.workphone.FORWARD_MULTIPLE', { msg_svr_ids: msgSvrIds.join(','), to_id: toId }, 'messages_forwarded', { count: msgSvrIds.length, to_id: toId, forwarded: 0, fallback: agg.fallback }); }, revokeMessage: function (params) { var msgSvrId = (params && params.msg_svr_id) || ''; if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' }; // 撤回资格预检(DB · 复用已验证 _execSQL):消息须存在、为本人发送(isSend=1)、且在 2 分钟窗口内。 // 不满足直接诚实返回原因,避免对不可撤回消息发 intent 假成功。真撤回 NetScene 仍待真机实现。 // 注意:_intentAction 自带 Java.performNow,资格通过后的 intent 须在 performNow 外执行(避免嵌套死锁)。 var pre = Java.performNow(function () { try { var _rid = String(msgSvrId).replace(/'/g, "''"); var sql = "SELECT isSend, createTime FROM message WHERE msgSvrId = '" + _rid + "' OR msgId = '" + _rid + "' LIMIT 1"; var rows = _execSQL(sql); if (!rows || rows.length === 0) { return { ok: false, result: { success: false, error: '消息不存在或已删除', error_code: 'msg_not_found', msg_svr_id: msgSvrId } }; } var row = rows[0]; if (String(row.isSend) !== '1') { return { ok: false, result: { success: false, error: '仅可撤回本人发送的消息', error_code: 'not_own_message', msg_svr_id: msgSvrId } }; } var createMs = parseInt(row.createTime, 10) || 0; var ageMs = Date.now() - createMs; if (createMs > 0 && ageMs > 120000) { return { ok: false, result: { success: false, error: '超过 2 分钟撤回窗口', error_code: 'revoke_window_expired', age_seconds: Math.round(ageMs / 1000), msg_svr_id: msgSvrId } }; } return { ok: true, row: row }; } catch (e) { return { ok: false, result: { success: false, error: String(e), msg_svr_id: msgSvrId } }; } }); if (!pre.ok) return pre.result; // 真 Frida 撤回:RevokeMsgEvent + IEvent.e(8.0.x autogen,参照 SendMsgEvent) var fridaRes = Java.performNow(function () { return _revokeMessageByRevokeMsgEvent(msgSvrId, pre.row); }); if (fridaRes && fridaRes.success && fridaRes.verified) { emitEvent('message_revoked', { msg_svr_id: msgSvrId, method: 'revoke_msg_event' }); return fridaRes; } // 真 RPC 未 DB 确认 → 诚实返回,不伪造 intent 成功 return fridaRes || { success: false, error: 'revoke_frida_failed', msg_svr_id: msgSvrId, eligible: true }; }, // ==================== H35 注册/登录 ==================== registerAccount: function (params) { var phone = (params && params.phone) || ''; var nickname = (params && params.nickname) || ''; var password = (params && params.password) || ''; if (!phone) return { success: false, error: '缺少 phone' }; return _intentAction('com.workphone.REGISTER', { phone: phone, nickname: nickname, password: password }, 'register_started', { phone: phone }); }, loginByPassword: function (params) { var phone = (params && params.phone) || ''; var password = (params && params.password) || ''; if (!phone || !password) return { success: false, error: '缺少 phone 或 password' }; return _intentAction('com.workphone.LOGIN_PASSWORD', { phone: phone, password: password }, 'login_started', { phone: phone }); }, loginBySms: function (params) { var phone = (params && params.phone) || ''; if (!phone) return { success: false, error: '缺少 phone' }; return _intentAction('com.workphone.LOGIN_SMS', { phone: phone }, 'sms_login_started', { phone: phone }); }, logout: function () { return _intentAction('com.workphone.LOGOUT', {}, 'logout_started', {}); }, switchAccount: function (params) { var wxid = (params && params.wxid) || ''; return _intentAction('com.workphone.SWITCH_ACCOUNT', { wxid: wxid }, 'account_switched', { wxid: wxid || 'picker' }); }, voiceCall: function (params) { var uid = (params && (params.user_id || params.to_id)) || ''; return _intentAction('com.workphone.VOICE_CALL', { user_id: uid }, 'voice_call', { user_id: uid }); }, videoCall: function (params) { var uid = (params && (params.user_id || params.to_id)) || ''; return _intentAction('com.workphone.VIDEO_CALL', { user_id: uid }, 'video_call', { user_id: uid }); }, shareRealTimeLocation: function (params) { var uid = (params && (params.user_id || params.to_id)) || ''; return _intentAction('com.workphone.SHARE_RT_LOCATION', { user_id: uid }, 'rt_location', { user_id: uid }); }, downloadFile: function (params) { var uid = (params && (params.user_id || params.to_id)) || ''; return _intentAction('com.workphone.DOWNLOAD_FILE', { user_id: uid }, 'file_downloaded', { user_id: uid }); }, massSend: function (params) { var ids = (params && params.user_ids) || []; var content = (params && params.content) || ''; if (!content || !ids.length) return { success: false, error: '缺少 content 或 user_ids' }; return _intentAction('com.workphone.MASS_SEND', { user_ids: ids.join(','), content: content }, 'mass_send', { count: ids.length }); }, replyComment: function (params) { return _intentAction('com.workphone.REPLY_COMMENT', { video_id: (params && params.video_id) || '', comment_id: (params && params.comment_id) || '', content: (params && params.content) || '', }, 'reply_comment', params || {}); }, receivePayment: function (params) { return _intentAction('com.workphone.RECEIVE_PAYMENT', { amount: (params && params.amount) || '', desc: (params && params.desc) || '', }, 'payment_received', params || {}); }, autoRegister: function (params) { var phone = (params && params.phone) || ''; var nickname = (params && params.nickname) || '卡若AI'; var password = (params && params.password) || ''; return _intentAction('com.workphone.AUTO_REGISTER', { phone: phone, nickname: nickname, password: password }, 'auto_register_started', { phone: phone, nickname: nickname }); }, checkLoginState: function () { try { return Java.performNow(function () { var ctx = getCtx(); var am = ctx.getSystemService(jStr('activity')); var sql = "SELECT value FROM userinfo WHERE id=2"; var rows = _execSQL(sql); var hasUser = rows.length > 0 && rows[0].value && rows[0].value.length > 0; return { success: true, logged_in: hasUser, nickname: hasUser ? rows[0].value : '', state: hasUser ? 'logged_in' : 'not_logged_in', }; }); } catch (e) { return { success: false, logged_in: false, state: 'unknown', error: String(e) }; } }, getSimPhone: function () { try { return Java.performNow(function () { var ctx = getCtx(); var TelephonyManager = Java.use('android.telephony.TelephonyManager'); var tm = Java.cast(ctx.getSystemService(jStr('phone')), TelephonyManager); var line1 = ''; try { line1 = safeStr(tm.getLine1Number()); } catch (_) {} var simOp = ''; try { simOp = safeStr(tm.getSimOperatorName()); } catch (_) {} var simState = -1; try { simState = tm.getSimState(); } catch (_) {} return { success: true, phone: line1, operator: simOp, sim_state: simState, found: line1.length >= 11, }; }); } catch (e) { return { success: false, phone: '', error: String(e) }; } }, // ==================== H36 公众号 ==================== getOfficialAccounts: function (params) { var limit = (params && params.limit) || 100; try { return Java.performNow(function () { var sql = "SELECT username, nickname, conRemark FROM rcontact WHERE username LIKE 'gh_%' ORDER BY nickname LIMIT " + limit; var rows = _execSQL(sql); return { success: true, accounts: rows.map(function (r) { return { wxid: r.username, name: r.nickname, remark: r.conRemark }; }), count: rows.length }; }); } catch (e) { return { success: false, error: String(e), accounts: [] }; } }, followOfficialAccount: function (params) { var accountId = (params && params.account_id) || ''; if (!accountId) return { success: false, error: '缺少 account_id' }; return _intentAction('com.workphone.FOLLOW_OA', { account_id: accountId }, 'oa_followed', { account_id: accountId }); }, unfollowOfficialAccount: function (params) { var accountId = (params && params.account_id) || ''; if (!accountId) return { success: false, error: '缺少 account_id' }; return _intentAction('com.workphone.UNFOLLOW_OA', { account_id: accountId }, 'oa_unfollowed', { account_id: accountId }); }, getOfficialAccountArticles: function (params) { var accountId = (params && params.account_id) || ''; var limit = (params && params.limit) || 10; if (!accountId) return { success: false, error: '缺少 account_id' }; return _intentAction('com.workphone.GET_OA_ARTICLES', { account_id: accountId, limit: String(limit) }, null, {}); }, // ==================== H37 表情管理 ==================== sendEmoji: function (params) { var toId = (params && params.to_id) || (params && params.user_id) || ''; var emojiMd5 = (params && params.emoji_md5) || (params && params.emoji_name) || ''; if (!toId || !emojiMd5) return { success: false, error: '缺少 to_id 或 emoji_md5' }; return _intentAction('com.workphone.SEND_EMOJI', { to_id: toId, emoji_md5: emojiMd5 }, 'emoji_sent', { to_id: toId }); }, addCustomEmoji: function (params) { var imagePath = (params && params.image_path) || ''; if (!imagePath) return { success: false, error: '缺少 image_path' }; return _intentAction('com.workphone.ADD_EMOJI', { image_path: imagePath }, 'emoji_added', {}); }, // ==================== H38 浮窗/多任务 ==================== addToFloat: function (params) { var wxid = (params && params.wxid) || ''; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.ADD_FLOAT', { wxid: wxid }, 'float_added', { wxid: wxid }); }, removeFromFloat: function (params) { var wxid = (params && params.wxid) || ''; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.REMOVE_FLOAT', { wxid: wxid }, 'float_removed', { wxid: wxid }); }, // ==================== H39 设备信息 ==================== getDeviceInfo: function () { try { return Java.performNow(function () { var Build = Java.use('android.os.Build'); var VERSION = Java.use('android.os.Build$VERSION'); return { success: true, device: { model: safeStr(Build.MODEL.value), brand: safeStr(Build.BRAND.value), manufacturer: safeStr(Build.MANUFACTURER.value), android_version: safeStr(VERSION.RELEASE.value), sdk_int: VERSION.SDK_INT.value, device: safeStr(Build.DEVICE.value), product: safeStr(Build.PRODUCT.value), fingerprint: safeStr(Build.FINGERPRINT.value), display: safeStr(Build.DISPLAY.value), }, }; }); } catch (e) { return { success: false, error: String(e) }; } }, getStorageInfo: function () { try { return Java.performNow(function () { var StatFs = Java.use('android.os.StatFs'); var Environment = Java.use('android.os.Environment'); var stat = StatFs.$new(Environment.getDataDirectory().getPath()); var totalBytes = stat.getTotalBytes(); var freeBytes = stat.getFreeBytes(); return { success: true, storage: { total_mb: Math.round(totalBytes / 1048576), free_mb: Math.round(freeBytes / 1048576), used_percent: Math.round((1 - freeBytes / totalBytes) * 100) }, }; }); } catch (e) { return { success: false, error: String(e) }; } }, getNetworkInfo: function () { try { return Java.performNow(function () { var ctx = getCtx(); var cm = ctx.getSystemService(jStr('connectivity')); var ConnectivityManager = Java.use('android.net.ConnectivityManager'); var mgr = Java.cast(cm, ConnectivityManager); var activeNet = mgr.getActiveNetworkInfo(); if (activeNet && activeNet.isConnected()) { return { success: true, network: { type: safeStr(activeNet.getTypeName()), connected: true, detail: safeStr(activeNet.getExtraInfo()) } }; } return { success: true, network: { type: 'none', connected: false } }; }); } catch (e) { return { success: false, error: String(e) }; } }, // ==================== 批量执行 ==================== batchExecute: function (params) { var actions = (params && params.actions) || []; if (actions.length === 0) return { success: false, error: '缺少 actions' }; var results = []; for (var i = 0; i < actions.length; i++) { var act = actions[i]; var fn = rpc.exports[act.action]; if (fn) { try { results.push({ action: act.action, result: fn(act.params || {}) }); } catch (e) { results.push({ action: act.action, result: { success: false, error: String(e) } }); } } else { results.push({ action: act.action, result: { success: false, error: '未知操作' } }); } } return { success: true, results: results, count: results.length }; }, // ==================== 奥创协议补全(1077-1270 缺口 action)==================== // 读类走真 DB(_execSQL message 表 · 字段与 revokeMessage/getMessages 同源),禁止臆造成功; // 写/操作类走 _intentAction 诚实通道(无接收端返回 success:false,与奥创 APK 协议一致)。 // —— 会话/消息读类(真 DB)—— getConversations: function (params) { var limit = (params && params.limit) || 30; try { return Java.performNow(function () { // 每个 talker 取最新一条,近似会话列表(TriggerConversationPushTask 1232) var sql = "SELECT talker, MAX(createTime) AS lastTime, COUNT(*) AS msgCount " + "FROM message GROUP BY talker ORDER BY lastTime DESC LIMIT " + parseInt(limit, 10); var rows = _execSQL(sql) || []; return { success: true, conversations: rows, count: rows.length, source: 'db.message' }; }); } catch (e) { return { success: false, error: String(e) }; } }, getMessageById: function (params) { var id = (params && params.msg_svr_id) || ''; if (!id) return { success: false, error: '缺少 msg_svr_id' }; try { return Java.performNow(function () { var _id = String(id).replace(/'/g, "''"); var sql = "SELECT msgSvrId, msgId, talker, isSend, type, content, createTime " + "FROM message WHERE msgSvrId='" + _id + "' OR msgId='" + _id + "' LIMIT 1"; var rows = _execSQL(sql) || []; if (!rows.length) return { success: false, error: '消息不存在', error_code: 'msg_not_found', msg_svr_id: id }; return { success: true, message: rows[0], source: 'db.message' }; }); } catch (e) { return { success: false, error: String(e) }; } }, getMsgIdsByTime: function (params) { var start = parseInt((params && params.start_time) || 0, 10); var end = parseInt((params && params.end_time) || 0, 10) || Date.now(); try { return Java.performNow(function () { var sql = "SELECT msgSvrId, createTime FROM message WHERE createTime>=" + start + " AND createTime<=" + end + " ORDER BY createTime DESC LIMIT 500"; var rows = _execSQL(sql) || []; return { success: true, msg_svr_ids: rows, count: rows.length, source: 'db.message' }; }); } catch (e) { return { success: false, error: String(e) }; } }, getMessageRawXml: function (params) { var id = (params && params.msg_svr_id) || ''; if (!id) return { success: false, error: '缺少 msg_svr_id' }; try { return Java.performNow(function () { var _id = String(id).replace(/'/g, "''"); var sql = "SELECT msgSvrId, type, content FROM message WHERE msgSvrId='" + _id + "' OR msgId='" + _id + "' LIMIT 1"; var rows = _execSQL(sql) || []; if (!rows.length) return { success: false, error: '消息不存在', error_code: 'msg_not_found', msg_svr_id: id }; return { success: true, raw_xml: rows[0].content, type: rows[0].type, msg_svr_id: id, source: 'db.message' }; }); } catch (e) { return { success: false, error: String(e) }; } }, // —— 加好友多场景(写类 · intent 诚实通道)—— addFriendInChatRoom: function (params) { var room = (params && params.group_id) || ''; var wxid = (params && (params.wxid || params.user_id)) || ''; if (!room || !wxid) return { success: false, error: '缺少 group_id 或 wxid' }; return _intentAction('com.workphone.ADD_FRIEND_IN_ROOM', { group_id: room, wxid: wxid, verify: (params && params.verify_message) || '' }, 'add_friend_in_room', { group_id: room, wxid: wxid }); }, addFriendFromPhonebook: function (params) { var phone = (params && params.phone) || ''; if (!phone) return { success: false, error: '缺少 phone' }; return _intentAction('com.workphone.ADD_FRIEND_PHONEBOOK', { phone: phone, verify: (params && params.verify_message) || '' }, 'add_friend_phonebook', { phone: phone }); }, addFriendWithScene: function (params) { var wxid = (params && (params.wxid || params.user_id)) || ''; var scene = (params && params.scene) || '30'; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.ADD_FRIEND_SCENE', { wxid: wxid, scene: String(scene), verify: (params && params.verify_message) || '' }, 'add_friend_scene', { wxid: wxid, scene: scene }); }, addFriendByCard: function (params) { var card = (params && (params.card_wxid || params.wxid)) || ''; if (!card) return { success: false, error: '缺少 card_wxid' }; return _intentAction('com.workphone.ADD_FRIEND_CARD', { card_wxid: card, verify: (params && params.verify_message) || '' }, 'add_friend_card', { card_wxid: card }); }, joinGroupByQr: function (params) { var qr = (params && (params.qr_content || params.image_url)) || ''; if (!qr) return { success: false, error: '缺少 qr_content 或 image_url' }; return _intentAction('com.workphone.JOIN_GROUP_QR', { qr: qr }, 'join_group_qr', { qr: qr }); }, // —— 群管理补(写类 · intent)—— approveGroupInvite: function (params) { var room = (params && params.group_id) || ''; var wxid = (params && (params.wxid || params.user_id)) || ''; if (!room) return { success: false, error: '缺少 group_id' }; return _intentAction('com.workphone.APPROVE_GROUP_INVITE', { group_id: room, wxid: wxid }, 'approve_group_invite', { group_id: room, wxid: wxid }); }, agreeJoinGroup: function (params) { var room = (params && (params.group_id || params.invite_url)) || ''; if (!room) return { success: false, error: '缺少 group_id 或 invite_url' }; return _intentAction('com.workphone.AGREE_JOIN_GROUP', { group_id: room }, 'agree_join_group', { group_id: room }); }, // —— 聊天补(写类 · intent)—— markConversationRead: function (params) { var talker = (params && (params.conversation_id || params.user_id || params.talker)) || ''; if (!talker) return { success: false, error: '缺少 conversation_id' }; return _intentAction('com.workphone.MARK_READ', { talker: talker }, 'conversation_read', { talker: talker }); }, sendMultiImage: function (params) { var talker = (params && (params.to_id || params.user_id)) || ''; var images = (params && params.images) || []; if (!talker || !images.length) return { success: false, error: '缺少 to_id 或 images' }; return _intentAction('com.workphone.SEND_MULTI_IMAGE', { to_id: talker, images: images.join(',') }, 'send_multi_image', { to_id: talker, count: images.length }); }, sendJielong: function (params) { var talker = (params && (params.to_id || params.group_id)) || ''; var content = (params && params.content) || ''; if (!talker || !content) return { success: false, error: '缺少 to_id 或 content' }; return _intentAction('com.workphone.SEND_JIELONG', { to_id: talker, content: content }, 'send_jielong', { to_id: talker }); }, sendFriendVerify: function (params) { var wxid = (params && (params.wxid || params.user_id)) || ''; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.SEND_FRIEND_VERIFY', { wxid: wxid, verify: (params && params.verify_message) || '' }, 'send_friend_verify', { wxid: wxid }); }, voiceToText: function (params) { var id = (params && params.msg_svr_id) || ''; if (!id) return { success: false, error: '缺少 msg_svr_id' }; return _intentAction('com.workphone.VOICE_TO_TEXT', { msg_svr_id: id }, 'voice_to_text', { msg_svr_id: id }); }, // —— 朋友圈补(写/读混合 · intent 诚实)—— getFriendMoments: function (params) { var wxid = (params && (params.wxid || params.user_id)) || ''; if (!wxid) return { success: false, error: '缺少 wxid' }; return _intentAction('com.workphone.GET_FRIEND_MOMENTS', { wxid: wxid }, 'get_friend_moments', { wxid: wxid }); }, getMomentDetail: function (params) { var sid = (params && params.sns_id) || ''; if (!sid) return { success: false, error: '缺少 sns_id' }; return _intentAction('com.workphone.GET_MOMENT_DETAIL', { sns_id: sid }, 'get_moment_detail', { sns_id: sid }); }, deleteMomentComment: function (params) { var sid = (params && params.sns_id) || ''; var cid = (params && params.comment_id) || ''; if (!sid) return { success: false, error: '缺少 sns_id' }; return _intentAction('com.workphone.DEL_MOMENT_COMMENT', { sns_id: sid, comment_id: cid }, 'del_moment_comment', { sns_id: sid }); }, replyMomentComment: function (params) { var sid = (params && params.sns_id) || ''; var content = (params && params.content) || ''; if (!sid || !content) return { success: false, error: '缺少 sns_id 或 content' }; return _intentAction('com.workphone.REPLY_MOMENT_COMMENT', { sns_id: sid, content: content }, 'reply_moment_comment', { sns_id: sid }); }, stopMomentsPraise: function () { return _intentAction('com.workphone.STOP_MOMENTS_PRAISE', {}, 'stop_moments_praise', {}); }, // —— 养号/清粉(写类 · intent)—— startNurture: function (params) { return _intentAction('com.workphone.START_NURTURE', { plan: (params && params.plan) || 'default' }, 'nurture_started', params || {}); }, stopNurture: function () { return _intentAction('com.workphone.STOP_NURTURE', {}, 'nurture_stopped', {}); }, detectZombieFans: function () { return _intentAction('com.workphone.DETECT_ZOMBIE', {}, 'detect_zombie_started', {}); }, stopDetectZombieFans: function () { return _intentAction('com.workphone.STOP_DETECT_ZOMBIE', {}, 'detect_zombie_stopped', {}); }, // —— 查询类(写类 · intent 诚实)—— queryRedPacket: function (params) { var id = (params && (params.red_packet_id || params.msg_svr_id)) || ''; if (!id) return { success: false, error: '缺少 red_packet_id' }; return _intentAction('com.workphone.QUERY_RED_PACKET', { red_packet_id: id }, 'query_red_packet', { red_packet_id: id }); }, getWechatLocation: function () { return _intentAction('com.workphone.GET_WX_LOCATION', {}, 'wechat_location', {}); }, searchOfficialAccount: function (params) { var kw = (params && params.keyword) || ''; if (!kw) return { success: false, error: '缺少 keyword' }; return _intentAction('com.workphone.SEARCH_BIZ', { keyword: kw }, 'search_biz', { keyword: kw }); }, // —— 手机操作(写类 · intent)—— phoneAction: function (params) { var act = (params && params.op) || ''; if (!act) return { success: false, error: '缺少 op(reboot/lock/screenshot 等)' }; return _intentAction('com.workphone.PHONE_ACTION', { op: act }, 'phone_action', { op: act }); }, }; // ============================================================ // § 5 内部实现 — 消息发送 (H17) // ============================================================ function _sendMessageInternal(toId, content, msgType) { var sent = false; var sendMethod = 'failed'; var intentFailure = null; var eventFailure = null; var verifiedMessage = null; // 方案 A: 直接调用微信内部消息发送类(按版本兼容表自动选择) var candidateClasses = getCompat('send_message'); 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; sendMethod = 'internal_' + c.cls.split('.').pop(); log('info', 'send', '消息通过内部方法发送: ' + c.cls); emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: sendMethod }); } } catch (_) { continue; } } // 方案 B: WeChat 8.0.69 已验证存在 SendMsgEvent;发布后必须 DB 回查命中,才算成功。 if (!sent && (msgType || 'text') === 'text') { var eventResult = _sendMessageBySendMsgEvent(toId, content); if (eventResult && eventResult.success && eventResult.verified) { sent = true; sendMethod = eventResult.method || 'send_msg_event'; verifiedMessage = eventResult.message || null; log('info', 'send', '消息通过 SendMsgEvent 发送并 DB 验证'); emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: sendMethod, message_id: eventResult.message_id }); } else { eventFailure = eventResult || { success: false, error: 'send_msg_event_failed' }; log('warn', 'send', 'SendMsgEvent 未验证: ' + safeStr(eventFailure.error)); } } // 方案 C: Intent 广播降级;无真实接收端时不得把广播计为发送成功。 if (!sent) { var intentResult = _intentAction( 'com.workphone.SEND_MESSAGE', { to_id: toId, content: content, msg_type: msgType }, 'message_sent', { to_id: toId, content: content, msg_type: msgType, method: 'intent_broadcast' } ); if (intentResult && intentResult.success) { sent = true; sendMethod = 'intent_broadcast'; log('info', 'send', '消息通过 Intent 广播发送'); } else { intentFailure = intentResult || { success: false, error: 'intent_dispatch_failed' }; log('warn', 'send', 'Intent 发送未验证: ' + safeStr(intentFailure.error)); } } var messageId = sent ? ((verifiedMessage && verifiedMessage.id) || generateId('msg')) : null; if (sent) { var talker = (toId === '文件传输助手' || toId === 'File Transfer') ? 'filehelper' : toId; _messageBuffer.push({ msg_svr_id: messageId, msg_type: msgType || 'text', from_id: talker, content: content, timestamp: new Date().toISOString(), is_send: true, }); if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) { _messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6)); } } var result = { success: sent, message_id: messageId, method: sendMethod, verified: sent, to_id: toId, }; if (!sent && intentFailure) { result.error = intentFailure.error || 'send_failed'; result.action = intentFailure.action || 'com.workphone.SEND_MESSAGE'; result.hint = intentFailure.hint || ''; if (eventFailure) { result.event_error = eventFailure.error || 'send_msg_event_failed'; result.event_published = !!eventFailure.published; result.event_verified = !!eventFailure.verified; result.event_method = eventFailure.method || 'send_msg_event'; } } else if (!sent && eventFailure) { result.error = eventFailure.error || 'send_msg_event_failed'; result.event_published = !!eventFailure.published; result.event_verified = !!eventFailure.verified; result.event_method = eventFailure.method || 'send_msg_event'; } return result; } function _normalizeTalkerId(toId) { if (toId === '文件传输助手' || toId === 'File Transfer') return 'filehelper'; return toId; } function _calcSendMsgScene(toId) { try { var A2 = Java.use('vn0.a2'); return A2.y(jStr(toId)); } catch (_) { return 0; } } function _sendMessageBySendMsgEvent(toId, content) { var published = false; var scene = 0; try { var SendMsgEvent = Java.use('com.tencent.mm.autogen.events.SendMsgEvent'); var IEvent = Java.use('com.tencent.mm.sdk.event.IEvent'); var event = SendMsgEvent.$new(); var payload = event.g.value; scene = _calcSendMsgScene(toId); payload.a.value = jStr(toId); payload.b.value = jStr(content); payload.c.value = scene; payload.d.value = 0; published = !!IEvent.e.overload().call(event); var verify = _findSentMessageInDB(toId, content, 3000); if (verify.success) { return { success: true, method: 'send_msg_event', published: published, verified: true, message_id: verify.message_id, message: verify.message, scene: scene }; } return { success: false, method: 'send_msg_event', published: published, verified: false, scene: scene, error: verify.error || 'db_verify_timeout', verify: verify }; } catch (e) { return { success: false, method: 'send_msg_event', published: published, verified: false, scene: scene, error: String(e) }; } } function _verifyMessageRevokedInDB(msgSvrId, timeoutMs) { var _id = String(msgSvrId).replace(/'/g, "''"); var deadline = Date.now() + (timeoutMs || 5000); var Thread = null; try { Thread = Java.use('java.lang.Thread'); } catch (_) {} do { var sql = "SELECT type, content FROM message WHERE msgSvrId = '" + _id + "' OR msgId = '" + _id + "' LIMIT 1"; var rows = _execSQL(sql); if (rows && rows.length > 0) { var t = String(rows[0].type); var c = rows[0].content || ''; if (t === '10000' || c.indexOf('撤回') !== -1 || c.indexOf('recalled') !== -1) { return { success: true, verified: true, type: t, content_preview: String(c).substring(0, 80) }; } } if (Thread) Thread.sleep(300); else Java.use('java.lang.Thread').sleep(300); } while (Date.now() < deadline); return { success: false, verified: false, error: 'revoke_db_verify_timeout' }; } function _revokeMessageByRevokeMsgEvent(msgSvrId, row) { var published = false; try { var RevokeMsgEvent = Java.use('com.tencent.mm.autogen.events.RevokeMsgEvent'); var IEvent = Java.use('com.tencent.mm.sdk.event.IEvent'); var event = RevokeMsgEvent.$new(); var payload = event.g.value; var talker = (row && row.talker) ? String(row.talker) : ''; var svrId = (row && row.msgSvrId) ? String(row.msgSvrId) : String(msgSvrId); var localId = (row && row.msgId) ? String(row.msgId) : String(msgSvrId); var createTime = parseInt((row && row.createTime) || '0', 10) || 0; // autogen payload 字段(8.0.69):a=talker b=msgSvrId(long) c=createTime d=msgId try { if (payload.a !== undefined) payload.a.value = jStr(talker); } catch (_) {} try { if (payload.b !== undefined) payload.b.value = jLong(svrId); } catch (_) {} try { if (payload.c !== undefined) payload.c.value = createTime; } catch (_) {} try { if (payload.d !== undefined) payload.d.value = jLong(localId); } catch (_) {} published = !!IEvent.e.overload().call(event); var verify = _verifyMessageRevokedInDB(msgSvrId, 5000); if (verify.verified) { return { success: true, method: 'revoke_msg_event', published: published, verified: true, msg_svr_id: msgSvrId, talker: talker, verify: verify, channel: 'hook' }; } return { success: false, method: 'revoke_msg_event', published: published, verified: false, msg_svr_id: msgSvrId, talker: talker, error: verify.error || 'revoke_not_confirmed_in_db', payload_class: safeStr(payload.getClass().getName()) }; } catch (e) { return { success: false, method: 'revoke_msg_event', published: published, verified: false, error: String(e) }; } } function _findSentMessageInDB(toId, content, timeoutMs) { var talker = _normalizeTalkerId(toId); var deadline = Date.now() + (timeoutMs || 2000); var Thread = null; try { Thread = Java.use('java.lang.Thread'); } catch (_) {} var sql = "SELECT msgId AS id, type, talker, content, createTime, isSend FROM message" + " WHERE talker='" + _escapeSql(talker) + "'" + " AND isSend=1" + " AND content='" + _escapeSql(content) + "'" + " ORDER BY createTime DESC LIMIT 1"; do { var rows = _execSQL(sql); if (rows && rows.length > 0) { return { success: true, message_id: rows[0].id || rows[0].msgId || null, message: rows[0], talker: talker }; } if (Date.now() >= deadline) break; if (Thread) Thread.sleep(250); } while (Date.now() < deadline); return { success: false, error: 'db_verify_timeout', talker: talker, diag: _lastDbDiag || {} }; } // ============================================================ // § 6 内部实现 — 数据库读取 (H15/H16) // ============================================================ function _getMessagesFromDB(conversationId, limit, offset) { limit = parseInt(limit, 10) || 3000; if (limit < 1) limit = 3000; if (limit > 5000) limit = 5000; offset = parseInt(offset, 10) || 0; if (offset < 0) offset = 0; // DB talker 用 wxid;显示名「文件传输助手」→ filehelper if (conversationId === '文件传输助手' || conversationId === 'File Transfer') { conversationId = 'filehelper'; } 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 + ' OFFSET ' + offset; var rows = _execSQL(sql); var totalCount = null; try { var cntRows = _execSQL('SELECT count(*) AS n FROM message' + where); totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null; } catch (e) { /* ignore count failure */ } 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, returned_count: rows.length, total_count: totalCount, requested_limit: limit, offset: offset, has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit, }; } function _getContactsFromDB(limit, offset) { limit = parseInt(limit, 10) || 10000; if (limit < 1) limit = 10000; if (limit > 20000) limit = 20000; offset = parseInt(offset, 10) || 0; if (offset < 0) offset = 0; var labelMap = {}; try { var labelRows = _execSQL("SELECT labelId, labelName FROM contactlabelinfo"); labelRows.forEach(function (r) { labelMap[r.labelId] = r.labelName; }); } catch (e) { /* 部分版本无标签表 */ } var contactLabelMap = {}; try { // 8.0.69 列名为 contactLabelIds(旧版 labelidlist) var clRows = _execSQL( "SELECT username, contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != ''" ); clRows.forEach(function (r) { if (!r.contactLabelIds) return; var ids = String(r.contactLabelIds).split(",").filter(function (id) { return id; }); contactLabelMap[r.username] = ids.map(function (id) { return labelMap[id] || id; }).filter(function (n) { return n; }); }); } catch (e) { /* ignore */ } // 8.0.69 兼容:先探测 rcontact 实际列,只 SELECT 存在的列(避免缺列如 signature/country 致整查询返 0) var _wantCols = ["username", "nickname", "conRemark", "type", "alias", "imgFlag", "sex", "country", "province", "city", "signature"]; var _availCols = {}; try { var _ti = _execSQL("PRAGMA table_info(rcontact)"); (_ti || []).forEach(function (c) { if (c && c.name) _availCols[c.name] = true; }); } catch (ept) { /* PRAGMA 不支持时退回全列 */ } var _useCols = Object.keys(_availCols).length > 0 ? _wantCols.filter(function (c) { return _availCols[c]; }) : _wantCols; if (_useCols.indexOf("username") < 0) _useCols.unshift("username"); var _cols = "SELECT " + _useCols.join(", ") + " FROM rcontact "; var _excludeSys = "username NOT LIKE '%@chatroom' AND username NOT LIKE 'gh_%' " + "AND username NOT IN ('filehelper','fmessage','medianote','floatbottle','newsapp','weixin','qmessage','tmessage') "; var page = " LIMIT " + limit + " OFFSET " + offset; var sql = _cols + "WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys + "ORDER BY conRemark, nickname" + page; var rows = _execSQL(sql); var _diag = { strict: (rows || []).length, use_cols: _useCols, avail_cols: Object.keys(_availCols), requested_limit: limit, offset: offset }; try { var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact"); _diag.rcontact_total = rawCnt && rawCnt[0] ? parseInt(rawCnt[0].n, 10) : null; } catch (e0a) { _diag.rcontact_total_err = String(e0a); } try { var filteredCnt = _execSQL("SELECT count(*) AS n FROM rcontact WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys); _diag.filtered_total = filteredCnt && filteredCnt[0] ? parseInt(filteredCnt[0].n, 10) : null; } catch (e0b) { _diag.filtered_total_err = String(e0b); } // 8.0.69 兼容回退①:严格过滤(type/verifyFlag)为空时,放宽为仅排除系统/群/公众号 if (!rows || rows.length === 0) { var sqlRelax = _cols + "WHERE verifyFlag=0 AND " + _excludeSys + "ORDER BY conRemark, nickname" + page; var relaxed = _execSQL(sqlRelax); _diag.relax_verifyflag = (relaxed || []).length; if (relaxed && relaxed.length > 0) { log('info', 'contacts', '严格过滤空,回退宽过滤命中 ' + relaxed.length + ' 条'); rows = relaxed; } } // 8.0.69 兼容回退②:连 verifyFlag=0 也空时,丢弃 verifyFlag,仅排除系统/群/公众号(部分版本好友 verifyFlag!=0) if (!rows || rows.length === 0) { var sqlNoVerify = _cols + "WHERE " + _excludeSys + "ORDER BY conRemark, nickname" + page; var noverify = _execSQL(sqlNoVerify); _diag.relax_noverify = (noverify || []).length; if (noverify && noverify.length > 0) { log('info', 'contacts', 'verifyFlag 过滤空,丢弃 verifyFlag 命中 ' + noverify.length + ' 条'); rows = noverify; } } // 诊断:rcontact 原始总行数 + 字段探测(定位 8.0.69 读空根因,写入返回的 _diag) if (!rows || rows.length === 0) { try { var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact"); _diag.rcontact_total = rawCnt && rawCnt[0] ? rawCnt[0].n : null; } catch (e0) { _diag.rcontact_total_err = String(e0); } try { var vf = _execSQL("SELECT verifyFlag, count(*) AS n FROM rcontact GROUP BY verifyFlag"); _diag.verifyflag_dist = vf; } catch (e1) { _diag.verifyflag_err = String(e1); } try { var tp = _execSQL("SELECT type, count(*) AS n FROM rcontact GROUP BY type LIMIT 20"); _diag.type_dist = tp; } catch (e2) { _diag.type_err = String(e2); } } var sexMap = { "0": "unknown", "1": "male", "2": "female" }; var contacts = rows.map(function (r) { var nick = r.nickname || ""; var remark = r.conRemark || ""; var alias = r.alias || ""; var wxid = r.username || ""; var display = nick || remark || alias || wxid; return { wxid: wxid, user_id: wxid, nickname: nick, display_name: display, remark: remark, alias: alias, wechat_id: alias, type: r.type, sex: sexMap[String(r.sex)] || "unknown", country: r.country || "", province: r.province || "", city: r.city || "", signature: r.signature || "", tags: contactLabelMap[wxid] || [], has_avatar: (r.imgFlag || 0) > 0, }; }); _contactCache = contacts; return { success: true, contacts: contacts, count: contacts.length, returned_count: contacts.length, total_count: _diag.filtered_total, raw_total_count: _diag.rcontact_total, requested_limit: limit, offset: offset, has_more: _diag.filtered_total !== null && _diag.filtered_total !== undefined ? _diag.filtered_total > offset + contacts.length : contacts.length >= limit, _diag: _diag }; } function _getContactInfoFromDB(wxid) { var sql = "SELECT username, nickname, conRemark, type, alias 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) { return _intentAction( 'com.workphone.ADD_FRIEND', { user_id: userId, message: message, source_type: sourceType }, 'friend_request_sent', { user_id: userId, message: message, source_type: sourceType } ); } function _acceptFriendInternal(encryptUserName, ticket) { return _intentAction( 'com.workphone.ACCEPT_FRIEND', { encrypt_username: encryptUserName, ticket: ticket }, 'friend_accepted', { encrypt_username: encryptUserName } ); } function _deleteFriendInternal(wxid) { return _intentAction( 'com.workphone.DELETE_FRIEND', { wxid: wxid }, 'friend_deleted', { wxid: wxid } ); } function _setRemarkInternal(wxid, remark) { return _intentAction('com.workphone.SET_REMARK', { wxid: wxid, remark: remark }); } // ============================================================ // § 8 内部实现 — 群管理 (H22) // ============================================================ function _getGroupsFromDB(limit, offset) { limit = parseInt(limit, 10) || 100; if (limit < 1) limit = 100; if (limit > 5000) limit = 5000; offset = parseInt(offset, 10) || 0; if (offset < 0) offset = 0; var sql = "SELECT username, nickname FROM rcontact" + " WHERE username LIKE '%@chatroom'" + " ORDER BY nickname LIMIT " + limit + " OFFSET " + offset; var rows = _execSQL(sql); var totalCount = null; try { var cntRows = _execSQL("SELECT count(*) AS n FROM rcontact WHERE username LIKE '%@chatroom'"); totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null; } catch (e) { /* ignore count failure */ } return { success: true, groups: rows.map(function (r) { return { group_id: r.username, name: r.nickname }; }), count: rows.length, returned_count: rows.length, total_count: totalCount, requested_limit: limit, offset: offset, has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit, }; } function _getLabelsFromDB(limit, offset) { limit = parseInt(limit, 10) || 5000; if (limit < 1) limit = 5000; if (limit > 20000) limit = 20000; offset = parseInt(offset, 10) || 0; if (offset < 0) offset = 0; var tables = [ { table: "contactlabel", id: "labelID", name: "labelName" }, { table: "contactlabelinfo", id: "labelId", name: "labelName" } ]; var rows = []; var totalCount = null; var sourceTable = ""; for (var i = 0; i < tables.length; i++) { var t = tables[i]; try { rows = _execSQL( "SELECT " + t.id + " AS labelID, " + t.name + " AS labelName FROM " + t.table + " ORDER BY CAST(" + t.id + " AS INTEGER), " + t.id + " LIMIT " + limit + " OFFSET " + offset ); var cntRows = _execSQL("SELECT count(*) AS n FROM " + t.table); totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null; sourceTable = t.table; break; } catch (e) { rows = []; totalCount = null; } } return { success: true, labels: rows.map(function (r) { return { id: r.labelID || r.labelId, name: r.labelName || "" }; }), count: rows.length, returned_count: rows.length, total_count: totalCount, requested_limit: limit, offset: offset, has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit, source_table: sourceTable, }; } 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) { return _intentAction( 'com.workphone.CREATE_GROUP', { member_ids: memberIds.join(','), topic: topic || '' }, 'group_created', { member_ids: memberIds, topic: topic } ); } function _inviteToGroupInternal(groupId, memberIds) { return _intentAction( 'com.workphone.INVITE_GROUP', { group_id: groupId, member_ids: memberIds.join(',') }, 'group_invite', { group_id: groupId, member_ids: memberIds } ); } function _removeFromGroupInternal(groupId, memberIds) { return _intentAction( 'com.workphone.REMOVE_GROUP_MEMBER', { group_id: groupId, member_ids: memberIds.join(',') }, 'group_member_removed', { group_id: groupId, member_ids: memberIds } ); } function _setGroupAnnouncementInternal(groupId, announcement) { return _intentAction( 'com.workphone.SET_GROUP_ANNOUNCEMENT', { group_id: groupId, announcement: announcement } ); } function _setGroupNameInternal(groupId, name) { return _intentAction('com.workphone.SET_GROUP_NAME', { group_id: groupId, name: name }); } function _quitGroupInternal(groupId) { return _intentAction( 'com.workphone.QUIT_GROUP', { group_id: groupId }, 'group_quit', { group_id: groupId } ); } // ============================================================ // § 9 内部实现 — 朋友圈 (H20/H21) // ============================================================ // 取当前栈顶 Activity(ActivityThread.mActivities 遍历 paused=false) function _getTopActivity() { var top = null; var lastAny = null; // 兜底:最后一个可取到的 activity(即便 paused) var preferred = null; // 优先:分享/选聊天/搜索类 activity(按类名匹配) try { var ActivityThread = Java.use('android.app.ActivityThread'); var at = ActivityThread.currentActivityThread(); var mActivities = at.mActivities.value; // ArrayMap var vals = mActivities.values().toArray(); for (var i = 0; i < vals.length; i++) { var rec = Java.cast(vals[i], Java.use('android.app.ActivityThread$ActivityClientRecord')); try { var act = rec.activity.value; if (act) { lastAny = act; var cn = ''; try { cn = act.getClass().getName(); } catch (_) {} // 选聊天/分享/搜索界面优先(MIUI 下分享瞬间主 activity 可能短暂 paused) if (cn && (cn.indexOf('ShareImgUI') >= 0 || cn.indexOf('SelectConversationUI') >= 0 || cn.indexOf('ShareToTimeLineUI') >= 0 || cn.indexOf('Search') >= 0 || cn.indexOf('SnsUploadUI') >= 0)) { preferred = act; } } if (!rec.paused.value) { top = act; break; } } catch (_) {} } } catch (e) { log('warn', 'sns', '_getTopActivity 失败: ' + e); } // 优先级:非paused栈顶 > 分享/选聊天类 > 任意最后一个(best-effort,修「无栈顶 Activity」) return top || preferred || lastAny; } // 递归遍历 view 树,找文本匹配且可点击的 View 并 performClick function _clickViewByText(root, texts) { if (!root) return false; try { var TextView = Java.use('android.widget.TextView'); var ViewGroup = Java.use('android.view.ViewGroup'); // 当前节点是 TextView 且文本命中 try { var asTv = Java.cast(root, TextView); var t = asTv.getText(); var s = t ? t.toString() : ''; for (var k = 0; k < texts.length; k++) { if (s.indexOf(texts[k]) >= 0) { var clickTarget = root; // 向上找可点击祖先(按钮文字常包在可点击容器内) var p = root; for (var up = 0; up < 4 && p; up++) { if (p.isClickable()) { clickTarget = p; break; } p = p.getParent(); } if (clickTarget && clickTarget.performClick) { clickTarget.performClick(); log('info', 'sns', '点击发表按钮: "' + s + '"'); return true; } } } } catch (_) {} // 递归子节点 try { var vg = Java.cast(root, ViewGroup); var n = vg.getChildCount(); for (var c = 0; c < n; c++) { if (_clickViewByText(vg.getChildAt(c), texts)) return true; } } catch (_) {} } catch (_) {} return false; } // 遍历找搜索框 EditText 并用 Frida setText 输入(不经 IME/INJECT_EVENTS,直接触发 TextWatcher 过滤)。 // 用于微信"选择一个聊天"(ShareImgUI):目标联系人常不在最近聊天可见列表,需搜索过滤后才渲染。 function _findEditTextAndSetText(root, text) { if (!root) return false; try { var EditText = Java.use('android.widget.EditText'); var ViewGroup = Java.use('android.view.ViewGroup'); try { var et = Java.cast(root, EditText); try { et.requestFocus(); } catch (_) {} et.setText.overload('java.lang.CharSequence').call(et, text); log('info', 'send', '搜索框输入: ' + text); return true; } catch (_) {} try { var vg = Java.cast(root, ViewGroup); var n = vg.getChildCount(); for (var c = 0; c < n; c++) { if (_findEditTextAndSetText(vg.getChildAt(c), text)) return true; } } catch (_) {} } catch (_) {} return false; } // 朋友圈发布界面打开后,自动点「发表」(best-effort,失败由 need_confirm_send 兜底) function _autoClickPublish(delayMs) { try { var Handler = Java.use('android.os.Handler'); var Looper = Java.use('android.os.Looper'); var handler = Handler.$new(Looper.getMainLooper()); var Runnable = Java.use('java.lang.Runnable'); var R = Java.registerClass({ name: 'com.workphone.PublishClicker' + Date.now(), implements: [Runnable], methods: { run: function () { try { var act = _getTopActivity(); if (!act) { log('warn', 'sns', '自动发表:无栈顶 Activity'); return; } var decor = act.getWindow().getDecorView(); var ok = _clickViewByText(decor, ['发表', '完成', 'Post', 'Send']); emitEvent('moments_publish_click', { clicked: ok }); log('info', 'sns', '自动发表点击结果: ' + ok); } catch (e) { log('warn', 'sns', '自动发表执行失败: ' + e); } } } }); handler.postDelayed(R.$new(), delayMs || 2000); return true; } catch (e) { log('warn', 'sns', '_autoClickPublish 调度失败: ' + e); return false; } } // 分享后 u2 自动选联系人 + 发送(best-effort):两段延迟点击。 // 第一段点联系人文本(昵称/备注/"文件传输助手"),第二段点"发送"确认。 // wxid → 微信"选择一个聊天"列表显示名候选解析。 // 真机实测:to_id=filehelper 在列表里显示为"文件传输助手",按 wxid 文本点不中 → 必须解析显示名。 // 顺序:DB 备注(conRemark) > 特殊账号映射 > DB 昵称(nickname) > 原 wxid 兜底。 function _resolveContactDisplayNames(toId) { var cands = []; var special = { 'filehelper': ['文件传输助手', 'File Transfer'], 'fmessage': ['服务通知'], 'medianote': ['语音记事本'], 'weixin': ['微信团队', 'WeChat Team'], 'floatbottle': ['漂流瓶'], 'newsapp': ['腾讯新闻'], }; // DB 查备注/昵称(备注优先,最贴近列表显示) try { Java.performNow(function () { var esc = String(toId).replace(/'/g, "''"); var rows = _execSQL("SELECT nickname, conRemark FROM rcontact WHERE username='" + esc + "' LIMIT 1"); if (rows && rows.length > 0) { if (rows[0].conRemark) cands.push(rows[0].conRemark); if (rows[0].nickname && rows[0].nickname !== rows[0].conRemark) cands.push(rows[0].nickname); } }); } catch (e) { log('warn', 'send', '解析显示名 DB 查询失败: ' + e); } if (special[toId]) cands = cands.concat(special[toId]); cands.push(toId); // 微信号/wxid 兜底 var seen = {}, out = []; for (var i = 0; i < cands.length; i++) { var v = cands[i]; if (v && !seen[v]) { seen[v] = 1; out.push(v); } } return out; } function _autoSelectAndSend(toId, delayMs) { try { var nameCandidates = _resolveContactDisplayNames(toId); log('info', 'send', '选联系人候选显示名: ' + JSON.stringify(nameCandidates)); var Handler = Java.use('android.os.Handler'); var Looper = Java.use('android.os.Looper'); var handler = Handler.$new(Looper.getMainLooper()); var Runnable = Java.use('java.lang.Runnable'); var d = delayMs || 2500; // 第-1段:点"搜索"伪框进真搜索界面(ShareImgUI 顶部搜索是 TextView 伪框,点击才出 EditText) var RC = Java.registerClass({ name: 'com.workphone.ShareSearchOpener' + Date.now(), implements: [Runnable], methods: { run: function () { try { var act = _getTopActivity(); if (!act) return; var decor = act.getWindow().getDecorView(); var ok = _clickViewByText(decor, ['搜索', 'Search']); log('info', 'send', '点搜索伪框 = ' + ok); } catch (e) { log('warn', 'send', '点搜索框失败: ' + e); } } } }); handler.postDelayed(RC.$new(), d); // 第〇段:搜索框输入显示名过滤(最近聊天列表懒加载,目标常不在可见项) var RS = Java.registerClass({ name: 'com.workphone.ShareSearcher' + Date.now(), implements: [Runnable], methods: { run: function () { try { var act = _getTopActivity(); if (!act) return; var decor = act.getWindow().getDecorView(); var kw = nameCandidates[0]; var typed = _findEditTextAndSetText(decor, kw); emitEvent('share_search_input', { keyword: kw, typed: typed }); log('info', 'send', '分享搜索输入 "' + kw + '" = ' + typed); } catch (e) { log('warn', 'send', '搜索输入失败: ' + e); } } } }); handler.postDelayed(RS.$new(), d + 1200); var R1 = Java.registerClass({ name: 'com.workphone.ContactSelector' + Date.now(), implements: [Runnable], methods: { run: function () { try { var act = _getTopActivity(); if (!act) { log('warn', 'send', '选联系人:无栈顶 Activity'); return; } var decor = act.getWindow().getDecorView(); var ok = _clickViewByText(decor, nameCandidates); emitEvent('share_contact_click', { to_id: toId, candidates: nameCandidates, clicked: ok }); log('info', 'send', '分享选联系人点击 ' + JSON.stringify(nameCandidates) + ' = ' + ok); } catch (e) { log('warn', 'send', '选联系人失败: ' + e); } } } }); handler.postDelayed(R1.$new(), d + 3100); var R2 = Java.registerClass({ name: 'com.workphone.ShareSendClicker' + Date.now(), implements: [Runnable], methods: { run: function () { try { var act = _getTopActivity(); if (!act) return; var decor = act.getWindow().getDecorView(); var ok = _clickViewByText(decor, ['发送', '發送', 'Send', '发送(1)', '發送(1)']); emitEvent('share_send_click', { clicked: ok }); log('info', 'send', '分享发送点击结果: ' + ok); } catch (e) { log('warn', 'send', '发送点击失败: ' + e); } } } }); handler.postDelayed(R2.$new(), d + 4700); return true; } catch (e) { log('warn', 'send', '_autoSelectAndSend 调度失败: ' + e); return false; } } // 真发布:启动微信朋友圈发布 Activity(SnsUploadUI)并预填内容 // 替代旧的 com.workphone.POST_MOMENTS 广播(无接收端=占位)。 // 纯文字朋友圈:Ksnsupload_type=9(社区已知),文本 extra 多 key 兜底。 // 媒体分享真实现(替代 no_receiver 占位):ACTION_SEND 系统分享拉起微信"选择联系人"界面。 // 微信进程内 startActivity 给自己:关 StrictMode 允许 file:// uri,避免 FileUriExposedException。 // 阶段一:真拉起微信分享界面(可观测、非占位);阶段二 u2 自动选 to_id + 发送(_autoSelectAndSend)。 function _shareMediaToWechat(filePath, mimeType, toId, kind) { try { // 关 VmPolicy 允许 file:// uri 跨组件传递 try { var StrictMode = Java.use('android.os.StrictMode'); var VmBuilder = Java.use('android.os.StrictMode$VmPolicy$Builder'); StrictMode.setVmPolicy(VmBuilder.$new().build()); } catch (_) {} var Intent = Java.use('android.content.Intent'); var Uri = Java.use('android.net.Uri'); var File = Java.use('java.io.File'); var Parcelable = Java.use('android.os.Parcelable'); var ctx = getCtx(); var f = File.$new(filePath); var exists = false; try { exists = f.exists(); } catch (_) {} if (!exists) { return { success: false, error: '文件不存在: ' + filePath, kind: kind }; } var uri = Uri.fromFile(f); function buildIntent(cls) { var intent = Intent.$new(); intent.setAction('android.intent.action.SEND'); intent.setType(mimeType); intent.putExtra('android.intent.extra.STREAM', Java.cast(uri, Parcelable)); intent.setPackage('com.tencent.mm'); // 直达微信"选择一个聊天"页(ShareImgUI),跳过 MIUI 系统分享面板那一层 if (cls) intent.setClassName('com.tencent.mm', cls); intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK intent.addFlags(0x00000001); // FLAG_GRANT_READ_URI_PERMISSION return intent; } var method = 'action_send'; var started = false; var lastErr = ''; try { ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI')); started = true; method = 'action_send_direct'; } catch (e1) { lastErr = String(e1); } if (!started) { ctx.startActivity(buildIntent(null)); started = true; method = 'action_send_systempanel'; } log('info', 'send', 'ACTION_SEND 分享' + kind + ' 拉起微信(' + method + '): ' + filePath + (lastErr ? ' fallback=' + lastErr : '')); emitEvent('media_share_ui_opened', { kind: kind, file: filePath, to_id: toId, mime: mimeType, method: method }); // 阶段二:u2 自动选联系人 + 发送(best-effort;失败由 need_manual_select 兜底) var scheduled = _autoSelectAndSend(toId, 2500); return { success: true, method: method, kind: kind, file: filePath, to_id: toId, auto_select_scheduled: scheduled, need_manual_select: !scheduled, note: scheduled ? '已拉起微信分享界面,2.5s 后自动选联系人发送' : '已拉起微信分享界面,待选联系人发送(u2 自动化未就绪)', }; } catch (e) { return { success: false, error: '分享' + kind + '失败: ' + String(e), kind: kind }; } } // 文本/链接分享真实现(替代 SEND_LINK no_receiver 占位):ACTION_SEND text/plain 拉起微信"选择联系人"。 // 与 _shareMediaToWechat 同款 intent,仅把 STREAM 换成 EXTRA_TEXT;链接以"标题+换行+URL"纯文本投递。 // 待真机验证:设备复连后第一批 A 类验证目标。 function _shareTextToWechat(text, toId) { try { var Intent = Java.use('android.content.Intent'); var ctx = getCtx(); function buildIntent(cls) { var intent = Intent.$new(); intent.setAction('android.intent.action.SEND'); intent.setType('text/plain'); intent.putExtra('android.intent.extra.TEXT', text); intent.setPackage('com.tencent.mm'); // 直达微信"选择一个聊天"页(ShareImgUI),跳过 MIUI 系统分享面板那一层 if (cls) intent.setClassName('com.tencent.mm', cls); intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK return intent; } var method = 'action_send_text'; var started = false; var lastErr = ''; // 真机实测发现:仅 setPackage 时 MIUI 仍弹系统分享面板(多一层"发送给朋友/收藏")。 // 优先 setClassName 直达 ShareImgUI(微信"选择联系人转发"页),失败再回退系统面板。 try { ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI')); started = true; method = 'action_send_text_direct'; } catch (e1) { lastErr = String(e1); } if (!started) { ctx.startActivity(buildIntent(null)); started = true; method = 'action_send_text_systempanel'; } log('info', 'send', 'ACTION_SEND 文本/链接 拉起微信(' + method + '): len=' + text.length + (lastErr ? ' fallback=' + lastErr : '')); emitEvent('text_share_ui_opened', { to_id: toId, text_len: text.length, method: method }); var scheduled = _autoSelectAndSend(toId, 2500); return { success: true, method: method, to_id: toId, auto_select_scheduled: scheduled, need_manual_select: !scheduled, note: scheduled ? '已拉起微信分享界面,2.5s 后自动选联系人发送' : '已拉起微信分享界面,待选联系人发送(u2 自动化未就绪)', }; } catch (e) { return { success: false, error: '文本/链接分享失败: ' + String(e) }; } } function _postMomentsInternal(content, imageUrls, autoSend) { imageUrls = imageUrls || []; if (autoSend === undefined) autoSend = true; var candidates = getCompat('sns_upload'); // [{cls:'...SnsUploadUI'}, ...] var lastErr = ''; for (var i = 0; i < candidates.length; i++) { var clsName = candidates[i].cls; try { var Intent = Java.use('android.content.Intent'); var ComponentName = Java.use('android.content.ComponentName'); var ctx = getCtx(); var SnsCls = Java.use(clsName); // 校验类存在 var intent = Intent.$new(); intent.setComponent(ComponentName.$new('com.tencent.mm', clsName)); intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK // 纯文字:type=9;图文:type=1(含 imglist) var isText = imageUrls.length === 0; intent.putExtra('Ksnsupload_type', isText ? 9 : 1); // 文本 extra 多 key 兜底(不同版本 key 名不同) intent.putExtra('Ksnsupload_text', content); intent.putExtra('sns_text', content); intent.putExtra('Ksns_upload_text', content); if (!isText) { intent.putExtra('Ksnsupload_imglist', imageUrls.join(',')); } ctx.startActivity(intent); log('info', 'sns', '启动 SnsUploadUI(' + clsName + ') content_len=' + content.length + ' images=' + imageUrls.length); emitEvent('moments_post_ui_opened', { content: content, image_count: imageUrls.length, cls: clsName }); // 打开发布界面+预填后,best-effort 自动点「发表」(失败由 need_confirm_send 兜底) var scheduled = autoSend ? _autoClickPublish(2200) : false; return { success: true, method: 'sns_upload_ui', cls: clsName, auto_click_scheduled: scheduled, need_confirm_send: !scheduled, note: scheduled ? '已打开朋友圈发布界面并预填,2.2s 后自动点发表' : '已打开发布界面并预填,待点发表', sns_id: generateId('sns'), }; } catch (e) { lastErr = String(e); } } return { success: false, error: '发朋友圈失败(所有 SnsUploadUI 候选): ' + lastErr }; } function _getMomentsFromDB(wxid, limit) { var snsPath = _getSnsDbPath(); if (!snsPath) { return { success: false, error: 'SnsMicroMsg.db 未找到', moments: [] }; } var where = wxid ? " WHERE userName='" + wxid.replace(/'/g, "''") + "'" : ''; // 8.0.69 兼容:探测 SnsInfo 实际列,只 SELECT 存在的列(避免 mediaCount/likeCount/commentCount 缺列致整查询返 0) var _wantSns = ["snsId", "userName", "createTime", "content", "mediaCount", "likeCount", "commentCount", "type", "sourceType"]; var _availSns = {}; var _snsDiag = {}; try { var _sti = _execSnsSQL("PRAGMA table_info(SnsInfo)"); (_sti || []).forEach(function (c) { if (c && c.name) _availSns[c.name] = true; }); _snsDiag.avail_cols = Object.keys(_availSns); } catch (eps) { _snsDiag.pragma_err = String(eps); } var _useSns = Object.keys(_availSns).length > 0 ? _wantSns.filter(function (c) { return _availSns[c]; }) : _wantSns; if (_useSns.indexOf("snsId") < 0) _useSns.unshift("snsId"); _snsDiag.use_cols = _useSns; var sql = "SELECT " + _useSns.join(", ") + " FROM SnsInfo" + where + " ORDER BY createTime DESC LIMIT " + limit; var rows = _execSnsSQL(sql); _snsDiag.rows = (rows || []).length; if (!rows || rows.length === 0) { try { var sc = _execSnsSQL("SELECT count(*) AS n FROM SnsInfo"); _snsDiag.snsinfo_total = sc && sc[0] ? sc[0].n : null; } catch (e0) { _snsDiag.total_err = String(e0); } var hasSnsRows = safeInt(_snsDiag.snsinfo_total, 0) > 0; var emptyMoments = _emptyReadFailure('朋友圈', _lastDbDiag, hasSnsRows); if (emptyMoments) { emptyMoments.moments = []; emptyMoments._diag = _snsDiag; return emptyMoments; } } 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, _diag: _snsDiag, }; } function _parseSnsContent(raw) { if (!raw) return { text: '' }; try { var idx = raw.indexOf(''); var endIdx = raw.indexOf(''); if (idx >= 0 && endIdx > idx) { return { text: raw.substring(idx + 13, endIdx) }; } } catch (_) {} return { text: safeStr(raw).substring(0, 200) }; } function _likeMomentsInternal(snsId) { return _intentAction( 'com.workphone.LIKE_MOMENTS', { sns_id: snsId }, 'moments_liked', { sns_id: snsId } ); } function _commentMomentsInternal(snsId, comment) { return _intentAction( 'com.workphone.COMMENT_MOMENTS', { sns_id: snsId, comment: comment }, 'moments_commented', { sns_id: snsId, comment: comment } ); } // ============================================================ // § 10 通用 Intent 调度器(所有非 DB 操作统一走此) // ============================================================ function _intentAction(intentAction, extras, eventType, eventPayload) { try { return Java.performNow(function () { var Intent = Java.use('android.content.Intent'); var ctx = getCtx(); var intent = Intent.$new(intentAction); var keys = Object.keys(extras || {}); for (var i = 0; i < keys.length; i++) { intent.putExtra(keys[i], String(extras[keys[i]])); } // 真机铁律 #3/#14:先校验是否存在接收端,无接收端不得返回 success:true(禁止假成功) var receivers = 0; try { var pm = ctx.getPackageManager(); var list = pm.queryBroadcastReceivers(intent, 0); receivers = list ? list.size() : 0; } catch (qe) { receivers = -1; // 查询失败:未知,按未验证处理 } if (receivers <= 0) { var receiverError = receivers === 0 ? 'no_receiver_registered' : 'receiver_query_failed'; log('warn', 'intent', intentAction + ' 接收端不可验证 → 不广播(避免假成功,需真实 RPC 或装接收端 App)'); return { success: false, method: 'intent_broadcast', verified: false, error: receiverError, receivers: receivers, action: intentAction, hint: '该动作经 Android 广播分发,当前设备未注册接收端;需实现真实 Frida RPC 或部署接收端 App', }; } ctx.sendBroadcast(intent); if (eventType) { emitEvent(eventType, eventPayload || {}); } log('info', 'intent', intentAction + ' → ' + keys.join(',') + ' (receivers=' + receivers + ')'); return { success: true, method: 'intent_broadcast', verified: receivers > 0, receivers: receivers, action: intentAction, }; }); } catch (e) { return { success: false, error: String(e), action: intentAction }; } } // ============================================================ // § 11 Hook 初始化 // ============================================================ Java.perform(function () { log('info', 'init', 'wechat_hook_v3.1 正在初始化(110 actions / 24 modules / 版本自适应)...'); // 获取微信版本 + 初始化版本兼容 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); _resolveVersionCompat(_wechatVersion); } catch (_) { _versionCompat = _DEFAULT_COMPAT; log('warn', 'init', '无法获取微信版本,使用 default 全候选'); } // 查找数据库路径 _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_v3.1 初始化完成 — 110 actions / 全功能就绪', _hooked); emitEvent('hook_initialized', { version: '3.0.0', wechat_version: _wechatVersion, hooked: _hooked, db_path: _dbPath || '', sns_db_path: _snsDbPath || '', }); });