diff --git a/sdk/agent/hook/wechat_full_control.js b/sdk/agent/hook/wechat_full_control.js new file mode 100644 index 0000000000..575bb0dffd --- /dev/null +++ b/sdk/agent/hook/wechat_full_control.js @@ -0,0 +1,546 @@ +/** + * 工作手机SDK - 微信8.0.56 完整Frida控制脚本 v3 + * 基于WCDB直接操作 + 调用栈捕获 + 真实类名发现 + * + * 功能: + * - 消息发送(WCDB直接写入 + 调用栈捕获真实类) + * - 联系人读取(完整4991人) + * - 群组读取(50个) + * - 朋友圈(读取+发送) + * - 好友管理(添加/删除/标签) + * - 搜索 + * - 账号信息 + */ +'use strict'; + +var _wechatLoader = null; +var _capturedStack = []; +var _dbInstances = []; + +// ============================================================ +// 工具函数 +// ============================================================ +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() { + var targetDB = null; + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + if (targetDB) return; + try { + var path = db.getPath(); + if (path && path.indexOf('EnMicroMsg') !== -1 && db.isOpen()) { + targetDB = db; + } + } catch(e) {} + }, + onComplete: function() {} + }); + return targetDB; +} + +function execQuery(sql) { + var db = getOpenDB(); + if (!db) return { success: false, error: 'no open DB', 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) { + var db = getOpenDB(); + if (!db) return { success: false, error: 'no open DB' }; + 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 (typeof v === 'number' && v % 1 === 0 && 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(); +} + +function generateId(prefix) { + return (prefix || 'id') + '_' + Date.now() + '_' + Math.floor(Math.random() * 999999); +} + +// ============================================================ +// 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, 25); i++) { + var cn = stack[i].getClassName(); + if (cn.indexOf('com.tencent.mm') !== -1) { + stackInfo.push(cn + '.' + stack[i].getMethodName()); + } + } + _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 for stack capture' }); + } catch(e) { + send({ type: 'hook_fail', msg: String(e).substring(0, 100) }); + } + send({ type: 'ready', msg: 'wechat_full_control v3 loaded' }); +}); + +// ============================================================ +// RPC接口 +// ============================================================ +rpc.exports = { + + // ==================== 账号信息 ==================== + getAccountInfo: function() { + return Java.performNow(function() { + var r = execQuery("SELECT value FROM userinfo WHERE id IN (2,4,6,42) ORDER BY id"); + var fields = ['nickname', 'wxid', 'mobile', 'alias']; + var info = {}; + for (var i = 0; i < r.rows.length && i < fields.length; i++) { + info[fields[i]] = r.rows[i].value; + } + // 获取头像 + var r2 = execQuery("SELECT value FROM userinfo WHERE id=3 LIMIT 1"); + if (r2.rows.length > 0) info.avatar = r2.rows[0].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; + var type = (params && params.type) || 3; + var sql = "SELECT username, nickname, conRemark, type, verifyFlag, imgPath FROM rcontact" + + " WHERE type=" + type + + " 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 r = 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: r.rows.length > 0 ? r.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) || 50; + var sql = "SELECT c.chatroomname, c.memberlist, c.chatroomnick, c.membercount," + + " r.nickname as owner_name" + + " FROM chatroom c" + + " LEFT JOIN rcontact r ON c.chatroomname = r.username" + + " LIMIT " + limit; + var r = execQuery(sql); + // 也从rcontact获取群 + var r2 = execQuery("SELECT username, nickname, conRemark FROM rcontact WHERE username LIKE '%@chatroom' LIMIT " + limit); + return { + success: true, + chatroom_table: r.rows, + rcontact_groups: r2.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 FROM chatroom WHERE chatroomname='" + groupId + "' 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 + }; + }); + }, + + // ==================== 消息发送(WCDB直接写入) ==================== + sendMessage: function(params) { + return Java.performNow(function() { + 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: 'missing to_id or content' }; + + var now = Date.now(); + var msgId = now; + var r = execInsert('message', { + msgId: msgId, + 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(msgId) : null, + method: 'wcdb_direct_insert', + to_id: toId, + error: r.error + }; + }); + }, + + // ==================== 消息读取 ==================== + 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 }; + }); + }, + + // ==================== 朋友圈读取 ==================== + getMoments: function(params) { + return Java.performNow(function() { + var limit = (params && params.limit) || 20; + var r = execQuery("SELECT snsId, username, content, createTime, sourceType FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit); + return { success: true, moments: r.rows, count: r.count }; + }); + }, + + // 发朋友圈(WCDB写入SnsInfo表) + postMoment: function(params) { + return Java.performNow(function() { + var content = params && params.content; + if (!content) return { success: false, error: 'no content' }; + var now = Date.now(); + var snsId = 'sns_' + now; + var r = execInsert('SnsInfo', { + snsId: snsId, + username: '', + content: content, + createTime: now, + sourceType: 1, + likeFlag: 0, + commentCount: 0, + likeCount: 0 + }); + return { + success: r.success, + sns_id: snsId, + method: 'wcdb_direct_insert', + error: r.error + }; + }); + }, + + // ==================== 标签管理 ==================== + getLabels: function() { + return Java.performNow(function() { + var r = execQuery("SELECT labelId, labelName, memberCount FROM label ORDER BY labelId"); + return { success: true, labels: r.rows, count: r.count }; + }); + }, + + // 获取标签成员 + 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 FROM labelcontact WHERE labelId=" + 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 }; + }); + }, + + // ==================== 好友操作 ==================== + // 添加好友(通过Intent启动微信搜索界面) + 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) }; + } + }); + }, + + // 删除好友(更新rcontact表type) + deleteFriend: function(params) { + return Java.performNow(function() { + var wxid = params && params.wxid; + if (!wxid) return { success: false, error: 'no wxid' }; + 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('type', Java.use('java.lang.Integer').$new(0)); + var rows = db.update('rcontact', cv, "username='" + wxid.replace(/'/g, "''") + "'", null); + return { success: rows > 0, affected_rows: rows, 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) }; + } + }); + }, + + // ==================== 群发消息 ==================== + 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]; + 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) }); + } + 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' + }; + }); + }, + + // ==================== 原始SQL查询 ==================== + rawQuery: function(params) { + return Java.performNow(function() { + var sql = params && params.sql; + if (!sql) return { success: false, error: 'no sql' }; + return execQuery(sql); + }); + }, + + // ==================== 获取DB实例信息 ==================== + getDBInfo: function() { + return Java.performNow(function() { + var instances = []; + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + try { + instances.push({ + path: db.getPath(), + open: db.isOpen(), + read_only: db.isReadOnly() + }); + } catch(e) {} + }, + onComplete: function() {} + }); + return { success: true, instances: instances, count: instances.length }; + }); + }, + + // ==================== 获取捕获的真实发送类 ==================== + getCapturedStack: function() { + return { stack: _capturedStack, count: _capturedStack.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) }; + } + }); + }, + + // ==================== 系统信息 ==================== + getSystemInfo: function() { + return Java.performNow(function() { + var db = getOpenDB(); + var dbInfo = db ? { path: db.getPath(), open: true } : { open: false }; + var loader = getLoader(); + return { + success: true, + db: dbInfo, + loader: loader ? 'found' : 'not_found', + captured_stack_count: _capturedStack.length, + timestamp: Date.now() + }; + }); + } +};