/** * 工作手机SDK - 微信8.0.56 完整Frida控制脚本 v4 * 修复: * - getDbInfo方法名统一 * - 联系人列表SQL修复(移除过严过滤) * - 朋友圈使用SNS数据库 * - 标签使用正确DB * - 群发消息完善 * - 添加conversation读取 */ 'use strict'; var _wechatLoader = null; var _capturedStack = []; var _snsDB = null; // ============================================================ // 工具函数 // ============================================================ function getLoader() { if (_wechatLoader) return _wechatLoader; Java.enumerateClassLoaders({ onMatch: function(loader) { if (_wechatLoader) return; try { loader.loadClass('com.tencent.mm.app.Application'); _wechatLoader = loader; } catch(e) {} }, onComplete: function() {} }); return _wechatLoader; } function getOpenDB(dbName) { var targetDB = null; var searchName = dbName || 'EnMicroMsg'; Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { onMatch: function(db) { if (targetDB) return; try { var path = db.getPath(); if (path && path.indexOf(searchName) !== -1 && db.isOpen()) { targetDB = db; } } catch(e) {} }, onComplete: function() {} }); return targetDB; } function getAllOpenDBs() { var dbs = []; Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { onMatch: function(db) { try { var path = db.getPath(); if (path && db.isOpen()) { dbs.push({ path: path, open: true, readonly: db.isReadOnly() }); } } catch(e) {} }, onComplete: function() {} }); return dbs; } function execQuery(sql, dbName) { var db = getOpenDB(dbName); if (!db) return { success: false, error: 'no open DB: ' + (dbName || 'EnMicroMsg'), rows: [] }; try { var cursor = db.rawQuery(sql, null); var cols = cursor.getColumnNames(); var rows = []; while (cursor.moveToNext()) { var row = {}; for (var i = 0; i < cols.length; i++) { try { row[cols[i]] = cursor.getString(i); } catch(e) { row[cols[i]] = null; } } rows.push(row); } cursor.close(); return { success: true, rows: rows, count: rows.length }; } catch(e) { return { success: false, error: String(e).substring(0, 200), rows: [] }; } } function execInsert(table, kvMap, dbName) { var db = getOpenDB(dbName); if (!db) return { success: false, error: 'no open DB: ' + (dbName || 'EnMicroMsg') }; try { var ContentValues = Java.use('android.content.ContentValues'); var cv = ContentValues.$new(); var keys = Object.keys(kvMap); for (var i = 0; i < keys.length; i++) { var k = keys[i]; var v = kvMap[k]; if (v === null || v === undefined) continue; if (typeof v === 'number' && v > 2000000000) { cv.put(k, Java.use('java.lang.Long').$new(v)); } else if (typeof v === 'number') { cv.put(k, Java.use('java.lang.Integer').$new(v)); } else { cv.put(k, String(v)); } } var rowId = db.insert(table, null, cv); return { success: rowId > 0, row_id: rowId.toString() }; } catch(e) { return { success: false, error: String(e).substring(0, 200) }; } } function getCtx() { return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); } // ============================================================ // Hook:监听消息写入,捕获真实发送类调用栈 // ============================================================ Java.perform(function() { try { var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); WCDB.insert.overload( 'java.lang.String', 'java.lang.String', 'android.content.ContentValues' ).implementation = function(table, nullCol, values) { var result = this.insert(table, nullCol, values); if (table === 'message') { try { var isSend = values.getAsInteger('isSend'); if (isSend && parseInt(isSend.toString()) === 1) { var content = values.getAsString('content'); var talker = values.getAsString('talker'); var Exception = Java.use('java.lang.Exception'); var ex = Exception.$new('stack'); var stack = ex.getStackTrace(); var stackInfo = []; for (var i = 0; i < Math.min(stack.length, 30); i++) { var cn = stack[i].getClassName(); if (cn.indexOf('com.tencent.mm') !== -1) { stackInfo.push(cn + '.' + stack[i].getMethodName() + ':' + stack[i].getLineNumber()); } } _capturedStack = stackInfo; send({ type: 'real_send_captured', talker: talker, content: content ? content.substring(0, 60) : '', stack: stackInfo.slice(0, 8) }); } } catch(e2) {} } return result; }; send({ type: 'hook_ok', msg: 'WCDB insert hooked v4' }); } catch(e) { send({ type: 'hook_fail', msg: String(e).substring(0, 100) }); } send({ type: 'ready', msg: 'wechat_full_control v4 loaded' }); }); // ============================================================ // RPC接口 v4 // ============================================================ rpc.exports = { // ==================== 系统信息 ==================== getSystemInfo: function() { return Java.performNow(function() { var db = getOpenDB(); var dbs = getAllOpenDBs(); return { success: true, db: db ? { path: db.getPath(), open: true } : { open: false }, all_dbs: dbs, db_count: dbs.length, loader: getLoader() ? 'found' : 'not_found', captured_stack_count: _capturedStack.length, timestamp: Date.now() }; }); }, // ==================== 账号信息 ==================== getAccountInfo: function() { return Java.performNow(function() { // id: 2=nickname, 4=wxid, 6=mobile, 42=alias, 3=avatar var r = execQuery("SELECT id, value FROM userinfo WHERE id IN (2,3,4,6,42) ORDER BY id"); var info = {}; var idMap = { '2': 'nickname', '3': 'avatar', '4': 'wxid', '6': 'mobile', '42': 'alias' }; for (var i = 0; i < r.rows.length; i++) { var row = r.rows[i]; var key = idMap[row.id] || ('field_' + row.id); info[key] = row.value; } return { success: true, account: info }; }); }, // ==================== 联系人(完整) ==================== getContacts: function(params) { return Java.performNow(function() { var limit = (params && params.limit) || 200; var offset = (params && params.offset) || 0; // 修复:不过滤verifyFlag,只过滤type=3 var sql = "SELECT username, nickname, conRemark, type, verifyFlag, contactLabelIds FROM rcontact" + " WHERE type=3" + " AND username NOT IN ('filehelper','weixin','brandsessionholder','medianote','floatbottle')" + " ORDER BY conRemark, nickname" + " LIMIT " + limit + " OFFSET " + offset; var r = execQuery(sql); return { success: true, contacts: r.rows, count: r.count, offset: offset, limit: limit }; }); }, // 联系人总数 getContactsCount: function() { return Java.performNow(function() { var r1 = execQuery("SELECT COUNT(*) as cnt FROM rcontact WHERE type=3"); var r2 = execQuery("SELECT COUNT(*) as cnt FROM rcontact"); var r3 = execQuery("SELECT COUNT(*) as cnt FROM chatroom"); return { success: true, type3_friends: r1.rows.length > 0 ? r1.rows[0].cnt : 0, total_rcontact: r2.rows.length > 0 ? r2.rows[0].cnt : 0, groups: r3.rows.length > 0 ? r3.rows[0].cnt : 0 }; }); }, // ==================== 群组 ==================== getGroups: function(params) { return Java.performNow(function() { var limit = (params && params.limit) || 100; var sql = "SELECT chatroomname, displayname, chatroomnick, memberCount, roomowner, chatroomnotice FROM chatroom LIMIT " + limit; var r = execQuery(sql); return { success: true, groups: r.rows, count: r.count }; }); }, // 群成员 getGroupMembers: function(params) { return Java.performNow(function() { var groupId = params && params.group_id; if (!groupId) return { success: false, error: 'no group_id' }; var r = execQuery("SELECT memberlist, chatroomnick, memberCount FROM chatroom WHERE chatroomname='" + groupId.replace(/'/g, "''") + "' LIMIT 1"); if (r.rows.length === 0) return { success: false, error: 'group not found' }; var memberList = r.rows[0].memberlist || ''; var members = memberList ? memberList.split(';').filter(function(m) { return m.length > 0; }) : []; return { success: true, group_id: groupId, group_name: r.rows[0].chatroomnick || groupId, member_ids: members, count: members.length, db_count: r.rows[0].memberCount }; }); }, // ==================== 消息发送(WCDB直接写入) ==================== sendMessage: function(params) { return Java.performNow(function() { var toId = params && params.to_id; var content = params && params.content; if (!toId || !content) return { success: false, error: 'missing to_id or content' }; var now = Date.now(); var r = execInsert('message', { msgId: now, type: 1, isSend: 1, talker: toId, content: content, createTime: now, status: 2, imgPath: '', lvbuffer: '' }); if (r.success) { send({ type: 'message_sent', to_id: toId, content: content.substring(0, 50), method: 'wcdb_insert' }); } return { success: r.success, message_id: r.success ? String(now) : null, method: 'wcdb_direct_insert', to_id: toId, error: r.error }; }); }, // ==================== 群发消息 ==================== massMessage: function(params) { return Java.performNow(function() { var targets = params && params.targets; var content = params && params.content; if (!targets || !content) return { success: false, error: 'missing targets or content' }; var now = Date.now(); var results = []; for (var i = 0; i < targets.length; i++) { var toId = targets[i]; if (!toId) continue; var msgId = now + i; var r = execInsert('message', { msgId: msgId, type: 1, isSend: 1, talker: toId, content: content, createTime: now + i, status: 2, imgPath: '', lvbuffer: '' }); results.push({ to_id: toId, success: r.success, msg_id: String(msgId), error: r.error }); } var successCount = results.filter(function(r) { return r.success; }).length; return { success: successCount > 0, total: targets.length, success_count: successCount, results: results, method: 'wcdb_mass_insert' }; }); }, // ==================== 消息读取 ==================== getMessages: function(params) { return Java.performNow(function() { var talker = params && params.talker; var limit = (params && params.limit) || 50; var sql = "SELECT msgId, type, talker, content, createTime, isSend, status FROM message" + (talker ? " WHERE talker='" + talker.replace(/'/g, "''") + "'" : '') + " ORDER BY createTime DESC LIMIT " + limit; var r = execQuery(sql); return { success: true, messages: r.rows, count: r.count }; }); }, // ==================== 最近会话 ==================== getConversations: function(params) { return Java.performNow(function() { var limit = (params && params.limit) || 20; var r = execQuery("SELECT username, content, createTime, unReadCount, isSend FROM conversation ORDER BY createTime DESC LIMIT " + limit); return { success: true, conversations: r.rows, count: r.count }; }); }, // ==================== 朋友圈(SNS数据库) ==================== getMoments: function(params) { return Java.performNow(function() { var limit = (params && params.limit) || 20; // 先找SNS数据库 var dbs = getAllOpenDBs(); var snsDbName = null; for (var i = 0; i < dbs.length; i++) { var p = dbs[i].path; if (p && (p.indexOf('snsdb') !== -1 || p.indexOf('SNS') !== -1 || p.indexOf('sns') !== -1)) { snsDbName = p.split('/').pop().replace('.db', ''); break; } } if (snsDbName) { var r = execQuery("SELECT snsId, username, content, createTime FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit, snsDbName); return { success: true, moments: r.rows, count: r.count, db: snsDbName }; } // 尝试在主DB查 var r2 = execQuery("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%Sns%'"); return { success: false, error: 'SNS DB not found', tables: r2.rows, all_dbs: dbs.map(function(d) { return d.path; }) }; }); }, // 发朋友圈(通过Intent启动微信发布界面) postMoment: function(params) { return Java.performNow(function() { var content = params && params.content; if (!content) return { success: false, error: 'no content' }; try { var ctx = getCtx(); var Intent = Java.use('android.content.Intent'); var intent = Intent.$new(); // 微信朋友圈发布Activity intent.setClassName('com.tencent.mm', 'com.tencent.mm.plugin.sns.ui.SnsUploadUI'); intent.putExtra('Ksnsupload_type', 1); intent.putExtra('Ksnsupload_content', content); intent.addFlags(0x10000000); ctx.startActivity(intent); return { success: true, method: 'intent_SnsUploadUI', content: content.substring(0, 50) }; } catch(e) { // 备用:SnsActivity try { var ctx2 = getCtx(); var Intent2 = Java.use('android.content.Intent'); var intent2 = Intent2.$new(); intent2.setClassName('com.tencent.mm', 'com.tencent.mm.plugin.sns.ui.SnsActivity'); intent2.putExtra('Ksnsupload_type', 1); intent2.addFlags(0x10000000); ctx2.startActivity(intent2); return { success: true, method: 'intent_SnsActivity_fallback', content: content.substring(0, 50) }; } catch(e2) { return { success: false, error: String(e) + ' | ' + String(e2) }; } } }); }, // ==================== 标签管理 ==================== getLabels: function() { return Java.performNow(function() { // 标签在rcontact的contactLabelIds字段,或独立的label表 // 先查所有DB中的label表 var dbs = getAllOpenDBs(); for (var i = 0; i < dbs.length; i++) { var dbPath = dbs[i].path; var dbName = dbPath.split('/').pop().replace('.db', ''); var r = execQuery("SELECT * FROM label LIMIT 100", dbName); if (r.success && r.count > 0) { return { success: true, labels: r.rows, count: r.count, db: dbName }; } } // 从rcontact的contactLabelIds提取标签ID var r2 = execQuery("SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 100"); var labelIds = new Set(); for (var j = 0; j < r2.rows.length; j++) { var ids = r2.rows[j].contactLabelIds; if (ids) { ids.split(',').forEach(function(id) { if (id.trim()) labelIds.add(id.trim()); }); } } return { success: true, labels: Array.from(labelIds).map(function(id) { return { labelId: id }; }), count: labelIds.size, source: 'contactLabelIds', note: 'label table empty, extracted from rcontact.contactLabelIds' }; }); }, // 获取标签成员 getLabelMembers: function(params) { return Java.performNow(function() { var labelId = params && params.label_id; if (!labelId) return { success: false, error: 'no label_id' }; var r = execQuery("SELECT username, nickname, conRemark FROM rcontact WHERE contactLabelIds LIKE '%" + labelId + "%' LIMIT 200"); return { success: true, label_id: labelId, members: r.rows, count: r.count }; }); }, // ==================== 搜索 ==================== searchContacts: function(params) { return Java.performNow(function() { var keyword = params && params.keyword; if (!keyword) return { success: false, error: 'no keyword' }; 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 + "%')" + " LIMIT 50"; var r = execQuery(sql); return { success: true, results: r.rows, count: r.count }; }); }, searchMessages: function(params) { return Java.performNow(function() { var keyword = params && params.keyword; if (!keyword) return { success: false, error: 'no keyword' }; var kw = keyword.replace(/'/g, "''"); var sql = "SELECT msgId, talker, content, createTime, isSend FROM message" + " WHERE content LIKE '%" + kw + "%'" + " ORDER BY createTime DESC LIMIT 50"; var r = execQuery(sql); return { success: true, results: r.rows, count: r.count }; }); }, // ==================== 好友操作 ==================== addFriend: function(params) { return Java.performNow(function() { var wxid = params && params.wxid; if (!wxid) return { success: false, error: 'no wxid' }; try { var ctx = getCtx(); var Intent = Java.use('android.content.Intent'); var intent = Intent.$new(); intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.contact.AddContactUI'); intent.putExtra('search_str', wxid); intent.addFlags(0x10000000); ctx.startActivity(intent); return { success: true, method: 'intent_AddContactUI', wxid: wxid }; } catch(e) { return { success: false, error: String(e) }; } }); }, setRemark: function(params) { return Java.performNow(function() { var wxid = params && params.wxid; var remark = params && params.remark; if (!wxid || !remark) return { success: false, error: 'missing params' }; var db = getOpenDB(); if (!db) return { success: false, error: 'no db' }; try { var ContentValues = Java.use('android.content.ContentValues'); var cv = ContentValues.$new(); cv.put('conRemark', remark); var rows = db.update('rcontact', cv, "username='" + wxid.replace(/'/g, "''") + "'", null); return { success: rows > 0, affected_rows: rows, wxid: wxid, remark: remark }; } catch(e) { return { success: false, error: String(e) }; } }); }, // ==================== 原始SQL ==================== rawQuery: function(params) { return Java.performNow(function() { var sql = params && params.sql; var dbName = params && params.db; if (!sql) return { success: false, error: 'no sql' }; return execQuery(sql, dbName); }); }, // ==================== DB信息 ==================== getDbInfo: function() { return Java.performNow(function() { var dbs = getAllOpenDBs(); return { success: true, instances: dbs, count: dbs.length }; }); }, // ==================== 导航 ==================== navigateToChat: function(params) { return Java.performNow(function() { var wxid = params && params.wxid; if (!wxid) return { success: false, error: 'no wxid' }; try { var ctx = getCtx(); var Intent = Java.use('android.content.Intent'); var intent = Intent.$new(); intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.chatting.ChattingUI'); intent.putExtra('Chat_User', wxid); intent.addFlags(0x10000000); ctx.startActivity(intent); return { success: true, method: 'intent_ChattingUI', wxid: wxid }; } catch(e) { return { success: false, error: String(e) }; } }); }, // ==================== 获取调用栈 ==================== getCapturedStack: function() { return { stack: _capturedStack, count: _capturedStack.length }; }, // ==================== 枚举所有DB中的表 ==================== listAllTables: function() { return Java.performNow(function() { var dbs = getAllOpenDBs(); var result = {}; for (var i = 0; i < dbs.length; i++) { var dbPath = dbs[i].path; var dbName = dbPath.split('/').pop().replace('.db', ''); var r = execQuery("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", dbName); result[dbName] = r.rows.map(function(row) { return row.name; }); } return { success: true, databases: result }; }); } };