feat: 完整Frida控制脚本v3 sdk/agent/hook/wechat_full_control.js
This commit is contained in:
546
sdk/agent/hook/wechat_full_control.js
Normal file
546
sdk/agent/hook/wechat_full_control.js
Normal file
@@ -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()
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user