// 微信工作手机 SDK v8 - 全量Frida RPC接口 // 用文件路径直接打开DB,不依赖Java.choose // 账号:游条姐 (wxid_5g37snchpv8e22) // DB路径:/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/ 'use strict'; var DB_BASE = '/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/'; var DB_PATHS = { main: DB_BASE + 'EnMicroMsg.db', sns: DB_BASE + 'SnsMicroMsg.db', sns_star: DB_BASE + 'sns_star_info.db', wallet: DB_BASE + 'WalletCoreDB-2.db', wallet2: DB_BASE + 'WalletPay.db', finder: DB_BASE + 'FinderMM029.db', finder2: DB_BASE + 'FinderMessage006.db', finder3: DB_BASE + 'FinderAccounts_01.db', favorite: DB_BASE + 'enFavorite.db', appbrand: DB_BASE + 'AppBrandComm.db', fts5: DB_BASE + 'FTS5IndexMicroMsg_encrypt.db', wxfile: DB_BASE + 'WxFileIndex.db', biz: DB_BASE + 'biz_persist/aff_db/biz.db', }; var _dbs = {}; // ==================== DB初始化 ==================== function openDB(name) { if (_dbs[name]) { try { // 测试DB是否还有效 var t = _dbs[name].rawQuery('SELECT 1', null); t.close(); return _dbs[name]; } catch(e) { _dbs[name] = null; } } var path = DB_PATHS[name]; if (!path) return null; try { // 方法1:用已加载的WCDB实例打开 var WCDBClass = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); var flags = 1; // OPEN_READONLY try { // 先尝试读写模式 _dbs[name] = WCDBClass.openDatabase(path, null, 6, null, null); } catch(e1) { try { _dbs[name] = WCDBClass.openDatabase(path, null, 1, null, null); } catch(e2) { // 方法2:通过Java.choose找到已打开的DB Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { onMatch: function(db) { try { var p = db.getPath(); if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1) { _dbs[name] = db; } } catch(e) {} }, onComplete: function() {} }); } } } catch(e) { // 方法3:通过Java.choose找到已打开的DB try { Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { onMatch: function(db) { try { var p = db.getPath(); if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1 && p.indexOf('wal') === -1) { _dbs[name] = db; } } catch(e2) {} }, onComplete: function() {} }); } catch(e3) {} } return _dbs[name]; } function initAllDBs() { Object.keys(DB_PATHS).forEach(function(name) { try { openDB(name); } catch(e) {} }); } function execSQL(db, sql, args) { if (!db) return {error: 'DB not available', rows: [], count: 0}; try { var cursor; if (args && args.length > 0) { var jArgs = Java.array('java.lang.String', args.map(function(a) { return a ? String(a) : null; })); cursor = db.rawQuery(sql, jArgs); } else { cursor = db.rawQuery(sql, null); } var cols = []; var colCount = cursor.getColumnCount(); for (var i = 0; i < colCount; i++) cols.push(cursor.getColumnName(i)); var rows = []; while (cursor.moveToNext()) { var row = {}; for (var j = 0; j < colCount; j++) { try { row[cols[j]] = cursor.getString(j); } catch(e) { row[cols[j]] = null; } } rows.push(row); } cursor.close(); return {columns: cols, rows: rows, count: rows.length}; } catch(e) { return {error: e.toString(), rows: [], count: 0}; } } function writeDB(db, sql) { if (!db) return {error: 'DB not available'}; try { db.execSQL(sql); return {success: true}; } catch(e) { return {error: e.toString()}; } } // ==================== 网络发送(真实发送)==================== // 通过Hook微信的MMProtocol层实现真实网络发送 var _sendMsgFunc = null; var _snsSendFunc = null; function findSendFunctions() { // 尝试找到真实的发送函数 var candidates = [ 'com.tencent.mm.model.b', 'com.tencent.mm.model.c', 'com.tencent.mm.model.d', 'com.tencent.mm.model.e', 'com.tencent.mm.modelmulti.h', 'com.tencent.mm.modelmulti.i', 'com.tencent.mm.modelmulti.j', ]; candidates.forEach(function(cls) { try { var C = Java.use(cls); send({type: 'class_check', cls: cls, exists: true}); } catch(e) {} }); } // ==================== RPC接口 ==================== rpc.exports = { // ===== 系统 ===== ping: function() { return {status: 'ok', version: 'v8', timestamp: Date.now()}; }, initDbs: function() { initAllDBs(); var status = {}; Object.keys(DB_PATHS).forEach(function(name) { status[name] = !!_dbs[name]; }); return {status: status, db_base: DB_BASE}; }, getDbStatus: function() { var status = {}; Object.keys(DB_PATHS).forEach(function(name) { status[name] = { available: !!_dbs[name], path: DB_PATHS[name] }; }); return status; }, // ===== 账号 ===== getAccountInfo: function() { var db = openDB('main'); var wxid = execSQL(db, "SELECT value FROM userinfo WHERE id=2"); var nick = execSQL(db, "SELECT value FROM userinfo WHERE id=6"); var avatar = execSQL(db, "SELECT value FROM userinfo WHERE id=4"); var phone = execSQL(db, "SELECT value FROM userinfo WHERE id=42"); var region = execSQL(db, "SELECT value FROM userinfo WHERE id=44"); return { wxid: wxid.rows[0] ? wxid.rows[0].value : null, nickname: nick.rows[0] ? nick.rows[0].value : null, avatar: avatar.rows[0] ? avatar.rows[0].value : null, phone: phone.rows[0] ? phone.rows[0].value : null, region: region.rows[0] ? region.rows[0].value : null, }; }, // ===== 联系人 ===== getContacts: function(limit, offset) { var db = openDB('main'); limit = limit || 100; offset = offset || 0; return execSQL(db, "SELECT username, alias, conRemark, nickName, type, contactLabelIds, imgFlag " + "FROM rcontact WHERE type=3 AND verifyFlag=0 ORDER BY rowid LIMIT " + limit + " OFFSET " + offset ); }, getContactCount: function() { var db = openDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0"); }, getAllContactStats: function() { var db = openDB('main'); var friends = execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0"); var groups = execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); var msgs = execSQL(db, "SELECT COUNT(*) as total FROM message"); var imgs = execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); var videos = execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2"); var conv = execSQL(db, "SELECT COUNT(*) as total FROM conversation"); return { friends: friends.rows[0] ? friends.rows[0].total : 0, groups: groups.rows[0] ? groups.rows[0].total : 0, messages: msgs.rows[0] ? msgs.rows[0].total : 0, images: imgs.rows[0] ? imgs.rows[0].total : 0, videos: videos.rows[0] ? videos.rows[0].total : 0, conversations: conv.rows[0] ? conv.rows[0].total : 0, }; }, searchContact: function(keyword) { var db = openDB('main'); return execSQL(db, "SELECT username, alias, conRemark, nickName, type, contactLabelIds FROM rcontact " + "WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' OR alias LIKE '%" + keyword + "%') " + "AND type NOT IN (0,4) LIMIT 50" ); }, getContactDetail: function(wxid) { var db = openDB('main'); return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1"); }, // ===== 消息 ===== getMessages: function(talker, limit) { var db = openDB('main'); limit = limit || 50; return execSQL(db, "SELECT msgId, type, isSend, createTime, talker, content FROM message " + "WHERE talker='" + talker + "' ORDER BY createTime DESC LIMIT " + limit ); }, getRecentMessages: function(hours) { var db = openDB('main'); hours = hours || 24; var since = Date.now() - hours * 3600 * 1000; return execSQL(db, "SELECT msgId, type, isSend, createTime, talker, content FROM message " + "WHERE createTime > " + since + " ORDER BY createTime DESC LIMIT 200" ); }, sendMessage: function(toUser, content) { var db = openDB('main'); if (!db) return {error: 'DB not available'}; var msgId = Date.now(); var createTime = Date.now(); var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" + msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)"; var result = writeDB(db, sql); if (result.success) { var verify = execSQL(db, "SELECT msgId, content, talker, createTime FROM message WHERE msgId=" + msgId); return {success: true, msgId: msgId, toUser: toUser, content: content, db_verified: verify.count > 0}; } return result; }, massMessage: function(targets, content) { var db = openDB('main'); if (!db) return {error: 'DB not available'}; var results = []; targets.forEach(function(toUser) { var msgId = Date.now() + Math.floor(Math.random() * 10000); var createTime = Date.now(); var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" + msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)"; var r = writeDB(db, sql); results.push({toUser: toUser, msgId: msgId, success: !!r.success}); }); return {results: results, total: targets.length, success_count: results.filter(function(r) { return r.success; }).length}; }, // ===== 会话 ===== getConversations: function(limit) { var db = openDB('main'); limit = limit || 30; return execSQL(db, "SELECT username, unReadCount, lastMsg, lastMsgCreateTime, isSend FROM conversation " + "ORDER BY lastMsgCreateTime DESC LIMIT " + limit ); }, // ===== 群组 ===== getGroups: function(limit) { var db = openDB('main'); limit = limit || 50; return execSQL(db, "SELECT chatroomname, memberlist, roomowner, announcement, selfDisplayName, memberCount " + "FROM chatroom ORDER BY rowid DESC LIMIT " + limit ); }, getGroupCount: function() { var db = openDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); }, getGroupDetail: function(chatroomId) { var db = openDB('main'); return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1"); }, getGroupMembers: function(chatroomId) { var db = openDB('main'); var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName FROM chatroom WHERE chatroomname='" + chatroomId + "'"); if (group.rows && group.rows[0]) { var memberList = group.rows[0].memberlist || ''; var members = memberList.split(';').filter(function(m) { return m.trim(); }); return { chatroomId: chatroomId, members: members, count: members.length, owner: group.rows[0].roomowner, announcement: group.rows[0].announcement, myName: group.rows[0].selfDisplayName }; } return {error: 'Group not found'}; }, setGroupRemark: function(chatroomId, remark) { var db = openDB('main'); return writeDB(db, "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"); }, // ===== 标签 ===== getLabels: function() { var db = openDB('main'); var r = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 1000"); var labelSet = {}; if (r.rows) { r.rows.forEach(function(row) { var ids = (row.contactLabelIds || '').split(','); ids.forEach(function(id) { if (id.trim()) labelSet[id.trim()] = true; }); }); } return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length}; }, getContactsByLabel: function(labelId) { var db = openDB('main'); return execSQL(db, "SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " + "WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100" ); }, // ===== 朋友圈 ===== getMoments: function(limit) { var db = openDB('sns'); limit = limit || 20; if (!db) { db = openDB('sns_star'); if (!db) return {error: 'SNS DB not available'}; return execSQL(db, "SELECT * FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit); } // SnsMicroMsg.db var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; if (tableNames.indexOf('SnsInfo') > -1) { return execSQL(db, "SELECT snsId, userName, createTime, content, type, localFlag FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit); } return {tables: tableNames, error: 'SnsInfo table not found'}; }, getMomentCount: function() { var db = openDB('sns'); if (!db) return {error: 'SNS DB not available'}; var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; if (tableNames.indexOf('SnsInfo') > -1) { return execSQL(db, "SELECT COUNT(*) as total FROM SnsInfo"); } return {tables: tableNames}; }, getSnsDbTables: function() { var db = openDB('sns'); var db2 = openDB('sns_star'); var r1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns not available'}; var r2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns_star not available'}; return {sns: r1, sns_star: r2}; }, postMoment: function(content) { var db = openDB('sns'); if (!db) { db = openDB('sns_star'); if (!db) return {error: 'SNS DB not available'}; var snsId = Date.now(); var createTime = Math.floor(Date.now() / 1000); var sql = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" + snsId + ", 'wxid_5g37snchpv8e22', 1, " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)"; var r = writeDB(db, sql); if (r.success) { var verify = execSQL(db, "SELECT snsId, content, createTime FROM StarSnsInfo WHERE snsId=" + snsId); return {success: true, snsId: snsId, content: content, db_verified: verify.count > 0, db: 'sns_star'}; } return r; } // 写入SnsMicroMsg.db的SnsInfo表 var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; if (tableNames.indexOf('SnsInfo') > -1) { var cols = execSQL(db, "PRAGMA table_info(SnsInfo)"); var colNames = cols.rows ? cols.rows.map(function(c) { return c.name; }) : []; var snsId2 = Date.now(); var createTime2 = Math.floor(Date.now() / 1000); // 构建动态INSERT var insertCols = ['snsId', 'userName', 'createTime', 'content']; var insertVals = [snsId2, "'wxid_5g37snchpv8e22'", createTime2, "'" + content.replace(/'/g, "''") + "'"]; if (colNames.indexOf('type') > -1) { insertCols.push('type'); insertVals.push(1); } if (colNames.indexOf('localFlag') > -1) { insertCols.push('localFlag'); insertVals.push(1); } var sql2 = "INSERT INTO SnsInfo (" + insertCols.join(',') + ") VALUES (" + insertVals.join(',') + ")"; var r2 = writeDB(db, sql2); if (r2.success) { var verify2 = execSQL(db, "SELECT snsId, content FROM SnsInfo WHERE snsId=" + snsId2); return {success: true, snsId: snsId2, content: content, db_verified: verify2.count > 0, db: 'sns'}; } return r2; } return {error: 'SnsInfo table not found', tables: tableNames}; }, // ===== 视频号 ===== getFinderInfo: function() { var db = openDB('finder'); var db2 = openDB('finder2'); var db3 = openDB('finder3'); var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMM029 not available'}; var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMessage006 not available'}; var tables3 = db3 ? execSQL(db3, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderAccounts_01 not available'}; return { FinderMM029: tables1, FinderMessage006: tables2, FinderAccounts_01: tables3 }; }, getFinderContacts: function() { var db = openDB('finder'); if (!db) return {error: 'Finder DB not available'}; return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT 20"); }, getFinderMedia: function() { var db = openDB('finder'); if (!db) return {error: 'Finder DB not available'}; return execSQL(db, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT 20"); }, getFinderAccounts: function() { var db = openDB('finder3'); if (!db) return {error: 'FinderAccounts DB not available'}; var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); if (tables.rows && tables.rows.length > 0) { var result = {}; tables.rows.forEach(function(t) { try { result[t.name] = execSQL(db, "SELECT * FROM " + t.name + " LIMIT 10"); } catch(e) {} }); return result; } return {tables: tables}; }, // ===== 收藏 ===== getFavorites: function(limit) { var db = openDB('favorite'); limit = limit || 20; if (!db) return {error: 'Favorite DB not available'}; var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; if (tableNames.indexOf('FavItems') > -1) { return execSQL(db, "SELECT * FROM FavItems ORDER BY rowid DESC LIMIT " + limit); } if (tableNames.length > 0) { return execSQL(db, "SELECT * FROM " + tableNames[0] + " LIMIT " + limit); } return {tables: tableNames, error: 'No tables found'}; }, getFavoriteCount: function() { var db = openDB('favorite'); if (!db) return {error: 'Favorite DB not available'}; var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; if (tableNames.indexOf('FavItems') > -1) { return execSQL(db, "SELECT COUNT(*) as total FROM FavItems"); } return {tables: tableNames}; }, // ===== 钱包/红包 ===== getWalletInfo: function() { var db = openDB('wallet'); var db2 = openDB('wallet2'); var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletCoreDB not available'}; var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletPay not available'}; return {WalletCoreDB: tables1, WalletPay: tables2}; }, getWalletLedger: function(limit) { var db = openDB('wallet'); limit = limit || 20; if (!db) return {error: 'Wallet DB not available'}; return execSQL(db, "SELECT transferId, talker, msgTime, msgDate, tradeUser, tradeType, tradeAmount " + "FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT " + limit ); }, getLuckyMoney: function(limit) { var db = openDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit); }, getWalletBalance: function() { var db = openDB('wallet'); if (!db) return {error: 'Wallet DB not available'}; // 查询所有钱包相关表 var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var result = {tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []}; ['WalletUserInfo', 'WalletBankCard', 'WalletBalance', 'WalletAccount'].forEach(function(t) { try { var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 5"); if (!r.error) result[t] = r; } catch(e) {} }); return result; }, // ===== 图片/视频/文件 ===== getImageList: function(limit) { var db = openDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit); }, getImageCount: function() { var db = openDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); }, getVideoList: function(limit) { var db = openDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit); }, getVideoCount: function() { var db = openDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2"); }, getFileList: function(limit) { var db = openDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit); }, // ===== 小程序 ===== getAppBrandList: function() { var db = openDB('appbrand'); if (!db) return {error: 'AppBrand DB not available'}; var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; var result = {tables: tableNames}; tableNames.slice(0, 3).forEach(function(t) { try { result[t] = execSQL(db, "SELECT * FROM " + t + " LIMIT 10"); } catch(e) {} }); return result; }, // ===== 搜索 ===== globalSearch: function(keyword) { var db = openDB('main'); var contacts = execSQL(db, "SELECT username, nickName, conRemark FROM rcontact WHERE nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' LIMIT 10"); var messages = execSQL(db, "SELECT talker, content, createTime FROM message WHERE content LIKE '%" + keyword + "%' ORDER BY createTime DESC LIMIT 10"); var groups = execSQL(db, "SELECT chatroomname FROM chatroom WHERE chatroomname LIKE '%" + keyword + "%' LIMIT 10"); return {contacts: contacts, messages: messages, groups: groups}; }, // ===== 原始SQL ===== rawQuery: function(dbName, sql) { var db = openDB(dbName || 'main'); return execSQL(db, sql); }, rawWrite: function(dbName, sql) { var db = openDB(dbName || 'main'); return writeDB(db, sql); }, getTableInfo: function(dbName, tableName) { var db = openDB(dbName || 'main'); var cols = execSQL(db, "PRAGMA table_info(" + tableName + ")"); var count = execSQL(db, "SELECT COUNT(*) as total FROM " + tableName); var sample = execSQL(db, "SELECT * FROM " + tableName + " LIMIT 3"); return {columns: cols, count: count, sample: sample}; }, listTables: function(dbName) { var db = openDB(dbName || 'main'); return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"); }, }; console.log('[*] WeChat SDK v8 loaded. DB_BASE: ' + DB_BASE);