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

604 lines
25 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 v8-fix - 全量Frida RPC接口
// 用Java.choose找到已加载的DB实例已验证有效
// 账号:游条姐 (wxid_5g37snchpv8e22)
'use strict';
var _dbs = {}; // name -> db instance
// ==================== DB初始化 ====================
function initAllDBs() {
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var p = db.getPath();
if (!p) return;
var name = p.split('/').pop().replace('.db','');
if (p.indexOf('EnMicroMsg') > -1) _dbs['main'] = db;
else if (p.indexOf('SnsMicroMsg') > -1) _dbs['sns'] = db;
else if (p.indexOf('sns_star_info') > -1) _dbs['sns_star'] = db;
else if (p.indexOf('WalletCoreDB-2') > -1) _dbs['wallet'] = db;
else if (p.indexOf('WalletPay') > -1) _dbs['wallet2'] = db;
else if (p.indexOf('FinderMM029') > -1) _dbs['finder'] = db;
else if (p.indexOf('FinderMessage006') > -1) _dbs['finder2'] = db;
else if (p.indexOf('FinderAccounts_01') > -1) _dbs['finder3'] = db;
else if (p.indexOf('enFavorite') > -1) _dbs['favorite'] = db;
else if (p.indexOf('FTS5IndexMicroMsg') > -1) _dbs['fts5'] = db;
else if (p.indexOf('AppBrandComm') > -1) _dbs['appbrand'] = db;
else if (p.indexOf('WxFileIndex') > -1) _dbs['wxfile'] = db;
_dbs['_path_' + name] = db;
} catch(e) {}
},
onComplete: function() {}
});
}
function getDB(name) {
if (!_dbs[name]) initAllDBs();
return _dbs[name] || null;
}
function execSQL(db, sql, args) {
if (!db) return {error: 'DB not available', rows: [], count: 0};
try {
var cursor;
if (args && args.length > 0) {
var jArgs = Java.array('java.lang.String', args.map(function(a) { return a ? String(a) : null; }));
cursor = db.rawQuery(sql, jArgs);
} else {
cursor = db.rawQuery(sql, null);
}
var cols = [];
var colCount = cursor.getColumnCount();
for (var i = 0; i < colCount; i++) cols.push(cursor.getColumnName(i));
var rows = [];
while (cursor.moveToNext()) {
var row = {};
for (var j = 0; j < colCount; j++) {
try { row[cols[j]] = cursor.getString(j); } catch(e) { row[cols[j]] = null; }
}
rows.push(row);
}
cursor.close();
return {columns: cols, rows: rows, count: rows.length};
} catch(e) {
return {error: e.toString(), rows: [], count: 0};
}
}
function writeSQL(db, sql) {
if (!db) return {error: 'DB not available'};
try {
db.execSQL(sql);
return {success: true};
} catch(e) {
return {error: e.toString()};
}
}
// ==================== RPC接口 ====================
rpc.exports = {
// ===== 系统 =====
ping: function() {
return {status: 'ok', version: 'v8-fix', timestamp: Date.now()};
},
initDbs: function() {
initAllDBs();
var status = {};
['main','sns','sns_star','wallet','wallet2','finder','finder2','finder3','favorite','fts5','appbrand'].forEach(function(n) {
status[n] = !!_dbs[n];
});
return {status: status, total_dbs: Object.keys(_dbs).length};
},
listAllDbs: function() {
initAllDBs();
var list = [];
Object.keys(_dbs).forEach(function(k) {
if (!k.startsWith('_path_')) return;
try {
var db = _dbs[k];
var p = db.getPath();
list.push({name: k.replace('_path_',''), path: p});
} catch(e) {}
});
return {dbs: list, count: list.length};
},
// ===== 账号 =====
getAccountInfo: function() {
var db = getDB('main');
var wxid = execSQL(db, "SELECT value FROM userinfo WHERE id=2");
var nick = execSQL(db, "SELECT value FROM userinfo WHERE id=6");
var avatar = execSQL(db, "SELECT value FROM userinfo WHERE id=4");
var phone = execSQL(db, "SELECT value FROM userinfo WHERE id=42");
var region = execSQL(db, "SELECT value FROM userinfo WHERE id=44");
var sex = execSQL(db, "SELECT value FROM userinfo WHERE id=12");
return {
wxid: wxid.rows[0] ? wxid.rows[0].value : null,
nickname: nick.rows[0] ? nick.rows[0].value : null,
avatar: avatar.rows[0] ? avatar.rows[0].value : null,
phone: phone.rows[0] ? phone.rows[0].value : null,
region: region.rows[0] ? region.rows[0].value : null,
sex: sex.rows[0] ? sex.rows[0].value : null,
};
},
// ===== 联系人 =====
getContacts: function(limit, offset) {
var db = getDB('main');
limit = limit || 100; offset = offset || 0;
return execSQL(db,
"SELECT username, alias, conRemark, nickName, type, contactLabelIds, imgFlag " +
"FROM rcontact WHERE type=3 AND verifyFlag=0 ORDER BY rowid LIMIT " + limit + " OFFSET " + offset
);
},
getContactCount: function() {
var db = getDB('main');
var r1 = execSQL(db, "SELECT COUNT(*) as friends FROM rcontact WHERE type=3 AND verifyFlag=0");
var r2 = execSQL(db, "SELECT COUNT(*) as total FROM rcontact");
return {
friends: r1.rows[0] ? r1.rows[0].friends : 0,
total: r2.rows[0] ? r2.rows[0].total : 0
};
},
getAllStats: function() {
var db = getDB('main');
var friends = execSQL(db, "SELECT COUNT(*) as n FROM rcontact WHERE type=3 AND verifyFlag=0");
var groups = execSQL(db, "SELECT COUNT(*) as n FROM chatroom");
var msgs = execSQL(db, "SELECT COUNT(*) as n FROM message");
var imgs = execSQL(db, "SELECT COUNT(*) as n FROM ImgInfo2");
var videos = execSQL(db, "SELECT COUNT(*) as n FROM videoinfo2");
var conv = execSQL(db, "SELECT COUNT(*) as n FROM conversation");
var files = execSQL(db, "SELECT COUNT(*) as n FROM FileMsgInfo");
return {
friends: friends.rows[0] ? parseInt(friends.rows[0].n) : 0,
groups: groups.rows[0] ? parseInt(groups.rows[0].n) : 0,
messages: msgs.rows[0] ? parseInt(msgs.rows[0].n) : 0,
images: imgs.rows[0] ? parseInt(imgs.rows[0].n) : 0,
videos: videos.rows[0] ? parseInt(videos.rows[0].n) : 0,
conversations: conv.rows[0] ? parseInt(conv.rows[0].n) : 0,
files: files.rows[0] ? parseInt(files.rows[0].n) : 0,
};
},
searchContact: function(keyword) {
var db = getDB('main');
return execSQL(db,
"SELECT username, alias, conRemark, nickName, type FROM rcontact " +
"WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' OR alias LIKE '%" + keyword + "%') " +
"AND type NOT IN (0,4) LIMIT 50"
);
},
getContactDetail: function(wxid) {
var db = getDB('main');
return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1");
},
// ===== 消息 =====
getMessages: function(talker, limit) {
var db = getDB('main');
limit = limit || 50;
return execSQL(db,
"SELECT msgId, type, isSend, createTime, talker, content FROM message " +
"WHERE talker='" + talker + "' ORDER BY createTime DESC LIMIT " + limit
);
},
getRecentMessages: function(hours) {
var db = getDB('main');
hours = hours || 24;
var since = Date.now() - hours * 3600 * 1000;
return execSQL(db,
"SELECT msgId, type, isSend, createTime, talker, content FROM message " +
"WHERE createTime > " + since + " ORDER BY createTime DESC LIMIT 200"
);
},
sendMessage: function(toUser, content) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var msgId = Date.now();
var createTime = Date.now();
var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" +
msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)";
var result = writeSQL(db, sql);
if (result.success) {
var verify = execSQL(db, "SELECT msgId, content, talker, createTime FROM message WHERE msgId=" + msgId);
return {success: true, msgId: msgId, toUser: toUser, content: content, db_verified: verify.count > 0, note: 'Written to local DB. Network sync requires real class name.'};
}
return result;
},
massMessage: function(targets, content) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var results = [];
targets.forEach(function(toUser) {
var msgId = Date.now() + Math.floor(Math.random() * 100000);
var createTime = Date.now();
var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" +
msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)";
var r = writeSQL(db, sql);
results.push({toUser: toUser, msgId: msgId, success: !!r.success, error: r.error || null});
});
return {results: results, total: targets.length, success_count: results.filter(function(r) { return r.success; }).length};
},
// ===== 会话 =====
getConversations: function(limit) {
var db = getDB('main');
limit = limit || 30;
return execSQL(db,
"SELECT username, unReadCount, lastMsg, lastMsgCreateTime, isSend FROM conversation " +
"ORDER BY lastMsgCreateTime DESC LIMIT " + limit
);
},
// ===== 群组 =====
getGroups: function(limit) {
var db = getDB('main');
limit = limit || 50;
return execSQL(db,
"SELECT chatroomname, memberlist, roomowner, announcement, selfDisplayName, memberCount " +
"FROM chatroom ORDER BY rowid DESC LIMIT " + limit
);
},
getGroupCount: function() {
var db = getDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM chatroom");
},
getGroupDetail: function(chatroomId) {
var db = getDB('main');
return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1");
},
getGroupMembers: function(chatroomId) {
var db = getDB('main');
var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName, memberCount FROM chatroom WHERE chatroomname='" + chatroomId + "'");
if (group.rows && group.rows[0]) {
var memberList = group.rows[0].memberlist || '';
var members = memberList.split(';').filter(function(m) { return m.trim(); });
return {
chatroomId: chatroomId,
members: members,
count: members.length,
owner: group.rows[0].roomowner,
announcement: group.rows[0].announcement,
myName: group.rows[0].selfDisplayName,
memberCount: group.rows[0].memberCount
};
}
return {error: 'Group not found'};
},
setGroupRemark: function(chatroomId, remark) {
var db = getDB('main');
return writeSQL(db, "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'");
},
setGroupAnnouncement: function(chatroomId, announcement) {
var db = getDB('main');
return writeSQL(db, "UPDATE chatroom SET announcement='" + announcement.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'");
},
// ===== 标签 =====
getLabels: function() {
var db = getDB('main');
var r = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 2000");
var labelSet = {};
if (r.rows) {
r.rows.forEach(function(row) {
var ids = (row.contactLabelIds || '').split(',');
ids.forEach(function(id) { if (id.trim()) labelSet[id.trim()] = (labelSet[id.trim()] || 0) + 1; });
});
}
return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length, usage: labelSet};
},
getContactsByLabel: function(labelId) {
var db = getDB('main');
return execSQL(db,
"SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " +
"WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100"
);
},
// ===== 朋友圈 =====
getSnsDbInfo: function() {
initAllDBs();
var result = {};
['sns','sns_star'].forEach(function(name) {
var db = _dbs[name];
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
result[name] = {
available: true,
path: db.getPath(),
tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []
};
} else {
result[name] = {available: false};
}
});
return result;
},
getMoments: function(limit) {
limit = limit || 20;
var db = getDB('sns');
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
if (tableNames.indexOf('SnsInfo') > -1) {
var r = execSQL(db, "SELECT snsId, userName, createTime, content, type, localFlag FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit);
r.db = 'SnsMicroMsg';
return r;
}
}
var db2 = getDB('sns_star');
if (db2) {
var r2 = execSQL(db2, "SELECT * FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit);
r2.db = 'sns_star_info';
return r2;
}
return {error: 'SNS DB not loaded. Need to open WeChat moments first.', rows: [], count: 0};
},
getMomentCount: function() {
var db = getDB('sns');
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
if (tableNames.indexOf('SnsInfo') > -1) {
return execSQL(db, "SELECT COUNT(*) as total FROM SnsInfo");
}
}
var db2 = getDB('sns_star');
if (db2) {
return execSQL(db2, "SELECT COUNT(*) as total FROM StarSnsInfo");
}
return {error: 'SNS DB not loaded', count: 0};
},
postMoment: function(content) {
var db = getDB('sns');
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
if (tableNames.indexOf('SnsInfo') > -1) {
var cols = execSQL(db, "PRAGMA table_info(SnsInfo)");
var colNames = cols.rows ? cols.rows.map(function(c) { return c.name; }) : [];
var snsId = Date.now();
var createTime = Math.floor(Date.now() / 1000);
var insertCols = ['snsId', 'userName', 'createTime', 'content'];
var insertVals = [snsId, "'wxid_5g37snchpv8e22'", createTime, "'" + content.replace(/'/g, "''") + "'"];
if (colNames.indexOf('type') > -1) { insertCols.push('type'); insertVals.push(1); }
if (colNames.indexOf('localFlag') > -1) { insertCols.push('localFlag'); insertVals.push(1); }
var sql = "INSERT INTO SnsInfo (" + insertCols.join(',') + ") VALUES (" + insertVals.join(',') + ")";
var r = writeSQL(db, sql);
if (r.success) {
var verify = execSQL(db, "SELECT snsId, content FROM SnsInfo WHERE snsId=" + snsId);
return {success: true, snsId: snsId, content: content, db_verified: verify.count > 0, db: 'SnsMicroMsg'};
}
return r;
}
}
var db2 = getDB('sns_star');
if (db2) {
var snsId2 = Date.now();
var createTime2 = Math.floor(Date.now() / 1000);
var sql2 = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" +
snsId2 + ", 'wxid_5g37snchpv8e22', 1, " + createTime2 + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)";
var r2 = writeSQL(db2, sql2);
if (r2.success) {
var verify2 = execSQL(db2, "SELECT snsId, content FROM StarSnsInfo WHERE snsId=" + snsId2);
return {success: true, snsId: snsId2, content: content, db_verified: verify2.count > 0, db: 'sns_star_info'};
}
return r2;
}
return {error: 'SNS DB not loaded. Open WeChat moments first.', success: false};
},
// ===== 视频号 =====
getFinderInfo: function() {
initAllDBs();
var result = {};
['finder','finder2','finder3'].forEach(function(name) {
var db = _dbs[name];
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
result[name] = {
available: true,
path: db.getPath().split('/').pop(),
tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []
};
} else {
result[name] = {available: false};
}
});
return result;
},
getFinderContacts: function(limit) {
var db = getDB('finder');
limit = limit || 20;
if (!db) return {error: 'FinderMM029 DB not available'};
return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT " + limit);
},
getFinderMedia: function(limit) {
var db = getDB('finder');
limit = limit || 20;
if (!db) return {error: 'FinderMM029 DB not available'};
return execSQL(db, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT " + limit);
},
getFinderAccounts: function() {
var db = getDB('finder3');
if (!db) return {error: 'FinderAccounts DB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var result = {tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []};
(result.tables || []).forEach(function(t) {
try { result[t] = execSQL(db, "SELECT * FROM " + t + " LIMIT 5"); } catch(e) {}
});
return result;
},
// ===== 收藏 =====
getFavorites: function(limit) {
limit = limit || 20;
var db = getDB('favorite');
if (db) {
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
if (tableNames.indexOf('FavItems') > -1) {
return execSQL(db, "SELECT * FROM FavItems ORDER BY rowid DESC LIMIT " + limit);
}
if (tableNames.length > 0) {
return execSQL(db, "SELECT * FROM " + tableNames[0] + " LIMIT " + limit);
}
}
// 尝试FTS5
var db2 = getDB('fts5');
if (db2) {
var tables2 = execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames2 = tables2.rows ? tables2.rows.map(function(t) { return t.name; }) : [];
if (tableNames2.indexOf('FTS5MetaFavorite') > -1) {
return execSQL(db2, "SELECT * FROM FTS5MetaFavorite LIMIT " + limit);
}
}
return {error: 'Favorite DB not loaded', rows: [], count: 0};
},
// ===== 钱包/红包 =====
getWalletInfo: function() {
var db = getDB('wallet');
if (!db) return {error: 'WalletCoreDB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var result = {tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []};
return result;
},
getWalletLedger: function(limit) {
var db = getDB('wallet');
limit = limit || 20;
if (!db) return {error: 'Wallet DB not available'};
return execSQL(db,
"SELECT transferId, talker, msgTime, msgDate, tradeUser, tradeType, tradeAmount " +
"FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT " + limit
);
},
getLuckyMoney: function(limit) {
var db = getDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit);
},
getWalletUserInfo: function() {
var db = getDB('wallet');
if (!db) return {error: 'Wallet DB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
var result = {tables: tableNames};
tableNames.forEach(function(t) {
try {
var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 3");
if (!r.error && r.count > 0) result[t] = r;
} catch(e) {}
});
return result;
},
// ===== 图片/视频/文件 =====
getImageList: function(limit) {
var db = getDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getImageCount: function() {
var db = getDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2");
},
getVideoList: function(limit) {
var db = getDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getVideoCount: function() {
var db = getDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2");
},
getFileList: function(limit) {
var db = getDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit);
},
// ===== 小程序 =====
getAppBrandList: function() {
var db = getDB('appbrand');
if (!db) return {error: 'AppBrand DB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : [];
var result = {tables: tableNames};
tableNames.slice(0, 5).forEach(function(t) {
try {
var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 5");
if (!r.error) result[t] = r;
} catch(e) {}
});
return result;
},
// ===== 搜索 =====
globalSearch: function(keyword) {
var db = getDB('main');
var contacts = execSQL(db, "SELECT username, nickName, conRemark FROM rcontact WHERE nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' LIMIT 10");
var messages = execSQL(db, "SELECT talker, content, createTime FROM message WHERE content LIKE '%" + keyword + "%' ORDER BY createTime DESC LIMIT 10");
var groups = execSQL(db, "SELECT chatroomname FROM chatroom WHERE chatroomname LIKE '%" + keyword + "%' LIMIT 10");
return {contacts: contacts, messages: messages, groups: groups};
},
// ===== 原始SQL =====
rawQuery: function(dbName, sql) {
var db = getDB(dbName || 'main');
return execSQL(db, sql);
},
rawWrite: function(dbName, sql) {
var db = getDB(dbName || 'main');
return writeSQL(db, sql);
},
getTableInfo: function(dbName, tableName) {
var db = getDB(dbName || 'main');
var cols = execSQL(db, "PRAGMA table_info(" + tableName + ")");
var count = execSQL(db, "SELECT COUNT(*) as total FROM " + tableName);
var sample = execSQL(db, "SELECT * FROM " + tableName + " LIMIT 3");
return {columns: cols, count: count, sample: sample};
},
listTables: function(dbName) {
var db = getDB(dbName || 'main');
return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
},
};
// 启动时初始化
Java.perform(function() {
initAllDBs();
console.log('[*] WeChat SDK v8-fix loaded. DBs found: ' + Object.keys(_dbs).length);
});