diff --git a/sdk/agent/hook/wechat_full_control_v4.js b/sdk/agent/hook/wechat_full_control_v4.js new file mode 100644 index 0000000000..aad05fbb57 --- /dev/null +++ b/sdk/agent/hook/wechat_full_control_v4.js @@ -0,0 +1,592 @@ +/** + * 工作手机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 }; + }); + } +};