feat: publish workphone SDK deployment and API docs

This commit is contained in:
Manus AI
2026-07-14 18:10:52 +08:00
commit 021d633cc1
534 changed files with 122391 additions and 0 deletions

View File

@@ -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 };
});
}
};