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

547 lines
21 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控制脚本 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()
};
});
}
};