Files
workphone-sdk/sdk/agent/hook/wechat_full_control_v4.js

593 lines
24 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 工作手机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 };
});
}
};