// 微信工作手机 SDK v8-fix - 全量Frida RPC接口 // 用Java.choose找到已加载的DB实例(已验证有效) // 账号:游条姐 (wxid_5g37snchpv8e22) 'use strict'; var _dbs = {}; // name -> db instance // ==================== DB初始化 ==================== function initAllDBs() { Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { onMatch: function(db) { try { var p = db.getPath(); if (!p) return; var name = p.split('/').pop().replace('.db',''); if (p.indexOf('EnMicroMsg') > -1) _dbs['main'] = db; else if (p.indexOf('SnsMicroMsg') > -1) _dbs['sns'] = db; else if (p.indexOf('sns_star_info') > -1) _dbs['sns_star'] = db; else if (p.indexOf('WalletCoreDB-2') > -1) _dbs['wallet'] = db; else if (p.indexOf('WalletPay') > -1) _dbs['wallet2'] = db; else if (p.indexOf('FinderMM029') > -1) _dbs['finder'] = db; else if (p.indexOf('FinderMessage006') > -1) _dbs['finder2'] = db; else if (p.indexOf('FinderAccounts_01') > -1) _dbs['finder3'] = db; else if (p.indexOf('enFavorite') > -1) _dbs['favorite'] = db; else if (p.indexOf('FTS5IndexMicroMsg') > -1) _dbs['fts5'] = db; else if (p.indexOf('AppBrandComm') > -1) _dbs['appbrand'] = db; else if (p.indexOf('WxFileIndex') > -1) _dbs['wxfile'] = db; _dbs['_path_' + name] = db; } catch(e) {} }, onComplete: function() {} }); } function getDB(name) { if (!_dbs[name]) initAllDBs(); return _dbs[name] || null; } 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 writeSQL(db, sql) { if (!db) return {error: 'DB not available'}; try { db.execSQL(sql); return {success: true}; } catch(e) { return {error: e.toString()}; } } // ==================== RPC接口 ==================== rpc.exports = { // ===== 系统 ===== ping: function() { return {status: 'ok', version: 'v8-fix', timestamp: Date.now()}; }, initDbs: function() { initAllDBs(); var status = {}; ['main','sns','sns_star','wallet','wallet2','finder','finder2','finder3','favorite','fts5','appbrand'].forEach(function(n) { status[n] = !!_dbs[n]; }); return {status: status, total_dbs: Object.keys(_dbs).length}; }, listAllDbs: function() { initAllDBs(); var list = []; Object.keys(_dbs).forEach(function(k) { if (!k.startsWith('_path_')) return; try { var db = _dbs[k]; var p = db.getPath(); list.push({name: k.replace('_path_',''), path: p}); } catch(e) {} }); return {dbs: list, count: list.length}; }, // ===== 账号 ===== getAccountInfo: function() { var db = getDB('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"); var sex = execSQL(db, "SELECT value FROM userinfo WHERE id=12"); 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, sex: sex.rows[0] ? sex.rows[0].value : null, }; }, // ===== 联系人 ===== getContacts: function(limit, offset) { var db = getDB('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 = getDB('main'); var r1 = execSQL(db, "SELECT COUNT(*) as friends FROM rcontact WHERE type=3 AND verifyFlag=0"); var r2 = execSQL(db, "SELECT COUNT(*) as total FROM rcontact"); return { friends: r1.rows[0] ? r1.rows[0].friends : 0, total: r2.rows[0] ? r2.rows[0].total : 0 }; }, getAllStats: function() { var db = getDB('main'); var friends = execSQL(db, "SELECT COUNT(*) as n FROM rcontact WHERE type=3 AND verifyFlag=0"); var groups = execSQL(db, "SELECT COUNT(*) as n FROM chatroom"); var msgs = execSQL(db, "SELECT COUNT(*) as n FROM message"); var imgs = execSQL(db, "SELECT COUNT(*) as n FROM ImgInfo2"); var videos = execSQL(db, "SELECT COUNT(*) as n FROM videoinfo2"); var conv = execSQL(db, "SELECT COUNT(*) as n FROM conversation"); var files = execSQL(db, "SELECT COUNT(*) as n FROM FileMsgInfo"); return { friends: friends.rows[0] ? parseInt(friends.rows[0].n) : 0, groups: groups.rows[0] ? parseInt(groups.rows[0].n) : 0, messages: msgs.rows[0] ? parseInt(msgs.rows[0].n) : 0, images: imgs.rows[0] ? parseInt(imgs.rows[0].n) : 0, videos: videos.rows[0] ? parseInt(videos.rows[0].n) : 0, conversations: conv.rows[0] ? parseInt(conv.rows[0].n) : 0, files: files.rows[0] ? parseInt(files.rows[0].n) : 0, }; }, searchContact: function(keyword) { var db = getDB('main'); return execSQL(db, "SELECT username, alias, conRemark, nickName, type 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 = getDB('main'); return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1"); }, // ===== 消息 ===== getMessages: function(talker, limit) { var db = getDB('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 = getDB('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 = getDB('main'); if (!db) return {error: 'main 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 = writeSQL(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, note: 'Written to local DB. Network sync requires real class name.'}; } return result; }, massMessage: function(targets, content) { var db = getDB('main'); if (!db) return {error: 'main DB not available'}; var results = []; targets.forEach(function(toUser) { var msgId = Date.now() + Math.floor(Math.random() * 100000); 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 = writeSQL(db, sql); results.push({toUser: toUser, msgId: msgId, success: !!r.success, error: r.error || null}); }); return {results: results, total: targets.length, success_count: results.filter(function(r) { return r.success; }).length}; }, // ===== 会话 ===== getConversations: function(limit) { var db = getDB('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 = getDB('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 = getDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); }, getGroupDetail: function(chatroomId) { var db = getDB('main'); return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1"); }, getGroupMembers: function(chatroomId) { var db = getDB('main'); var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName, memberCount 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, memberCount: group.rows[0].memberCount }; } return {error: 'Group not found'}; }, setGroupRemark: function(chatroomId, remark) { var db = getDB('main'); return writeSQL(db, "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"); }, setGroupAnnouncement: function(chatroomId, announcement) { var db = getDB('main'); return writeSQL(db, "UPDATE chatroom SET announcement='" + announcement.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"); }, // ===== 标签 ===== getLabels: function() { var db = getDB('main'); var r = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 2000"); 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()] = (labelSet[id.trim()] || 0) + 1; }); }); } return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length, usage: labelSet}; }, getContactsByLabel: function(labelId) { var db = getDB('main'); return execSQL(db, "SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " + "WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100" ); }, // ===== 朋友圈 ===== getSnsDbInfo: function() { initAllDBs(); var result = {}; ['sns','sns_star'].forEach(function(name) { var db = _dbs[name]; if (db) { var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); result[name] = { available: true, path: db.getPath(), tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : [] }; } else { result[name] = {available: false}; } }); return result; }, getMoments: function(limit) { limit = limit || 20; var db = getDB('sns'); if (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) { var r = execSQL(db, "SELECT snsId, userName, createTime, content, type, localFlag FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit); r.db = 'SnsMicroMsg'; return r; } } var db2 = getDB('sns_star'); if (db2) { var r2 = execSQL(db2, "SELECT * FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit); r2.db = 'sns_star_info'; return r2; } return {error: 'SNS DB not loaded. Need to open WeChat moments first.', rows: [], count: 0}; }, getMomentCount: function() { var db = getDB('sns'); if (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 COUNT(*) as total FROM SnsInfo"); } } var db2 = getDB('sns_star'); if (db2) { return execSQL(db2, "SELECT COUNT(*) as total FROM StarSnsInfo"); } return {error: 'SNS DB not loaded', count: 0}; }, postMoment: function(content) { var db = getDB('sns'); if (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) { var cols = execSQL(db, "PRAGMA table_info(SnsInfo)"); var colNames = cols.rows ? cols.rows.map(function(c) { return c.name; }) : []; var snsId = Date.now(); var createTime = Math.floor(Date.now() / 1000); var insertCols = ['snsId', 'userName', 'createTime', 'content']; var insertVals = [snsId, "'wxid_5g37snchpv8e22'", createTime, "'" + 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 sql = "INSERT INTO SnsInfo (" + insertCols.join(',') + ") VALUES (" + insertVals.join(',') + ")"; var r = writeSQL(db, sql); if (r.success) { var verify = execSQL(db, "SELECT snsId, content FROM SnsInfo WHERE snsId=" + snsId); return {success: true, snsId: snsId, content: content, db_verified: verify.count > 0, db: 'SnsMicroMsg'}; } return r; } } var db2 = getDB('sns_star'); if (db2) { var snsId2 = Date.now(); var createTime2 = Math.floor(Date.now() / 1000); var sql2 = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" + snsId2 + ", 'wxid_5g37snchpv8e22', 1, " + createTime2 + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)"; var r2 = writeSQL(db2, sql2); if (r2.success) { var verify2 = execSQL(db2, "SELECT snsId, content FROM StarSnsInfo WHERE snsId=" + snsId2); return {success: true, snsId: snsId2, content: content, db_verified: verify2.count > 0, db: 'sns_star_info'}; } return r2; } return {error: 'SNS DB not loaded. Open WeChat moments first.', success: false}; }, // ===== 视频号 ===== getFinderInfo: function() { initAllDBs(); var result = {}; ['finder','finder2','finder3'].forEach(function(name) { var db = _dbs[name]; if (db) { var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); result[name] = { available: true, path: db.getPath().split('/').pop(), tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : [] }; } else { result[name] = {available: false}; } }); return result; }, getFinderContacts: function(limit) { var db = getDB('finder'); limit = limit || 20; if (!db) return {error: 'FinderMM029 DB not available'}; return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT " + limit); }, getFinderMedia: function(limit) { var db = getDB('finder'); limit = limit || 20; if (!db) return {error: 'FinderMM029 DB not available'}; return execSQL(db, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT " + limit); }, getFinderAccounts: function() { var db = getDB('finder3'); if (!db) return {error: 'FinderAccounts 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; }) : []}; (result.tables || []).forEach(function(t) { try { result[t] = execSQL(db, "SELECT * FROM " + t + " LIMIT 5"); } catch(e) {} }); return result; }, // ===== 收藏 ===== getFavorites: function(limit) { limit = limit || 20; var db = getDB('favorite'); if (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('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); } } // 尝试FTS5 var db2 = getDB('fts5'); if (db2) { var tables2 = execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'"); var tableNames2 = tables2.rows ? tables2.rows.map(function(t) { return t.name; }) : []; if (tableNames2.indexOf('FTS5MetaFavorite') > -1) { return execSQL(db2, "SELECT * FROM FTS5MetaFavorite LIMIT " + limit); } } return {error: 'Favorite DB not loaded', rows: [], count: 0}; }, // ===== 钱包/红包 ===== getWalletInfo: function() { var db = getDB('wallet'); if (!db) return {error: 'WalletCoreDB 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; }) : []}; return result; }, getWalletLedger: function(limit) { var db = getDB('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 = getDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit); }, getWalletUserInfo: function() { var db = getDB('wallet'); if (!db) return {error: 'Wallet 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.forEach(function(t) { try { var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 3"); if (!r.error && r.count > 0) result[t] = r; } catch(e) {} }); return result; }, // ===== 图片/视频/文件 ===== getImageList: function(limit) { var db = getDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit); }, getImageCount: function() { var db = getDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); }, getVideoList: function(limit) { var db = getDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit); }, getVideoCount: function() { var db = getDB('main'); return execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2"); }, getFileList: function(limit) { var db = getDB('main'); limit = limit || 20; return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit); }, // ===== 小程序 ===== getAppBrandList: function() { var db = getDB('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, 5).forEach(function(t) { try { var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 5"); if (!r.error) result[t] = r; } catch(e) {} }); return result; }, // ===== 搜索 ===== globalSearch: function(keyword) { var db = getDB('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 = getDB(dbName || 'main'); return execSQL(db, sql); }, rawWrite: function(dbName, sql) { var db = getDB(dbName || 'main'); return writeSQL(db, sql); }, getTableInfo: function(dbName, tableName) { var db = getDB(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 = getDB(dbName || 'main'); return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"); }, }; // 启动时初始化 Java.perform(function() { initAllDBs(); console.log('[*] WeChat SDK v8-fix loaded. DBs found: ' + Object.keys(_dbs).length); });