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

55
sdk/agent/hook/README.md Normal file
View File

@@ -0,0 +1,55 @@
# 微信 Hook 设备端脚本Root 设备)
本目录用于 Root 安卓设备如红米11部署微信 Hook 脚本。
## 文件说明
- `wechat_hook_v1.js`:微信 Hook 初版脚本(含 RPC + 消息事件上报骨架)
- `wechat_hook_bridge.js`安全桥接模板ping / 连接状态 / 占位 RPC可替换为完整 `wechat_hook_v*.js`
- `setup_redmi11.sh`红米11 一键初始化脚本
## 快速执行
```bash
cd sdk/agent/hook
bash setup_redmi11.sh <device_id>
```
## 与后端接口联动
1. 上传脚本
```bash
curl -s -X POST "http://localhost:8899/api/v3/scripts?module_id=wechat_hook_v1&version=1.0.0" \
-F "file=@wechat_hook_v1.js" | python3 -m json.tool
```
2. 部署脚本到设备
```bash
curl -s -X POST "http://localhost:8899/api/v3/scripts/wechat_hook_v1_1.0.0/deploy" \
-H "Content-Type: application/json" \
-d '{"device_ids":["<device_id>"],"auto_reload":true}' | python3 -m json.tool
```
3. 检查设备模块状态
```bash
curl -s "http://localhost:8899/api/v3/devices/<device_id>/modules" | python3 -m json.tool
```
4. 查看 Hook 事件流WebSocket
`ws://localhost:8899/api/v3/hook/events/stream`
## 说明
当前 `wechat_hook_v1.js` 已具备:
- RPC通信骨架
- 进程信息读取
- SQLite消息表写入监听事件上报
下一步按微信版本补齐:
- `sendMessage` 真正发消息 Hook 点
- 联系人读取 DB 结构适配
- 好友请求/朋友圈/群管理扩展

View File

@@ -0,0 +1,22 @@
"""
Hook 模块 — Frida 注入通道
组件:
- FridaManager: 管理 Frida 与微信进程的连接
- HookExecutor: 将 unified 动作映射到 RPC 调用
- EventReporter: 将 Hook 事件上报到服务端
使用:
from hook import FridaManager, HookExecutor, EventReporter
mgr = FridaManager(on_event=reporter.on_hook_event)
mgr.start()
executor = HookExecutor(mgr)
result = executor.execute("send_message", {"to_id": "xxx", "content": "hello"})
"""
from .frida_manager import FridaManager
from .hook_executor import HookExecutor
from .event_reporter import EventReporter
__all__ = ["FridaManager", "HookExecutor", "EventReporter"]

View File

@@ -0,0 +1,86 @@
"""
Event Reporter — 将 Frida Hook 事件上报到 SDK 服务端
职责:
- 接收 FridaManager 回调的 hook_event
- 格式化为 SDK 服务端 WebSocket 协议
- 通过 Agent 的 WebSocket 连接上报
- 本地缓存(服务端离线时暂存)
"""
import time
import logging
import threading
from typing import Dict, Any, Optional, Callable, List
from collections import deque
logger = logging.getLogger(__name__)
MAX_BUFFER_SIZE = 500
class EventReporter:
"""Hook 事件上报器"""
def __init__(
self,
send_fn: Optional[Callable[[Dict[str, Any]], None]] = None,
buffer_size: int = MAX_BUFFER_SIZE,
):
self._send_fn = send_fn
self._buffer: deque = deque(maxlen=buffer_size)
self._stats = {"total": 0, "sent": 0, "buffered": 0, "errors": 0}
self._lock = threading.Lock()
def set_send_fn(self, fn: Callable[[Dict[str, Any]], None]):
self._send_fn = fn
self._flush_buffer()
def on_hook_event(self, payload: Dict[str, Any]):
event = {
"type": "hook_event",
"event_type": payload.get("event_type", "unknown"),
"platform": payload.get("platform", "wechat"),
"payload": payload.get("payload", {}),
"timestamp": payload.get("timestamp") or time.strftime("%Y-%m-%dT%H:%M:%S"),
}
with self._lock:
self._stats["total"] += 1
if self._send_fn:
try:
self._send_fn(event)
with self._lock:
self._stats["sent"] += 1
except Exception as e:
logger.warning(f"事件上报失败,暂存: {e}")
self._buffer.append(event)
with self._lock:
self._stats["buffered"] += 1
self._stats["errors"] += 1
else:
self._buffer.append(event)
with self._lock:
self._stats["buffered"] += 1
def _flush_buffer(self):
if not self._send_fn:
return
while self._buffer:
event = self._buffer.popleft()
try:
self._send_fn(event)
with self._lock:
self._stats["sent"] += 1
self._stats["buffered"] = max(0, self._stats["buffered"] - 1)
except Exception:
self._buffer.appendleft(event)
break
def get_stats(self) -> Dict[str, Any]:
with self._lock:
return dict(self._stats)
def get_buffered_events(self, limit: int = 50) -> List[Dict[str, Any]]:
return list(self._buffer)[:limit]

View File

@@ -0,0 +1,332 @@
/**
* 工作手机SDK - Frida Hook监听器
* 目标捕获微信8.0.56真实消息发送类名
* 方法Hook SQLite insert + Android.os.Handler + 调用栈分析
*/
'use strict';
var _foundSendClasses = [];
var _foundSnsClasses = [];
var _messageLog = [];
var _hookReady = false;
// ============================================================
// 工具函数
// ============================================================
function getStack() {
try {
var Exception = Java.use('java.lang.Exception');
var e = Exception.$new();
var stack = e.getStackTrace();
var result = [];
for (var i = 0; i < Math.min(stack.length, 20); i++) {
var frame = stack[i];
var cls = frame.getClassName();
var method = frame.getMethodName();
if (cls.indexOf('com.tencent.mm') !== -1) {
result.push(cls + '.' + method);
}
}
return result;
} catch(e) {
return ['stack_error: ' + e];
}
}
// ============================================================
// Hook 1: SQLite insert - 捕获消息写入调用栈
// ============================================================
function hookSQLiteInsert() {
var hooked = false;
// 尝试多个SQLite类
var sqliteClasses = [
'com.tencent.wcdb.database.SQLiteDatabase',
'net.sqlcipher.database.SQLiteDatabase',
'android.database.sqlite.SQLiteDatabase'
];
for (var i = 0; i < sqliteClasses.length; i++) {
try {
var SQLiteDB = Java.use(sqliteClasses[i]);
var clsName = sqliteClasses[i];
// Hook insert方法
try {
SQLiteDB.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 stack = getStack();
_messageLog.push({
type: 'message_insert',
table: table,
talker: talker,
content: content ? content.substring(0, 100) : '',
stack: stack,
time: Date.now()
});
send({ type: 'message_insert', talker: talker, content: content ? content.substring(0, 50) : '', stack: stack.slice(0, 5) });
}
} catch(e2) {}
}
if (table === 'SnsInfo' || table === 'SnsComment') {
var stack = getStack();
_foundSnsClasses = stack;
send({ type: 'sns_insert', table: table, stack: stack.slice(0, 5) });
}
return result;
};
send({ type: 'hook_ok', msg: 'SQLite.insert hooked: ' + clsName });
hooked = true;
} catch(e) {
send({ type: 'hook_fail', msg: clsName + '.insert: ' + e });
}
// Hook execSQL
try {
SQLiteDB.execSQL.overload('java.lang.String').implementation = function(sql) {
if (sql && sql.toLowerCase().indexOf('insert') !== -1 && sql.toLowerCase().indexOf('message') !== -1) {
var stack = getStack();
send({ type: 'execsql_message', sql: sql.substring(0, 100), stack: stack.slice(0, 5) });
}
return this.execSQL(sql);
};
} catch(e) {}
break;
} catch(e) {
send({ type: 'hook_fail', msg: sqliteClasses[i] + ': ' + e.toString().substring(0, 100) });
}
}
return hooked;
}
// ============================================================
// Hook 2: 尝试Hook已知的消息发送相关类
// ============================================================
function hookKnownClasses() {
// 微信8.0.x常见的消息发送类通过逆向分析得到的候选
var candidates = [
// 8.0.50+ 版本
{ cls: 'com.tencent.mm.model.chat.ChatManager', method: 'sendTextMessage' },
{ cls: 'com.tencent.mm.model.chat.ChatManager', method: 'a' },
{ cls: 'com.tencent.mm.sdk.platformtools.Util', method: 'sendMessage' },
// 通用模式
{ cls: 'com.tencent.mm.ui.chatting.ChattingUIFragment', method: 'sendTextMessage' },
{ cls: 'com.tencent.mm.ui.chatting.ChattingUIFragment', method: 'a' },
// 消息服务
{ cls: 'com.tencent.mm.plugin.messenger.service.MessageService', method: 'sendMessage' },
{ cls: 'com.tencent.mm.plugin.messenger.service.MessageService', method: 'a' },
];
var found = [];
for (var i = 0; i < candidates.length; i++) {
try {
var cls = Java.use(candidates[i].cls);
found.push(candidates[i].cls);
send({ type: 'class_found', cls: candidates[i].cls });
} catch(e) {
// 不存在,跳过
}
}
return found;
}
// ============================================================
// Hook 3: Hook Handler.dispatchMessage 捕获消息处理
// ============================================================
function hookHandlerDispatch() {
try {
var Handler = Java.use('android.os.Handler');
Handler.dispatchMessage.implementation = function(msg) {
this.dispatchMessage(msg);
};
// 不hook这个太频繁
} catch(e) {}
}
// ============================================================
// RPC接口
// ============================================================
rpc.exports = {
// 获取捕获的日志
getLogs: function() {
return { messages: _messageLog, sns: _foundSnsClasses, send_classes: _foundSendClasses };
},
// 触发一次消息发送(用于捕获调用栈)
triggerSendCapture: function(toId, content) {
try {
return Java.performNow(function() {
// 方式1: 通过WCDB
try {
var WCDBSQLite = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
send({ type: 'wcdb_found', msg: 'WCDB SQLiteDatabase exists' });
} catch(e) {
send({ type: 'wcdb_not_found', msg: String(e).substring(0, 100) });
}
// 方式2: 枚举ClassLoader
var loaders = [];
Java.enumerateClassLoaders({
onMatch: function(loader) {
loaders.push(loader.toString().substring(0, 80));
},
onComplete: function() {}
});
send({ type: 'classloaders', loaders: loaders });
return { success: true, loaders_count: loaders.length };
});
} catch(e) {
return { success: false, error: String(e) };
}
},
// 用正确ClassLoader查找类
findClassWithLoader: function(className) {
try {
return Java.performNow(function() {
var found = false;
var loaderInfo = [];
Java.enumerateClassLoaders({
onMatch: function(loader) {
try {
var cls = loader.loadClass(className);
found = true;
loaderInfo.push({ loader: loader.toString().substring(0, 60), found: true });
send({ type: 'class_loader_found', cls: className, loader: loader.toString().substring(0, 60) });
} catch(e) {
loaderInfo.push({ loader: loader.toString().substring(0, 40), found: false });
}
},
onComplete: function() {}
});
return { success: found, class: className, loaders: loaderInfo };
});
} catch(e) {
return { success: false, error: String(e) };
}
},
// 批量查找类用正确ClassLoader
findClassesBatch: function(classNames) {
try {
return Java.performNow(function() {
var results = {};
// 先找到包含微信类的ClassLoader
var wechatLoader = null;
Java.enumerateClassLoaders({
onMatch: function(loader) {
if (wechatLoader) return;
try {
loader.loadClass('com.tencent.mm.app.Application');
wechatLoader = loader;
} catch(e) {}
},
onComplete: function() {}
});
if (!wechatLoader) {
return { success: false, error: 'WeChat ClassLoader not found' };
}
send({ type: 'wechat_loader', loader: wechatLoader.toString().substring(0, 80) });
for (var i = 0; i < classNames.length; i++) {
try {
var cls = wechatLoader.loadClass(classNames[i]);
var methods = cls.getDeclaredMethods();
var methodList = [];
for (var j = 0; j < methods.length; j++) {
var m = methods[j];
var params = m.getParameterTypes().map(function(p) { return p.getName(); });
methodList.push(m.getName() + '(' + params.join(',') + ')');
}
results[classNames[i]] = { exists: true, methods: methodList };
} catch(e) {
results[classNames[i]] = { exists: false };
}
}
return { success: true, results: results };
});
} catch(e) {
return { success: false, error: String(e) };
}
},
// 用正确ClassLoader发送消息
sendMessageWithLoader: function(params) {
try {
return Java.performNow(function() {
var toId = params.to_id;
var content = params.content;
// 找微信ClassLoader
var wechatLoader = null;
Java.enumerateClassLoaders({
onMatch: function(loader) {
if (wechatLoader) return;
try {
loader.loadClass('com.tencent.mm.app.Application');
wechatLoader = loader;
} catch(e) {}
},
onComplete: function() {}
});
if (!wechatLoader) return { success: false, error: 'no wechat loader' };
// 尝试候选类
var sendCandidates = [
{ cls: 'com.tencent.mm.modelmulti.h', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.g', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.f', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.e', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.d', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.c', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.b', method: 'b' },
{ cls: 'com.tencent.mm.modelmulti.a', method: 'b' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.i', method: 'a' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.h', method: 'a' },
];
for (var i = 0; i < sendCandidates.length; i++) {
try {
var cls = wechatLoader.loadClass(sendCandidates[i].cls);
send({ type: 'candidate_found', cls: sendCandidates[i].cls });
// 获取方法
var methods = cls.getDeclaredMethods();
var methodNames = [];
for (var j = 0; j < methods.length; j++) {
methodNames.push(methods[j].getName() + '(' + methods[j].getParameterTypes().length + ')');
}
send({ type: 'candidate_methods', cls: sendCandidates[i].cls, methods: methodNames });
return { success: true, found_class: sendCandidates[i].cls, methods: methodNames };
} catch(e) {}
}
return { success: false, error: 'no send class found with loader' };
});
} catch(e) {
return { success: false, error: String(e) };
}
}
};
// 启动Hook
Java.perform(function() {
var hooked = hookSQLiteInsert();
var found = hookKnownClasses();
_hookReady = true;
send({ type: 'ready', sqlite_hooked: hooked, known_classes: found });
});

View File

@@ -0,0 +1,434 @@
"""
Frida Manager — 管理 Frida 与微信进程的连接生命周期
职责:
- 连接设备USB / remote / gadget 三种模式)
- attach 到微信进程
- 加载 JS 脚本
- 维护 rpc.exports 代理
- 自动重连
连接模式:
- usb: 需要 Root通过 frida-server 连接(传统方式)
- gadget: 无需 Root通过注入到 APK 中的 frida-gadget 连接
- remote: 通过 TCP 连接远程 frida-server
技术栈Frida 16.5.617.x 在 Android 11 上 Java 桥失效)
"""
import os
import re
import time
import logging
import threading
import subprocess
from typing import Optional, Callable, Dict, Any, Literal
logger = logging.getLogger(__name__)
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DEFAULT_SCRIPT = os.path.join(SCRIPT_DIR, "wechat_hook_v2.js")
WECHAT_PACKAGE = "com.tencent.mm"
import random as _random
_ANTI_DETECT_PORT_RANGE = (10000, 60000)
GADGET_PORT = _random.randint(*_ANTI_DETECT_PORT_RANGE)
ConnectionMode = Literal["usb", "gadget", "remote"]
_SNAKE_RE = re.compile(r'(?<=[a-z0-9])([A-Z])')
def _random_port() -> int:
return _random.randint(*_ANTI_DETECT_PORT_RANGE)
class FridaManager:
"""Frida 会话管理器,支持 USB / Gadget / Remote 三种连接模式"""
def __init__(
self,
device_serial: Optional[str] = None,
script_path: str = DEFAULT_SCRIPT,
on_event: Optional[Callable[[Dict[str, Any]], None]] = None,
on_detach: Optional[Callable[[str], None]] = None,
auto_reconnect: bool = True,
reconnect_interval: float = 5.0,
mode: ConnectionMode = "gadget",
gadget_host: str = "127.0.0.1",
gadget_port: int = 0,
use_adb_forward: bool = True,
):
if gadget_port == 0:
gadget_port = _random_port()
self.device_serial = device_serial
self.script_path = script_path
self.on_event = on_event
self.on_detach = on_detach
self.auto_reconnect = auto_reconnect
self.reconnect_interval = reconnect_interval
self.mode = mode
self.gadget_host = gadget_host
self.gadget_port = gadget_port
self.use_adb_forward = use_adb_forward
self._device = None
self._session = None
self._script = None
self._rpc = None
self._running = False
self._detaching = False
self._lock = threading.Lock()
@property
def connected(self) -> bool:
return self._session is not None and self._script is not None
@property
def rpc(self):
return self._rpc
def start(self) -> bool:
try:
import frida
except ImportError:
logger.error("frida 未安装,请执行: pip install frida-tools")
return False
with self._lock:
if self.connected:
return True
return self._do_attach()
def _setup_gadget_forward(self):
"""为 Gadget/Remote 模式设置 ADB 端口转发(手机本机 Agent 连 127.0.0.1 时跳过)"""
if not self.use_adb_forward:
logger.info("跳过 ADB forward设备本机 Frida remote")
return
try:
cmd = ["adb"]
if self.device_serial:
cmd += ["-s", self.device_serial]
cmd += ["forward", f"tcp:{self.gadget_port}", f"tcp:{self.gadget_port}"]
subprocess.run(cmd, capture_output=True, timeout=5)
logger.info(f"ADB 端口转发已设置: tcp:{self.gadget_port}")
except Exception as e:
logger.warning(f"端口转发设置失败: {e}")
def _do_attach(self) -> bool:
import frida
try:
if self.mode == "gadget":
self._setup_gadget_forward()
addr = f"{self.gadget_host}:{self.gadget_port}"
mgr = frida.get_device_manager()
self._device = mgr.add_remote_device(addr)
logger.info(f"Frida Gadget 已连接: {addr}")
self._session = self._device.attach("Gadget")
self._session.on("detached", self._on_session_detached)
logger.info("已 attach 到 Gadget 进程(无 Root 模式)")
elif self.mode == "remote":
self._setup_gadget_forward()
addr = f"{self.gadget_host}:{self.gadget_port}"
mgr = frida.get_device_manager()
self._device = mgr.add_remote_device(addr)
logger.info(f"Frida Remote 已连接: {addr}")
pid = self._find_wechat_pid()
if pid is None:
# WX-11 修复:禁用 frida spawnspawn 失败会在 ActivityManager 留下
# (null)pid 幽灵进程记录,阻塞微信后续启动需 reboot。改为 am start
# 温和拉起 + 轮询 attach仍失败则本轮放弃交重连循环稍后重试。
logger.warning("微信未运行am start 温和拉起(禁用 frida spawn 防 AM 幽灵进程 WX-11")
pid = self._launch_wechat_gently()
if pid is None:
logger.warning("微信仍未运行,本轮放弃 attach交重连循环重试 am start不 frida-spawn")
return False
self._session = self._device.attach(pid)
self._session.on("detached", self._on_session_detached)
logger.info(f"已 attach 到微信进程 pid={pid}")
else: # usb (需要 Root)
if self.device_serial:
self._device = frida.get_device(self.device_serial)
else:
self._device = frida.get_usb_device(timeout=10)
logger.info(f"Frida USB 已连接设备: {self._device.name}")
pid = self._find_wechat_pid()
if pid is None:
# WX-11 修复USB 模式同样禁用 frida spawn改 am start 温和拉起。
logger.warning("微信未运行am start 温和拉起(禁用 frida spawn 防 AM 幽灵进程 WX-11")
pid = self._launch_wechat_gently()
if pid is None:
logger.warning("微信仍未运行,本轮放弃 attach不 frida-spawn")
return False
self._session = self._device.attach(pid)
self._session.on("detached", self._on_session_detached)
logger.info(f"已 attach 到微信进程 pid={pid}")
self._load_script()
self._running = True
return True
except Exception as e:
logger.error(f"Frida attach 失败 [{self.mode}]: {e}")
self._do_cleanup()
return False
def _find_wechat_pid(self) -> Optional[int]:
# 手机本机shell pidof无需 adb
if not self.use_adb_forward:
try:
result = subprocess.run(
["pidof", WECHAT_PACKAGE],
capture_output=True, text=True, timeout=5,
)
out = (result.stdout or "").strip()
if out:
return int(out.split()[0])
except Exception:
pass
try:
result = subprocess.run(
["sh", "-c", f"pidof {WECHAT_PACKAGE}"],
capture_output=True, text=True, timeout=5,
)
out = (result.stdout or "").strip()
if out:
return int(out.split()[0])
except Exception:
pass
# 主机 ADB pidofRemote/Gadget 经端口转发时 enumerate 常漏进程)
if self.device_serial and self.use_adb_forward:
for attempt in range(3):
try:
result = subprocess.run(
["adb", "-s", self.device_serial, "shell", "pidof", WECHAT_PACKAGE],
capture_output=True, text=True, timeout=8,
)
out = (result.stdout or "").strip()
if out:
return int(out.split()[0])
except Exception:
pass
if attempt < 2:
time.sleep(2)
try:
for proc in self._device.enumerate_processes():
if proc.name in (WECHAT_PACKAGE, "WeChat") or getattr(proc, 'identifier', '') == WECHAT_PACKAGE:
return proc.pid
except Exception:
pass
try:
for app in self._device.enumerate_applications():
if app.identifier == WECHAT_PACKAGE and app.pid > 0:
return app.pid
except Exception:
pass
try:
result = subprocess.run(
["adb", "-s", self.device_serial or "", "shell", "pidof", WECHAT_PACKAGE],
capture_output=True, text=True, timeout=5,
)
if result.stdout.strip():
return int(result.stdout.strip().split()[0])
except Exception:
pass
return None
def _launch_wechat_gently(self, max_wait: int = 18) -> Optional[int]:
"""温和拉起微信:仅 am start + 轮询 pid绝不用 frida spawn。
WX-11frida spawn 失败会在 ActivityManager 留下 (null)pid 幽灵进程记录,
使微信被视为"正在启动"而拒绝后续 am start最终需 reboot 才能恢复。
故统一改用 am start 温和拉起并轮询,失败返回 None 交重连循环稍后重试。
"""
try:
cmd = ["am", "start", "-n", f"{WECHAT_PACKAGE}/.ui.LauncherUI"]
if self.device_serial:
cmd = ["adb", "-s", self.device_serial, "shell"] + cmd
subprocess.run(cmd, capture_output=True, timeout=15)
except Exception as e:
logger.warning(f"am start 拉起微信失败: {e}")
deadline = time.time() + max_wait
while time.time() < deadline:
pid = self._find_wechat_pid()
if pid:
logger.info(f"微信已温和拉起 pid={pid}")
return pid
time.sleep(2)
return None
def _load_script(self):
path = self.script_path
if not os.path.exists(path):
fallback = os.path.join(SCRIPT_DIR, "wechat_hook_v1.js")
if os.path.exists(fallback):
path = fallback
else:
raise FileNotFoundError(f"Hook 脚本不存在: {path}")
with open(path, "r", encoding="utf-8") as f:
source = f.read()
self._script = self._session.create_script(source)
self._script.on("message", self._on_message)
self._script.load()
# 注意:禁止调用 script.eternalize()。Frida 语义为「eternalize 后即与脚本断开交互」,
# 调用后 exports_sync 失效,所有 RPC 报 "script has been destroyed"。
# 保活改由 _on_session_detached → _reconnect_loopFS-3 detach 重连守护)实现。
self._rpc = self._script.exports_sync
logger.info(f"Hook 脚本已加载: {os.path.basename(path)}")
try:
pong = self._rpc.ping()
logger.info(f"RPC ping: {pong}")
except Exception as e:
logger.warning(f"RPC ping 失败: {e}")
def _on_message(self, message: dict, data):
if message.get("type") == "send":
payload = message.get("payload", {})
msg_type = payload.get("type", "")
if msg_type == "hook_event" and self.on_event:
self.on_event(payload)
elif msg_type == "log":
level = payload.get("level", "info")
tag = payload.get("tag", "hook")
text = payload.get("message", "")
getattr(logger, level, logger.info)(f"[{tag}] {text}")
elif message.get("type") == "error":
logger.error(f"Frida 脚本错误: {message.get('description', '')}")
def _on_session_detached(self, reason: str):
"""Frida 内部线程回调 — 只清理引用,不重复 detach/unload不获取 _lock避免死锁"""
logger.warning(f"Frida 会话断开: {reason}")
self._detaching = True
self._nullify_refs()
self._detaching = False
if self.on_detach:
try:
self.on_detach(reason)
except Exception as e:
logger.warning(f"on_detach 回调异常: {e}")
if self.auto_reconnect and self._running and reason != "application-requested":
threading.Thread(target=self._reconnect_loop, daemon=True).start()
def _reconnect_loop(self):
while self._running and not self.connected:
logger.info(f"尝试重连 Frida{self.reconnect_interval}s 后)...")
time.sleep(self.reconnect_interval)
with self._lock:
if self.connected:
break
try:
if self._do_attach():
logger.info("Frida 重连成功")
break
except Exception as e:
logger.warning(f"重连失败: {e}")
@staticmethod
def _to_snake(name: str) -> str:
return _SNAKE_RE.sub(r'_\1', name).lower()
def call_rpc(self, method: str, params: dict = None) -> Dict[str, Any]:
with self._lock:
if not self.connected or not self._rpc:
return {"success": False, "error": "Frida 未连接"}
rpc = self._rpc
try:
snake = self._to_snake(method)
fn = getattr(rpc, snake, None) or getattr(rpc, method, None)
if fn is None:
return {"success": False, "error": f"RPC 方法不存在: {method} ({snake})"}
result = fn(params) if params else fn({})
if isinstance(result, dict):
result.setdefault("success", True)
return result
return {"success": True, "data": result}
except Exception as e:
logger.error(f"RPC 调用失败 [{method}]: {e}")
return {"success": False, "error": str(e)}
def reload_script(self) -> Dict[str, Any]:
"""重新加载当前 Hook 脚本,不重启微信进程。"""
with self._lock:
try:
if not self._session:
ok = self._do_attach()
return {
"success": ok,
"reloaded": ok,
"script_path": self.script_path,
"method": "attach_and_load",
}
if self._script:
try:
self._script.unload()
except Exception as e:
logger.warning(f"Hook 脚本卸载失败,继续重载: {e}")
self._script = None
self._rpc = None
self._load_script()
return {
"success": True,
"reloaded": True,
"script_path": self.script_path,
"method": "reload_script",
}
except Exception as e:
self._script = None
self._rpc = None
logger.error(f"Hook 脚本重载失败: {e}")
return {
"success": False,
"reloaded": False,
"script_path": self.script_path,
"error": str(e),
}
def stop(self):
self._running = False
with self._lock:
self._do_cleanup()
logger.info("FridaManager 已停止")
def _nullify_refs(self):
"""清空引用(不执行 unload/detach用于 detach 回调内"""
self._script = None
self._session = None
self._rpc = None
def _do_cleanup(self):
"""主动断开时完整清理unload 脚本 + detach 会话)"""
if self._detaching:
return
try:
if self._script:
self._script.unload()
except Exception:
pass
try:
if self._session:
self._session.detach()
except Exception:
pass
self._nullify_refs()
def get_status(self) -> Dict[str, Any]:
return {
"connected": self.connected,
"mode": self.mode,
"device": self._device.name if self._device else None,
"device_serial": self.device_serial,
"script": os.path.basename(self.script_path),
"running": self._running,
"gadget_endpoint": f"{self.gadget_host}:{self.gadget_port}" if self.mode == "gadget" else None,
}

View File

@@ -0,0 +1,80 @@
// 微信8.0.56 真实发送类名捕获脚本
// 策略Hook WCDB写入 + OkHttp/网络层,捕获真实调用栈
var results = {
send_classes: [],
sns_classes: [],
network_calls: [],
db_writes: []
};
// 1. Hook WCDB SQLiteDatabase.insert - 捕获消息写入调用栈
try {
var WCDBClass = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
WCDBClass.insert.overload('java.lang.String', 'java.lang.String', 'android.content.ContentValues').implementation = function(table, nullColumnHack, values) {
var result = this.insert(table, nullColumnHack, values);
if (table === 'message' || table === 'SnsInfo' || table === 'StarSnsInfo') {
var stack = Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()).split('\n');
var relevant = stack.filter(function(s) { return s.indexOf('com.tencent.mm') > -1 && s.indexOf('wcdb') === -1; }).slice(0, 8);
send({type: 'db_write', table: table, stack: relevant.join('\n'), rowId: result});
}
return result;
};
console.log('[+] WCDB insert hooked');
} catch(e) { console.log('[-] WCDB insert hook failed: ' + e); }
// 2. Hook OkHttp3 发送请求 - 捕获微信HTTP请求
try {
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
var Request = Java.use('okhttp3.Request');
var RealCall = Java.use('okhttp3.internal.connection.RealCall');
RealCall.execute.implementation = function() {
var req = this.request();
var url = req.url().toString();
if (url.indexOf('weixin') > -1 || url.indexOf('wx') > -1 || url.indexOf('mm') > -1) {
var stack = Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()).split('\n');
var relevant = stack.filter(function(s) { return s.indexOf('com.tencent.mm') > -1; }).slice(0, 6);
send({type: 'network', url: url, stack: relevant.join('\n')});
}
return this.execute();
};
console.log('[+] OkHttp3 hooked');
} catch(e) { console.log('[-] OkHttp3 hook failed: ' + e); }
// 3. Hook MMProtocol/网络发送 - 微信自己的协议层
var netClasses = [
'com.tencent.mm.protocal.MMProtocol',
'com.tencent.mm.network.MMNetworkManager',
'com.tencent.mm.sdk.platformtools.NetWorkUtil',
'com.tencent.mm.model.b',
'com.tencent.mm.model.c',
'com.tencent.mm.model.d',
];
netClasses.forEach(function(cls) {
try {
var C = Java.use(cls);
send({type: 'found_class', cls: cls});
} catch(e) {}
});
// 4. 枚举所有已加载的 com.tencent.mm.model.* 类发送消息通常在model层
Java.enumerateLoadedClasses({
onMatch: function(name) {
if (name.indexOf('com.tencent.mm.model') > -1 ||
name.indexOf('com.tencent.mm.protocal') > -1 ||
name.indexOf('com.tencent.mm.network') > -1 ||
name.indexOf('SnsUpload') > -1 ||
name.indexOf('SendMsg') > -1 ||
name.indexOf('ChatMsg') > -1) {
send({type: 'class_found', name: name});
}
},
onComplete: function() {
send({type: 'enum_done'});
}
});
console.log('[*] Hook setup complete. Waiting for user to send a message...');
rpc.exports = {
getResults: function() { return results; }
};

View File

@@ -0,0 +1,450 @@
"""
Hook Executor v3.0 — 将 unified API 动作映射到 Frida rpc.exports 调用
职责:
- 接收 unified 指令send_message / get_contacts / add_friend / …)
- 通过 FridaManager.call_rpc 调用微信内部函数
- Hook 失败时降级到 ADB/u2 通道
对接:
- 服务端 unified.py → WebSocket 下发 → Agent → HookExecutor
- 或 ADB 模式unified.py → HookExecutor本地 Frida
支持 110 个操作 / 24 模块,与 wechat_hook_v3.0.js rpc.exports 一一对应
"""
import logging
from typing import Dict, Any
from .frida_manager import FridaManager
logger = logging.getLogger(__name__)
ACTION_TO_RPC: Dict[str, str] = {
# ── 探测 ──
"ping": "ping",
# ── H17 消息发送 ──
"send_message": "sendMessage",
"send_group_message": "sendGroupMessage",
# ── H15 消息获取 ──
"get_messages": "getMessages",
"get_recent_messages": "getRecentMessages",
"search_messages": "searchMessages",
# ── H16 联系人 ──
"get_contacts": "getContacts",
"get_contact_info": "getContactInfo",
"search_contacts": "searchContacts",
# ── H18/H19 好友管理 ──
"add_friend": "addFriend",
"accept_friend": "acceptFriend",
"delete_friend": "deleteFriend",
"set_friend_remark": "setFriendRemark",
"get_friend_requests": "getFriendRequests",
# ── H22 群管理 ──
"get_groups": "getGroups",
"get_group_info": "getGroupInfo",
"get_group_members": "getGroupMembers",
"create_group": "createGroup",
"invite_to_group": "inviteToGroup",
"remove_from_group": "removeFromGroup",
"set_group_announcement": "setGroupAnnouncement",
"set_group_name": "setGroupName",
"quit_group": "quitGroup",
# ── H20/H21 朋友圈 ──
"post_moments": "postMoments",
"get_moments": "getMoments",
"like_moments": "likeMoments",
"comment_moments": "commentMoments",
"delete_moments": "deleteMoments",
# ── H23 账号管理 ──
"get_profile": "getProfile",
"check_account_status": "checkAccountStatus",
"set_nickname": "setNickname",
"set_signature": "setSignature",
"set_avatar": "setAvatar",
"set_sex": "setSex",
"set_region": "setRegion",
"set_what_up": "setWhatUp",
# ── H24 账号安全 ──
"unblock_self": "unblockSelf",
"change_password": "changePassword",
"bind_phone": "bindPhone",
"unbind_phone": "unbindPhone",
"get_login_devices": "getLoginDevices",
"remove_login_device": "removeLoginDevice",
"enable_fingerprint": "enableFingerprint",
"set_account_protection": "setAccountProtection",
# ── H25 支付 ──
"send_red_packet": "sendRedPacket",
"receive_red_packet": "receiveRedPacket",
"send_transfer": "sendTransfer",
"receive_transfer": "receiveTransfer",
"get_wallet_balance": "getWalletBalance",
"get_transaction_history": "getTransactionHistory",
# ── H26 二维码 ──
"scan_qr_code": "scanQrCode",
"generate_my_qr_code": "generateMyQrCode",
"generate_group_qr_code": "generateGroupQrCode",
"add_friend_by_qr": "addFriendByQr",
# ── H27 视频号 ──
"browse_channels": "browseChannels",
"like_channel_video": "likeChannelVideo",
"comment_channel_video": "commentChannelVideo",
"follow_channel": "followChannel",
"unfollow_channel": "unfollowChannel",
"share_channel_video": "shareChannelVideo",
# ── H28 标签 ──
"get_labels": "getLabels",
"create_label": "createLabel",
"delete_label": "deleteLabel",
"set_contact_label": "setContactLabel",
"get_contacts_by_label": "getContactsByLabel",
# ── H29 收藏 ──
"get_favorites": "getFavorites",
"add_favorite": "addFavorite",
"delete_favorite": "deleteFavorite",
# ── H30 设置 ──
"set_privacy": "setPrivacy",
"set_notification": "setNotification",
"clear_chat_history": "clearChatHistory",
"set_chat_background": "setChatBackground",
"set_do_not_disturb": "setDoNotDisturb",
"pin_chat": "pinChat",
# ── H31 搜索 ──
"global_search": "globalSearch",
# ── H32 小程序 ──
"open_mini_program": "openMiniProgram",
"get_recent_mini_programs": "getRecentMiniPrograms",
"share_mini_program": "shareMiniProgram",
# ── H33 文件传输 ──
"send_image": "sendImage",
"send_video": "sendVideo",
"send_file": "sendFile",
"send_voice": "sendVoice",
"send_location": "sendLocation",
"send_card": "sendCard",
"send_link": "sendLink",
# ── H34 消息转发 ──
"forward_message": "forwardMessage",
"forward_multiple": "forwardMultiple",
"revoke_message": "revokeMessage",
# ── H35 注册/登录 ──
"register_account": "registerAccount",
"login_by_password": "loginByPassword",
"login_by_sms": "loginBySms",
"logout": "logout",
"switch_account": "switchAccount",
"auto_register": "autoRegister",
"check_login_state": "checkLoginState",
"get_sim_phone": "getSimPhone",
# ── H36 公众号 ──
"get_official_accounts": "getOfficialAccounts",
"follow_official_account": "followOfficialAccount",
"unfollow_official_account": "unfollowOfficialAccount",
"get_official_account_articles": "getOfficialAccountArticles",
# ── H37 表情 ──
"send_emoji": "sendEmoji",
"add_custom_emoji": "addCustomEmoji",
# ── H38 浮窗 ──
"add_to_float": "addToFloat",
"remove_from_float": "removeFromFloat",
# ── H39 设备信息 ──
"get_device_info": "getDeviceInfo",
"get_storage_info": "getStorageInfo",
"get_network_info": "getNetworkInfo",
# ── 系统 ──
"get_hook_status": "getHookStatus",
"diag_sns": "diagSns",
"diag_wcdb": "diagWcdb",
"diag_revoke_msg_event": "diagRevokeMsgEvent",
"get_process_info": "getProcessInfo",
"get_wechat_version": "getWechatVersion",
"batch_execute": "batchExecute",
# ── 矩阵 v8.0.56 补全Frida RPC 直映射)──
"get_safety_center": "getSafetyCenter",
"check_restrictions": "checkRestrictions",
"get_top_stories": "getTopStories",
"get_wechat_steps": "getWechatSteps",
"get_sticker_list": "getStickerList",
"show_payment_code": "showPaymentCode",
"like_wechat_steps": "likeWechatSteps",
"clear_cache": "clearCache",
"check_for_update": "checkForUpdate",
# ── 矩阵 REST E2E 补全 ──
"voice_call": "voiceCall",
"video_call": "videoCall",
"share_real_time_location": "shareRealTimeLocation",
"download_file": "downloadFile",
"mass_send": "massSend",
"batch_send": "massSend",
"reply_comment": "replyComment",
"receive_payment": "receivePayment",
# ── 奥创协议补全1077-1270 缺口 · 读类真 DB / 写类 intent 诚实通道)──
"get_conversations": "getConversations",
"get_message_by_id": "getMessageById",
"get_msg_ids_by_time": "getMsgIdsByTime",
"get_message_raw_xml": "getMessageRawXml",
"add_friend_in_room": "addFriendInChatRoom",
"add_friend_from_phonebook": "addFriendFromPhonebook",
"add_friend_with_scene": "addFriendWithScene",
"add_friend_by_card": "addFriendByCard",
"join_group_by_qr": "joinGroupByQr",
"approve_group_invite": "approveGroupInvite",
"agree_join_group": "agreeJoinGroup",
"mark_conversation_read": "markConversationRead",
"send_multi_image": "sendMultiImage",
"send_jielong": "sendJielong",
"send_friend_verify": "sendFriendVerify",
"voice_to_text": "voiceToText",
"get_friend_moments": "getFriendMoments",
"get_moment_detail": "getMomentDetail",
"delete_moment_comment": "deleteMomentComment",
"reply_moment_comment": "replyMomentComment",
"stop_moments_praise": "stopMomentsPraise",
"start_nurture": "startNurture",
"stop_nurture": "stopNurture",
"detect_zombie_fans": "detectZombieFans",
"stop_detect_zombie_fans": "stopDetectZombieFans",
"query_red_packet": "queryRedPacket",
"get_wechat_location": "getWechatLocation",
"search_official_account": "searchOfficialAccount",
"phone_action": "phoneAction",
}
# unified / ADB 引擎方法名 → HookExecutor 标准 action矩阵 v8.0.56 真机验收)
ACTION_ALIASES: Dict[str, str] = {
"recall_message": "revoke_message",
"search_contact": "search_contacts",
"get_friend_info": "get_contact_info",
"account_status": "check_account_status",
"safety_center": "get_safety_center",
"set_chat_top": "pin_chat",
"set_mute_chat": "set_do_not_disturb",
"get_video_list": "browse_channels",
"show_my_qr": "generate_my_qr_code",
"view_wallet": "get_wallet_balance",
"view_transactions": "get_transaction_history",
"wechat_search": "global_search",
"top_stories": "get_top_stories",
"get_steps": "get_wechat_steps",
"get_tags": "get_labels",
"create_tag": "create_label",
"delete_tag": "delete_label",
"add_tag": "set_contact_label",
"remove_tag": "set_contact_label",
"set_remark": "set_friend_remark",
"add_to_favorites": "add_favorite",
"clear_history": "clear_chat_history",
"like_video": "like_channel_video",
"comment_video": "comment_channel_video",
"follow_video_creator": "follow_channel",
"share_video": "share_channel_video",
"scan_add_friend": "add_friend_by_qr",
"scan_extract_qr": "scan_qr_code",
"extract_qr_from_image": "scan_qr_code",
"payment_receive": "receive_payment",
"send_voice_message": "send_voice",
"set_group_notice": "set_group_announcement",
"transfer": "send_transfer",
"unblock_account": "unblock_self",
"unblock_appeal": "unblock_self",
"appeal_restriction": "unblock_self",
"unblock_with_sms": "unblock_self",
"set_gender": "set_sex",
"set_moments_cover": "set_privacy",
"set_moments_privacy": "set_privacy",
"forward_moments_link": "post_moments",
"open_miniprogram": "open_mini_program",
"do_not_disturb": "set_do_not_disturb",
"like_steps": "like_wechat_steps",
"toggle_do_not_disturb": "set_do_not_disturb",
"clear_cache": "clear_cache",
"check_for_update": "check_for_update",
"send_file_from_chat": "send_file",
"send_voice_message": "send_voice",
"add_to_favorites": "add_favorite",
}
def resolve_action(action: str) -> str:
return ACTION_ALIASES.get(action, action)
def normalize_params(action: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""统一 API 参数 → Frida RPC 参数user_id→wxid 等)"""
p = dict(params or {})
resolved = resolve_action(action)
# 文件传输助手显示名 → wxid
for key in ("user_id", "wxid", "to_id", "conversation_id"):
if p.get(key) in ("文件传输助手", "File Transfer"):
p[key] = "filehelper"
if p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "pin_chat":
if "enable" in p and "pin" not in p:
p["pin"] = p["enable"]
if resolved == "get_contact_info" and p.get("user_id") and not p.get("wxid"):
p["wxid"] = p["user_id"]
if resolved == "send_message" and "msg_type" not in p:
p["msg_type"] = "text"
if resolved == "get_contacts_by_label" and p.get("tag_name") and not p.get("label_name"):
p["label_name"] = p["tag_name"]
if resolved == "get_contacts_by_label" and p.get("label_name") and not p.get("label_id"):
p["label_id"] = p["label_name"]
if resolved == "delete_label" and p.get("tag_name") and not p.get("label_id"):
p["label_id"] = p["tag_name"]
if resolved == "create_label" and p.get("tag_name") and not p.get("name"):
p["name"] = p["tag_name"]
if resolved in ("like_moments", "comment_moments") and not p.get("sns_id"):
if p.get("post_index") is not None:
p["sns_id"] = str(p["post_index"])
elif p.get("user_id"):
p["sns_id"] = "probe_sns"
if resolved == "remove_from_group" and p.get("member_id") and not p.get("member_ids"):
p["member_ids"] = [p["member_id"]]
if resolved == "set_privacy" and p.get("image_path") and not p.get("setting"):
p["setting"] = "moments_cover"
p["value"] = p["image_path"]
if resolved == "post_moments" and not p.get("content"):
p["content"] = p.get("url") or p.get("title") or p.get("content_desc") or "[probe]"
if action == "add_tag" and p.get("tag_name") and not p.get("label_ids"):
p["label_ids"] = [p["tag_name"]]
if action == "remove_tag":
p.setdefault("label_ids", [])
p["remove"] = True
if action == "add_tag" and p.get("tags") and not p.get("label_ids"):
p["label_ids"] = p["tags"]
p["wxid"] = p.get("wxid") or p.get("user_id")
if resolved == "delete_moments" and not p.get("sns_id"):
if p.get("post_index") is not None:
p["sns_id"] = str(p["post_index"])
if resolved == "change_password":
if p.get("old_pwd") and not p.get("old_password"):
p["old_password"] = p["old_pwd"]
if p.get("new_pwd") and not p.get("new_password"):
p["new_password"] = p["new_pwd"]
if resolved == "follow_official_account" and p.get("account_name") and not p.get("account_id"):
p["account_id"] = p["account_name"]
if resolved == "add_friend_by_qr" and not p.get("qr_content"):
p["qr_content"] = "probe"
if resolved == "scan_qr_code" and not p.get("image_path"):
p["image_path"] = "/sdcard/test.jpg"
if action == "set_moments_cover" and not p.get("setting"):
p["setting"] = "moments_cover"
p["value"] = p.get("image_path") or "/sdcard/test.jpg"
if resolved == "receive_red_packet" and not p.get("msg_svr_id"):
p["msg_svr_id"] = p.get("from_id") or "probe_msg"
if resolved == "comment_channel_video" and p.get("index") is not None and not p.get("video_id"):
p["video_id"] = f"index_{p['index']}"
if resolved == "follow_channel" and p.get("index") is not None and not p.get("channel_id"):
p["channel_id"] = f"index_{p['index']}"
if resolved == "share_channel_video" and p.get("index") is not None and not p.get("video_id"):
p["video_id"] = f"index_{p['index']}"
if resolved == "set_contact_label" and p.get("tags") and not p.get("label_ids"):
p["label_ids"] = p["tags"]
if p.get("to_id") and not p.get("user_id"):
p["user_id"] = p["to_id"]
if p.get("user_id") and not p.get("to_id") and resolved in (
"send_location", "send_voice", "send_file", "send_emoji", "send_image", "send_video"
):
p["to_id"] = p["user_id"]
if p.get("name") and not p.get("label"):
p["label"] = p["name"]
if p.get("emoji_name") and not p.get("emoji_md5"):
p["emoji_md5"] = p["emoji_name"]
if p.get("name") and not p.get("app_id") and resolved == "open_mini_program":
p["app_id"] = p["name"]
if p.get("index") is not None and not p.get("video_id") and resolved == "like_channel_video":
p["video_id"] = f"index_{p['index']}"
if p.get("privacy_type") and not p.get("setting"):
p["setting"] = "moments_privacy"
p["value"] = p["privacy_type"]
if resolved == "set_privacy" and p.get("days") and not p.get("setting"):
p["setting"] = "moments_days"
p["value"] = p["days"]
if p.get("content") and not p.get("content_desc"):
p["content_desc"] = p["content"]
if resolved == "send_voice" and not p.get("voice_path") and p.get("duration"):
p["voice_path"] = f"/sdcard/workphone/voice_{int(p['duration'])}s.amr"
return p
MODULE_NAMES = {
"H15": "消息接收", "H16": "联系人", "H17": "消息发送",
"H18": "好友请求", "H19": "好友管理", "H20": "朋友圈发布",
"H21": "朋友圈浏览", "H22": "群管理", "H23": "账号管理",
"H24": "账号安全", "H25": "支付", "H26": "二维码",
"H27": "视频号", "H28": "标签", "H29": "收藏",
"H30": "设置", "H31": "搜索", "H32": "小程序",
"H33": "文件传输", "H34": "消息转发", "H35": "注册/登录",
"H36": "公众号", "H37": "表情", "H38": "浮窗",
"H39": "设备信息",
}
class HookExecutor:
"""将 unified 动作转为 Frida RPC 调用 — 110 个操作全覆盖"""
def __init__(self, frida_mgr: FridaManager):
self.frida = frida_mgr
@property
def available(self) -> bool:
return self.frida.connected
def execute(self, action: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
if not self.available:
return {"success": False, "error": "Hook 通道不可用Frida 未连接)", "channel": "hook"}
canonical = resolve_action(action)
norm = normalize_params(action, params)
rpc_method = ACTION_TO_RPC.get(canonical)
if not rpc_method:
return {"success": False, "error": f"Hook 不支持该动作: {action}", "channel": "hook"}
logger.info(f"[HookExecutor] {action}{canonical} → rpc.{rpc_method}")
result = self.frida.call_rpc(rpc_method, norm)
result["channel"] = "hook"
result["action_resolved"] = canonical
return result
def supports(self, action: str) -> bool:
return resolve_action(action) in ACTION_TO_RPC
def get_supported_actions(self) -> list:
return sorted(ACTION_TO_RPC.keys())
def get_status(self) -> Dict[str, Any]:
return {
"available": self.available,
"total_actions": len(ACTION_TO_RPC),
"supported_actions": self.get_supported_actions(),
"modules": MODULE_NAMES,
"frida": self.frida.get_status(),
}

View File

@@ -0,0 +1,332 @@
/**
* common.js — 机擎 SDK Hook 脚本公共框架
*
* 所有平台 Hook 脚本(微信/抖音/小红书等)共用的工具函数和基础设施。
* 由各平台脚本通过 Frida evaluate 或内联引用使用。
*
* @version 1.0.0
*/
'use strict';
// ============================================================
// § 1 日志与事件
// ============================================================
var LOG_LEVEL_MAP = { debug: 0, info: 1, warn: 2, error: 3 };
var _currentLogLevel = 'info';
function setLogLevel(level) {
if (LOG_LEVEL_MAP[level] !== undefined) _currentLogLevel = level;
}
function log(level, tag, message, extra) {
if ((LOG_LEVEL_MAP[level] || 0) < (LOG_LEVEL_MAP[_currentLogLevel] || 0)) return;
send({
type: 'log',
level: level,
tag: String(tag || 'hook'),
message: String(message || ''),
extra: extra || {},
timestamp: Date.now(),
});
}
function emitEvent(eventType, payload) {
send({
type: 'hook_event',
event_type: String(eventType),
platform: (typeof PLATFORM !== 'undefined') ? PLATFORM : 'unknown',
payload: payload || {},
timestamp: new Date().toISOString(),
});
}
// ============================================================
// § 2 安全字符串/类型工具
// ============================================================
function safeStr(v) {
if (v === null || v === undefined) return '';
try { return v.toString(); } catch (_) { return ''; }
}
function safeInt(v, fallback) {
if (v === null || v === undefined) return fallback || 0;
try {
var n = parseInt(v, 10);
return isNaN(n) ? (fallback || 0) : n;
} catch (_) { return fallback || 0; }
}
function safeJSON(v) {
try { return JSON.parse(v); } catch (_) { return null; }
}
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
function generateId(prefix) {
return (prefix || 'id') + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
}
// ============================================================
// § 3 Java / Android 辅助
// ============================================================
function javaString(str) {
return Java.use('java.lang.String').$new(String(str));
}
function getContext() {
try {
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
} catch (e) {
log('warn', 'ctx', '获取 Context 失败: ' + e);
return null;
}
}
function getPackageVersion(packageName) {
try {
var ctx = getContext();
if (!ctx) return '';
var pm = ctx.getPackageManager();
var info = pm.getPackageInfo(javaString(packageName), 0);
return safeStr(info.versionName.value);
} catch (_) {
return '';
}
}
function isServiceRunning(serviceClass) {
try {
var ctx = getContext();
if (!ctx) return false;
var am = Java.cast(
ctx.getSystemService(javaString('activity')),
Java.use('android.app.ActivityManager')
);
var services = am.getRunningServices(100);
for (var i = 0; i < services.size(); i++) {
if (safeStr(services.get(i).service.value.getClassName()) === serviceClass) return true;
}
} catch (_) {}
return false;
}
// ============================================================
// § 4 SQLite 通用查询
// ============================================================
function execSQL(dbPath, sql, limit) {
limit = limit || 500;
var results = [];
try {
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
var db = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY.value);
var cursor = db.rawQuery(sql, null);
var count = 0;
while (cursor.moveToNext() && count < limit) {
var row = {};
for (var c = 0; c < cursor.getColumnCount(); c++) {
row[cursor.getColumnName(c)] = safeStr(cursor.getString(c));
}
results.push(row);
count++;
}
cursor.close();
db.close();
} catch (e) {
log('warn', 'db', 'execSQL 失败: ' + e + ' | sql=' + sql);
}
return results;
}
function findDatabase(basePath, dbName) {
try {
var File = Java.use('java.io.File');
var dir = File.$new(basePath);
if (!dir.exists()) return null;
var subs = dir.listFiles();
if (!subs) return null;
for (var i = 0; i < subs.length; i++) {
var candidate = subs[i].getAbsolutePath() + '/' + dbName;
var f = File.$new(candidate);
if (f.exists()) return candidate;
}
} catch (_) {}
return null;
}
// ============================================================
// § 5 Intent 辅助
// ============================================================
function sendBroadcast(action, extras) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getContext();
if (!ctx) return false;
var intent = Intent.$new(action);
if (extras) {
var keys = Object.keys(extras);
for (var i = 0; i < keys.length; i++) {
intent.putExtra(keys[i], String(extras[keys[i]]));
}
}
ctx.sendBroadcast(intent);
return true;
} catch (e) {
log('error', 'intent', 'sendBroadcast 失败: ' + e);
return false;
}
}
function startActivity(componentPkg, componentCls, extras) {
try {
var Intent = Java.use('android.content.Intent');
var ComponentName = Java.use('android.content.ComponentName');
var ctx = getContext();
if (!ctx) return false;
var intent = Intent.$new();
intent.setComponent(ComponentName.$new(componentPkg, componentCls));
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
if (extras) {
var keys = Object.keys(extras);
for (var i = 0; i < keys.length; i++) {
intent.putExtra(keys[i], String(extras[keys[i]]));
}
}
ctx.startActivity(intent);
return true;
} catch (e) {
log('error', 'intent', 'startActivity 失败: ' + e);
return false;
}
}
// ============================================================
// § 6 Hook 辅助(安全包装 Java.use / overload
// ============================================================
function tryJavaUse(className) {
try { return Java.use(className); } catch (_) { return null; }
}
function hookMethod(className, methodName, overloads, callbacks) {
try {
var Cls = Java.use(className);
var method = overloads ? Cls[methodName].overload.apply(Cls[methodName], overloads) : Cls[methodName];
method.implementation = callbacks.replace || function () {
if (callbacks.before) callbacks.before.apply(this, arguments);
var ret = method.apply(this, arguments);
if (callbacks.after) ret = callbacks.after.call(this, ret, arguments) || ret;
return ret;
};
log('info', 'hook', '已 Hook: ' + className + '.' + methodName);
return true;
} catch (e) {
log('warn', 'hook', 'Hook 失败: ' + className + '.' + methodName + ' → ' + e);
return false;
}
}
function hookConstructor(className, overloads, callback) {
try {
var Cls = Java.use(className);
var ctor = overloads ? Cls.$init.overload.apply(Cls.$init, overloads) : Cls.$init;
ctor.implementation = function () {
ctor.apply(this, arguments);
if (callback) callback.apply(this, arguments);
};
log('info', 'hook', '已 Hook 构造: ' + className);
return true;
} catch (e) {
log('warn', 'hook', 'Hook 构造失败: ' + className + ' → ' + e);
return false;
}
}
// ============================================================
// § 7 文件系统辅助
// ============================================================
function readFile(path) {
try {
var File = Java.use('java.io.File');
var FileInputStream = Java.use('java.io.FileInputStream');
var BufferedReader = Java.use('java.io.BufferedReader');
var InputStreamReader = Java.use('java.io.InputStreamReader');
var f = File.$new(path);
if (!f.exists()) return null;
var reader = BufferedReader.$new(InputStreamReader.$new(FileInputStream.$new(f)));
var sb = [];
var line;
while ((line = reader.readLine()) !== null) sb.push(safeStr(line));
reader.close();
return sb.join('\n');
} catch (_) { return null; }
}
function fileExists(path) {
try { return Java.use('java.io.File').$new(path).exists(); } catch (_) { return false; }
}
// ============================================================
// § 8 缓冲与节流
// ============================================================
function createThrottle(minIntervalMs) {
var lastFire = 0;
return function (fn) {
var now = Date.now();
if (now - lastFire >= minIntervalMs) {
lastFire = now;
fn();
}
};
}
function createRingBuffer(capacity) {
var buf = [];
return {
push: function (item) {
buf.push(item);
if (buf.length > capacity) buf = buf.slice(-Math.floor(capacity * 0.6));
},
slice: function (start, end) { return buf.slice(start, end); },
get length() { return buf.length; },
clear: function () { buf = []; },
};
}
// ============================================================
// § 9 导出(供其它脚本引用)
// ============================================================
var CommonUtils = {
log: log,
setLogLevel: setLogLevel,
emitEvent: emitEvent,
safeStr: safeStr,
safeInt: safeInt,
safeJSON: safeJSON,
clamp: clamp,
generateId: generateId,
javaString: javaString,
getContext: getContext,
getPackageVersion: getPackageVersion,
isServiceRunning: isServiceRunning,
execSQL: execSQL,
findDatabase: findDatabase,
sendBroadcast: sendBroadcast,
startActivity: startActivity,
tryJavaUse: tryJavaUse,
hookMethod: hookMethod,
hookConstructor: hookConstructor,
readFile: readFile,
fileExists: fileExists,
createThrottle: createThrottle,
createRingBuffer: createRingBuffer,
};

View File

@@ -0,0 +1,312 @@
/**
* syscall_hook.js — 系统调用层网络拦截(机擎 SDK v3.0 · H23
*
* 功能:
* 1. 拦截 libc connect/sendto/recvfrom — 监控所有网络连接
* 2. 拦截 SSL_read/SSL_write — HTTPS 明文抓取
* 3. DNS 解析拦截 — 域名映射追踪
* 4. 可选SSL Pinning Bypass
*
* 使用方式:
* 由 FridaManager 单独加载或与 wechat_hook_v2.js 并行
*
* @version 1.0.0
*/
'use strict';
var PLATFORM = 'syscall';
var SYSCALL_CONFIG = {
HOOK_CONNECT: true,
HOOK_SSL: true,
HOOK_DNS: true,
SSL_BYPASS: false,
LOG_LEVEL: 'info',
CAPTURE_PAYLOAD: false,
MAX_PAYLOAD_SIZE: 4096,
MONITORED_DOMAINS: [
'weixin.qq.com', 'wechat.com', 'wx.qq.com',
'long.weixin.qq.com', 'short.weixin.qq.com',
'szlong.weixin.qq.com', 'szshort.weixin.qq.com',
],
MONITORED_PORTS: [80, 443, 8080, 8443],
};
// ============================================================
// 工具
// ============================================================
function log(level, tag, message, extra) {
send({ type: 'log', level: level, tag: 'syscall.' + tag, message: String(message || ''), extra: extra || {}, timestamp: Date.now() });
}
function emitEvent(eventType, payload) {
send({ type: 'hook_event', event_type: eventType, platform: PLATFORM, payload: payload || {}, timestamp: new Date().toISOString() });
}
function ipFromSockaddr(ptr) {
if (ptr.isNull()) return { ip: '0.0.0.0', port: 0, family: 0 };
var family = ptr.readU16();
if (family === 2) {
var port = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var ip = ptr.add(4).readU8() + '.' + ptr.add(5).readU8() + '.' + ptr.add(6).readU8() + '.' + ptr.add(7).readU8();
return { ip: ip, port: port, family: 2 };
}
if (family === 10) {
var port6 = (ptr.add(2).readU8() << 8) | ptr.add(3).readU8();
var bytes = [];
for (var i = 0; i < 16; i++) bytes.push(ptr.add(8 + i).readU8());
if (bytes[10] === 0xff && bytes[11] === 0xff && bytes.slice(0, 10).every(function (b) { return b === 0; })) {
return { ip: bytes[12] + '.' + bytes[13] + '.' + bytes[14] + '.' + bytes[15], port: port6, family: 10 };
}
return { ip: '::ipv6', port: port6, family: 10 };
}
return { ip: 'unknown', port: 0, family: family };
}
// ============================================================
// 统计
// ============================================================
var _stats = {
connections: 0,
ssl_reads: 0,
ssl_writes: 0,
dns_queries: 0,
bytes_sent: 0,
bytes_received: 0,
};
var _connectionMap = {};
// ============================================================
// rpc.exports
// ============================================================
rpc.exports = {
ping: function () { return 'pong from syscall_hook'; },
getStats: function () { return _stats; },
getConnections: function (params) {
var limit = (params && params.limit) || 50;
var keys = Object.keys(_connectionMap).slice(-limit);
return {
success: true,
connections: keys.map(function (k) { return _connectionMap[k]; }),
count: keys.length,
total: Object.keys(_connectionMap).length,
};
},
setConfig: function (params) {
if (params) {
if (params.capture_payload !== undefined) SYSCALL_CONFIG.CAPTURE_PAYLOAD = !!params.capture_payload;
if (params.ssl_bypass !== undefined) SYSCALL_CONFIG.SSL_BYPASS = !!params.ssl_bypass;
if (params.log_level) SYSCALL_CONFIG.LOG_LEVEL = params.log_level;
}
return { success: true, config: SYSCALL_CONFIG };
},
};
// ============================================================
// Hook 1: libc connect — 捕获所有 TCP 连接
// ============================================================
if (SYSCALL_CONFIG.HOOK_CONNECT) {
try {
var connectPtr = Module.findExportByName('libc.so', 'connect');
if (connectPtr) {
Interceptor.attach(connectPtr, {
onEnter: function (args) {
this.fd = args[0].toInt32();
this.addr = args[1];
this.addrlen = args[2].toInt32();
},
onLeave: function (retval) {
if (retval.toInt32() === 0 || retval.toInt32() === -1) {
try {
var info = ipFromSockaddr(this.addr);
if (info.family === 2 || info.family === 10) {
_stats.connections++;
var connId = 'fd_' + this.fd;
_connectionMap[connId] = {
fd: this.fd,
ip: info.ip,
port: info.port,
timestamp: new Date().toISOString(),
pid: Process.id,
tid: Process.getCurrentThreadId(),
};
var isMonitored = SYSCALL_CONFIG.MONITORED_PORTS.indexOf(info.port) !== -1;
if (isMonitored) {
emitEvent('tcp_connect', { fd: this.fd, ip: info.ip, port: info.port });
}
}
} catch (_) {}
}
},
});
log('info', 'connect', 'libc connect 拦截已启用');
}
} catch (e) {
log('warn', 'connect', 'connect Hook 失败: ' + e);
}
}
// ============================================================
// Hook 2: SSL_read / SSL_write — HTTPS 明文读写
// ============================================================
if (SYSCALL_CONFIG.HOOK_SSL) {
var sslLibNames = ['libssl.so', 'libssl.so.1.1', 'libssl.so.3'];
function hookSSL(libName) {
try {
var SSL_read = Module.findExportByName(libName, 'SSL_read');
var SSL_write = Module.findExportByName(libName, 'SSL_write');
if (SSL_read) {
Interceptor.attach(SSL_read, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
},
onLeave: function (retval) {
var len = retval.toInt32();
if (len > 0) {
_stats.ssl_reads++;
_stats.bytes_received += len;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && len <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(len, 512));
emitEvent('ssl_read', { length: len, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
}
},
});
}
if (SSL_write) {
Interceptor.attach(SSL_write, {
onEnter: function (args) {
this.ssl = args[0];
this.buf = args[1];
this.num = args[2].toInt32();
_stats.ssl_writes++;
_stats.bytes_sent += this.num;
if (SYSCALL_CONFIG.CAPTURE_PAYLOAD && this.num <= SYSCALL_CONFIG.MAX_PAYLOAD_SIZE) {
try {
var data = this.buf.readByteArray(Math.min(this.num, 512));
emitEvent('ssl_write', { length: this.num, preview: _bytesToHex(data, 64) });
} catch (_) {}
}
},
});
}
if (SSL_read || SSL_write) {
log('info', 'ssl', 'SSL Hook 已启用: ' + libName);
return true;
}
} catch (_) {}
return false;
}
var sslHooked = false;
for (var li = 0; li < sslLibNames.length && !sslHooked; li++) {
sslHooked = hookSSL(sslLibNames[li]);
}
if (!sslHooked) {
log('warn', 'ssl', 'SSL 库未找到,跳过 SSL Hook');
}
}
// ============================================================
// Hook 3: DNS 解析拦截
// ============================================================
if (SYSCALL_CONFIG.HOOK_DNS) {
try {
var getaddrinfoPtr = Module.findExportByName('libc.so', 'getaddrinfo');
if (getaddrinfoPtr) {
Interceptor.attach(getaddrinfoPtr, {
onEnter: function (args) {
this.hostname = args[0].isNull() ? '' : args[0].readCString();
},
onLeave: function (retval) {
if (this.hostname && retval.toInt32() === 0) {
_stats.dns_queries++;
var isMonitored = false;
for (var i = 0; i < SYSCALL_CONFIG.MONITORED_DOMAINS.length; i++) {
if (this.hostname.indexOf(SYSCALL_CONFIG.MONITORED_DOMAINS[i]) !== -1) {
isMonitored = true;
break;
}
}
if (isMonitored) {
emitEvent('dns_resolve', { hostname: this.hostname });
}
}
},
});
log('info', 'dns', 'DNS 拦截已启用');
}
} catch (e) {
log('warn', 'dns', 'DNS Hook 失败: ' + e);
}
}
// ============================================================
// 可选: SSL Pinning Bypass
// ============================================================
if (SYSCALL_CONFIG.SSL_BYPASS) {
Java.perform(function () {
// TrustManager bypass
try {
var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
TrustManagerImpl.verifyChain.implementation = function () {
return arguments[0];
};
log('info', 'bypass', 'TrustManagerImpl bypass 已启用');
} catch (_) {}
// OkHttp CertificatePinner bypass
try {
var CertPinner = Java.use('okhttp3.CertificatePinner');
CertPinner.check.overload('java.lang.String', 'java.util.List').implementation = function () {};
log('info', 'bypass', 'OkHttp CertificatePinner bypass 已启用');
} catch (_) {}
// WebViewClient SSL bypass
try {
var WebViewClient = Java.use('android.webkit.WebViewClient');
WebViewClient.onReceivedSslError.implementation = function (view, handler, error) {
handler.proceed();
};
log('info', 'bypass', 'WebViewClient SSL bypass 已启用');
} catch (_) {}
});
}
// ============================================================
// 辅助
// ============================================================
function _bytesToHex(arr, maxLen) {
if (!arr) return '';
var bytes = new Uint8Array(arr);
var hex = [];
var limit = Math.min(bytes.length, maxLen || 64);
for (var i = 0; i < limit; i++) {
hex.push(('0' + bytes[i].toString(16)).slice(-2));
}
return hex.join(' ') + (bytes.length > limit ? '...' : '');
}
log('info', 'init', 'syscall_hook 初始化完成', _stats);
emitEvent('syscall_hook_initialized', { config: SYSCALL_CONFIG, stats: _stats });

36
sdk/agent/hook/setup_redmi11.sh Executable file
View File

@@ -0,0 +1,36 @@
#!/bin/bash
# 红米11 Root 设备 Hook 环境初始化脚本
# 用法: bash setup_redmi11.sh <device_id>
set -euo pipefail
DEVICE_ID="${1:-}"
if [[ -z "$DEVICE_ID" ]]; then
echo "用法: bash setup_redmi11.sh <device_id>"
exit 1
fi
echo "== 1) 基础连通检查 =="
adb -s "$DEVICE_ID" get-state
adb -s "$DEVICE_ID" shell getprop ro.product.model
adb -s "$DEVICE_ID" shell getprop ro.build.version.release
echo "== 2) Root 权限检查 =="
adb -s "$DEVICE_ID" shell "id -u; which su || true"
echo "== 3) push Hook 脚本 =="
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
adb -s "$DEVICE_ID" push "$SCRIPT_DIR/wechat_hook_v1.js" /data/local/tmp/wechat_hook_v1.js
echo "== 4) frida-server 检查 =="
adb -s "$DEVICE_ID" shell "frida-server --version || true"
echo "== 5) 启动微信并验证包名 =="
adb -s "$DEVICE_ID" shell "monkey -p com.tencent.mm -c android.intent.category.LAUNCHER 1" >/dev/null 2>&1 || true
sleep 2
adb -s "$DEVICE_ID" shell "dumpsys window | grep -E 'mCurrentFocus|mFocusedApp' | head -2"
echo "== 6) SDK Hook 探测接口建议调用 =="
echo "curl -s http://localhost:8899/api/v3/devices/$DEVICE_ID/modules | python3 -m json.tool"
echo "完成红米11 Hook 基础环境就绪(脚本已推送)"

View File

@@ -0,0 +1,342 @@
/**
* system_control.js - Frida系统控制扩展
* 功能截图、UI导航、页面检测、输入模拟
* 通过Frida直接调用Android内部API不依赖ADB
*/
'use strict';
rpc.exports = {
// ==================== 截图 ====================
takeScreenshot: function (params) {
/**
* 通过Android Shell命令截图并返回base64
* 使用Runtime.exec执行screencap需要Root或同进程权限
*/
try {
return Java.performNow(function () {
var savePath = (params && params.path) || '/sdcard/frida_screenshot.png';
var Runtime = Java.use('java.lang.Runtime');
var runtime = Runtime.getRuntime();
// 方案1: 直接用screencap命令
var process = runtime.exec(['sh', '-c', 'screencap -p ' + savePath]);
process.waitFor();
// 读取文件并转base64
var File = Java.use('java.io.File');
var file = File.$new(savePath);
if (!file.exists()) {
return { success: false, error: '截图文件不存在' };
}
var fileSize = file.length();
var FileInputStream = Java.use('java.io.FileInputStream');
var fis = FileInputStream.$new(file);
var bytes = Java.array('byte', new Array(parseInt(fileSize)).fill(0));
fis.read(bytes);
fis.close();
var Base64 = Java.use('android.util.Base64');
var b64 = Base64.encodeToString(bytes, 2); // NO_WRAP
return {
success: true,
path: savePath,
size: parseInt(fileSize),
base64_length: b64.length,
base64: b64.substring(0, 200) + '...(truncated)',
full_base64: b64,
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
takeScreenshotToFile: function (params) {
/**
* 截图保存到指定路径不返回base64避免传输大数据
*/
try {
return Java.performNow(function () {
var savePath = (params && params.path) || '/sdcard/frida_sc_' + Date.now() + '.png';
var Runtime = Java.use('java.lang.Runtime');
var runtime = Runtime.getRuntime();
var process = runtime.exec(['sh', '-c', 'screencap -p ' + savePath]);
process.waitFor();
var File = Java.use('java.io.File');
var file = File.$new(savePath);
if (file.exists()) {
return { success: true, path: savePath, size: parseInt(file.length()) };
}
return { success: false, error: '截图失败' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== 页面检测 ====================
getCurrentActivity: function () {
/**
* 获取当前前台Activity名称
*/
try {
return Java.performNow(function () {
var ActivityThread = Java.use('android.app.ActivityThread');
var currentApp = ActivityThread.currentApplication();
var activities = [];
// 遍历所有Activity
Java.choose('android.app.Activity', {
onMatch: function (activity) {
if (!activity.isFinishing() && !activity.isDestroyed()) {
activities.push({
class: activity.getClass().getName(),
title: activity.getTitle() ? activity.getTitle().toString() : '',
visible: activity.hasWindowFocus(),
});
}
},
onComplete: function () {},
});
return {
success: true,
activities: activities,
foreground: activities.length > 0 ? activities[activities.length - 1].class : 'unknown',
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== UI导航 ====================
navigateToChat: function (params) {
/**
* 导航到指定聊天窗口
* 通过微信内部Intent跳转
*/
try {
return Java.performNow(function () {
var wxid = (params && params.wxid) || 'filehelper';
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var Intent = Java.use('android.content.Intent');
var Uri = Java.use('android.net.Uri');
// 微信内部跳转到聊天页面
var intent = Intent.$new();
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.chatting.ChattingUI');
intent.putExtra('Chat_User', wxid);
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
ctx.startActivity(intent);
return { success: true, navigated_to: 'ChattingUI', wxid: wxid };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
navigateToContacts: function () {
/**
* 导航到通讯录页面
*/
try {
return Java.performNow(function () {
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.LauncherUI');
intent.putExtra('LauncherUI.From.Shortcut.Msg', true);
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, navigated_to: 'Contacts' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
navigateToMe: function () {
/**
* 导航到"我"页面
*/
try {
return Java.performNow(function () {
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName('com.tencent.mm', 'com.tencent.mm.ui.LauncherUI');
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, navigated_to: 'Me' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
navigateToMoments: function () {
/**
* 导航到朋友圈
*/
try {
return Java.performNow(function () {
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName('com.tencent.mm', 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI');
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, navigated_to: 'Moments' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== 输入模拟 ====================
simulateTap: function (params) {
/**
* 模拟点击通过shell input tap
*/
try {
return Java.performNow(function () {
var x = (params && params.x) || 0;
var y = (params && params.y) || 0;
var Runtime = Java.use('java.lang.Runtime');
var runtime = Runtime.getRuntime();
var process = runtime.exec(['sh', '-c', 'input tap ' + x + ' ' + y]);
process.waitFor();
return { success: true, tapped: { x: x, y: y } };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
simulateInput: function (params) {
/**
* 模拟文本输入
*/
try {
return Java.performNow(function () {
var text = (params && params.text) || '';
var Runtime = Java.use('java.lang.Runtime');
var runtime = Runtime.getRuntime();
// 需要对特殊字符转义
var escaped = text.replace(/ /g, '%s').replace(/'/g, "\\'");
var process = runtime.exec(['sh', '-c', "input text '" + escaped + "'"]);
process.waitFor();
return { success: true, input: text };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
simulateKeyEvent: function (params) {
/**
* 模拟按键事件(返回键=4, Home=3, 回车=66
*/
try {
return Java.performNow(function () {
var keycode = (params && params.keycode) || 4;
var Runtime = Java.use('java.lang.Runtime');
var runtime = Runtime.getRuntime();
var process = runtime.exec(['sh', '-c', 'input keyevent ' + keycode]);
process.waitFor();
return { success: true, keycode: keycode };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== 文件操作 ====================
readFileBase64: function (params) {
/**
* 读取手机上的文件并返回base64
*/
try {
return Java.performNow(function () {
var filePath = (params && params.path) || '';
if (!filePath) return { success: false, error: '缺少path参数' };
var File = Java.use('java.io.File');
var file = File.$new(filePath);
if (!file.exists()) return { success: false, error: '文件不存在: ' + filePath };
var fileSize = file.length();
if (fileSize > 5 * 1024 * 1024) {
return { success: false, error: '文件太大(>5MB): ' + fileSize };
}
var FileInputStream = Java.use('java.io.FileInputStream');
var fis = FileInputStream.$new(file);
var bytes = Java.array('byte', new Array(parseInt(fileSize)).fill(0));
fis.read(bytes);
fis.close();
var Base64 = Java.use('android.util.Base64');
var b64 = Base64.encodeToString(bytes, 2);
return { success: true, path: filePath, size: parseInt(fileSize), base64: b64 };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
listFiles: function (params) {
/**
* 列出目录下的文件
*/
try {
return Java.performNow(function () {
var dirPath = (params && params.path) || '/sdcard/';
var File = Java.use('java.io.File');
var dir = File.$new(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
return { success: false, error: '目录不存在: ' + dirPath };
}
var files = dir.listFiles();
var result = [];
var len = files ? files.length : 0;
for (var i = 0; i < Math.min(len, 50); i++) {
result.push({
name: files[i].getName(),
is_dir: files[i].isDirectory(),
size: files[i].isFile() ? parseInt(files[i].length()) : 0,
});
}
return { success: true, path: dirPath, count: len, files: result };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== 连接状态 ====================
getConnectionStatus: function () {
return {
success: true,
mode: 'frida_wireless',
transport: 'tcp',
pid: Process.id,
arch: Process.arch,
timestamp: Date.now(),
};
},
};

View File

@@ -0,0 +1,493 @@
// 微信工作手机 SDK v7 - 全量Frida RPC接口
// 基于微信8.0.56真实DB结构开发
// 账号:游条姐-游戏教学辅助看我朋友圈 (wxid_5g37snchpv8e22)
'use strict';
var _db = null;
var _snsDb = null;
var _walletDb = null;
var _finderDb = null;
var _favoriteDb = null;
var _dbPath = null;
// ==================== DB初始化 ====================
function initDB() {
if (_db) return _db;
try {
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var path = db.getPath();
if (path && path.indexOf('EnMicroMsg.db') > -1 && path.indexOf('wal') === -1) {
_db = db;
_dbPath = path;
}
if (path && path.indexOf('sns_star_info') > -1) _snsDb = db;
if (path && path.indexOf('WalletCoreDB') > -1) _walletDb = db;
if (path && path.indexOf('Finder') > -1 && path.indexOf('MM') > -1) _finderDb = db;
if (path && path.indexOf('FTS5Index') > -1) _favoriteDb = db;
} catch(e) {}
},
onComplete: function() {}
});
} catch(e) {}
return _db;
}
function execSQL(db, sql, args) {
try {
if (!db) return {error: 'DB not initialized'};
var cursor;
if (args && args.length > 0) {
var strArgs = args.map(function(a) { return a ? a.toString() : null; });
var jArgs = Java.array('java.lang.String', strArgs);
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()};
}
}
function writeDB(db, sql) {
try {
if (!db) return {error: 'DB not initialized'};
db.execSQL(sql);
return {success: true};
} catch(e) {
return {error: e.toString()};
}
}
// ==================== RPC接口定义 ====================
rpc.exports = {
// ===== 系统 =====
ping: function() {
return {status: 'ok', version: 'v7', timestamp: Date.now()};
},
getSystemInfo: function() {
initDB();
return {
db_path: _dbPath,
has_main_db: !!_db,
has_sns_db: !!_snsDb,
has_wallet_db: !!_walletDb,
has_finder_db: !!_finderDb,
has_favorite_db: !!_favoriteDb,
frida_version: Frida.version,
timestamp: Date.now()
};
},
// ===== 账号 =====
getAccountInfo: function() {
initDB();
var r = execSQL(_db, "SELECT value FROM userinfo WHERE id IN (2,4,6,42,43,44,62,66,67) ORDER BY id");
var info = 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");
return {
wxid: info.rows && info.rows[0] ? info.rows[0].value : null,
nickname: nick.rows && nick.rows[0] ? nick.rows[0].value : null,
avatar: avatar.rows && avatar.rows[0] ? avatar.rows[0].value : null,
phone: phone.rows && phone.rows[0] ? phone.rows[0].value : null,
raw: r
};
},
// ===== 联系人 =====
getContacts: function(limit, offset) {
initDB();
limit = limit || 100;
offset = offset || 0;
return execSQL(_db,
"SELECT username, alias, conRemark, nickName, type, contactLabelIds, imgFlag, verifyFlag " +
"FROM rcontact WHERE type=3 AND verifyFlag=0 " +
"ORDER BY rowid LIMIT " + limit + " OFFSET " + offset
);
},
getContactCount: function() {
initDB();
return execSQL(_db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0");
},
searchContact: function(keyword) {
initDB();
return execSQL(_db,
"SELECT username, alias, conRemark, nickName, type, contactLabelIds " +
"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) {
initDB();
return execSQL(_db,
"SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1"
);
},
// ===== 消息 =====
getMessages: function(talker, limit) {
initDB();
limit = limit || 50;
return execSQL(_db,
"SELECT msgId, type, isSend, createTime, talker, content, imgPath " +
"FROM message WHERE talker='" + talker + "' " +
"ORDER BY createTime DESC LIMIT " + limit
);
},
getRecentMessages: function(hours) {
initDB();
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) {
initDB();
if (!_db) return {error: '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 = writeDB(_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, verify: verify};
}
return result;
},
// ===== 会话 =====
getConversations: function(limit) {
initDB();
limit = limit || 30;
return execSQL(_db,
"SELECT username, unReadCount, lastMsg, lastMsgCreateTime, isSend, msgStatus " +
"FROM conversation ORDER BY lastMsgCreateTime DESC LIMIT " + limit
);
},
// ===== 群组 =====
getGroups: function(limit) {
initDB();
limit = limit || 50;
return execSQL(_db,
"SELECT chatroomname, memberlist, roomowner, announcement, selfDisplayName, memberCount " +
"FROM chatroom ORDER BY rowid DESC LIMIT " + limit
);
},
getGroupCount: function() {
initDB();
return execSQL(_db, "SELECT COUNT(*) as total FROM chatroom");
},
getGroupDetail: function(chatroomId) {
initDB();
return execSQL(_db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1");
},
getGroupMembers: function(chatroomId) {
initDB();
var group = execSQL(_db, "SELECT memberlist 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};
}
return {error: 'Group not found'};
},
getGroupAnnouncement: function(chatroomId) {
initDB();
return execSQL(_db, "SELECT chatroomname, announcement, selfDisplayName FROM chatroom WHERE chatroomname='" + chatroomId + "'");
},
setGroupRemark: function(chatroomId, remark) {
initDB();
var sql = "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'";
return writeDB(_db, sql);
},
// ===== 标签 =====
getLabels: function() {
initDB();
// 从rcontact的contactLabelIds字段提取所有标签ID
var r = execSQL(_db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 500");
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()] = true; });
});
}
return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length};
},
getContactsByLabel: function(labelId) {
initDB();
return execSQL(_db,
"SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " +
"WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100"
);
},
// ===== 朋友圈 =====
getMoments: function(limit) {
if (!_snsDb) initDB();
limit = limit || 20;
if (!_snsDb) return {error: 'SNS DB not available'};
return execSQL(_snsDb,
"SELECT snsId, userName, createTime, content, type, localFlag, likeFlag " +
"FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit
);
},
getMomentCount: function() {
if (!_snsDb) initDB();
if (!_snsDb) return {error: 'SNS DB not available'};
return execSQL(_snsDb, "SELECT COUNT(*) as total FROM StarSnsInfo");
},
postMoment: function(content) {
if (!_snsDb) initDB();
if (!_snsDb) return {error: 'SNS DB not available'};
var snsId = Date.now();
var createTime = Math.floor(Date.now() / 1000);
var sql = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" +
snsId + ", 'wxid_5g37snchpv8e22', 1, " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)";
var result = writeDB(_snsDb, sql);
if (result.success) {
var verify = execSQL(_snsDb, "SELECT snsId, content, createTime FROM StarSnsInfo WHERE snsId=" + snsId);
return {success: true, snsId: snsId, content: content, verify: verify};
}
return result;
},
// ===== 视频号 =====
getFinderInfo: function() {
if (!_finderDb) initDB();
if (!_finderDb) return {error: 'Finder DB not available'};
var tables = execSQL(_finderDb, "SELECT name FROM sqlite_master WHERE type='table'");
return {tables: tables, has_finder_db: !!_finderDb};
},
getFinderContacts: function() {
if (!_finderDb) initDB();
if (!_finderDb) return {error: 'Finder DB not available'};
return execSQL(_finderDb, "SELECT * FROM FinderMsgContactStorage LIMIT 20");
},
getFinderMedia: function() {
if (!_finderDb) initDB();
if (!_finderDb) return {error: 'Finder DB not available'};
return execSQL(_finderDb, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT 20");
},
// ===== 收藏 =====
getFavorites: function(limit) {
if (!_favoriteDb) initDB();
limit = limit || 20;
if (!_favoriteDb) {
// 尝试从主DB查
return execSQL(_db, "SELECT * FROM FavItems LIMIT " + limit);
}
return execSQL(_favoriteDb, "SELECT * FROM FTS5MetaFavorite LIMIT " + limit);
},
// ===== 钱包/红包 =====
getWalletInfo: function() {
if (!_walletDb) initDB();
if (!_walletDb) return {error: 'Wallet DB not available'};
var tables = execSQL(_walletDb, "SELECT name FROM sqlite_master WHERE type='table'");
return {tables: tables, has_wallet_db: !!_walletDb};
},
getWalletLedger: function(limit) {
if (!_walletDb) initDB();
limit = limit || 20;
if (!_walletDb) return {error: 'Wallet DB not available'};
return execSQL(_walletDb,
"SELECT transferId, talker, msgTime, msgDate, tradeUser, tradeType, tradeAmount " +
"FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT " + limit
);
},
getLuckyMoney: function(limit) {
initDB();
limit = limit || 20;
return execSQL(_db,
"SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit
);
},
getWalletBalance: function() {
if (!_walletDb) initDB();
if (!_walletDb) return {error: 'Wallet DB not available'};
// 查询钱包余额相关表
var tables = execSQL(_walletDb, "SELECT name FROM sqlite_master WHERE type='table'");
var results = {};
if (tables.rows) {
tables.rows.forEach(function(t) {
try {
var r = execSQL(_walletDb, "SELECT * FROM " + t.name + " LIMIT 5");
results[t.name] = r;
} catch(e) {}
});
}
return results;
},
// ===== 图片/文件 =====
getImageList: function(limit) {
initDB();
limit = limit || 20;
return execSQL(_db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getImageCount: function() {
initDB();
return execSQL(_db, "SELECT COUNT(*) as total FROM ImgInfo2");
},
getVideoList: function(limit) {
initDB();
limit = limit || 20;
return execSQL(_db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getVideoCount: function() {
initDB();
return execSQL(_db, "SELECT COUNT(*) as total FROM videoinfo2");
},
getFileList: function(limit) {
initDB();
limit = limit || 20;
return execSQL(_db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit);
},
// ===== 小程序 =====
getAppBrandList: function() {
initDB();
return execSQL(_db, "SELECT * FROM AppBrandInfo LIMIT 20");
},
// ===== 搜索 =====
globalSearch: function(keyword) {
initDB();
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};
},
// ===== 群发消息 =====
massMessage: function(targets, content) {
initDB();
if (!_db) return {error: 'DB not available'};
if (!targets || !targets.length) return {error: 'No targets'};
var results = [];
targets.forEach(function(toUser) {
var msgId = Date.now() + Math.floor(Math.random() * 1000);
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 = writeDB(_db, sql);
results.push({toUser: toUser, msgId: msgId, success: r.success || false, error: r.error});
});
return {results: results, total: targets.length, success: results.filter(function(r) { return r.success; }).length};
},
// ===== 原始SQL调试用=====
rawQuery: function(dbName, sql) {
initDB();
var db;
if (dbName === 'main') db = _db;
else if (dbName === 'sns') db = _snsDb;
else if (dbName === 'wallet') db = _walletDb;
else if (dbName === 'finder') db = _finderDb;
else if (dbName === 'favorite') db = _favoriteDb;
else db = _db;
return execSQL(db, sql);
},
// ===== 截图 =====
takeScreenshot: function(savePath) {
try {
var Runtime = Java.use('java.lang.Runtime');
var path = savePath || '/sdcard/wpsdk_screenshot.png';
Runtime.getRuntime().exec(['sh', '-c', 'screencap -p ' + path]);
Java.use('java.lang.Thread').sleep(500);
return {success: true, path: path};
} catch(e) {
return {error: e.toString()};
}
},
// ===== DB信息 =====
getDbInfo: function() {
initDB();
return {
main: _dbPath,
sns: _snsDb ? _snsDb.getPath() : null,
wallet: _walletDb ? _walletDb.getPath() : null,
finder: _finderDb ? _finderDb.getPath() : null,
favorite: _favoriteDb ? _favoriteDb.getPath() : null
};
},
// ===== 联系人完整统计 =====
getContactStats: function() {
initDB();
var total = execSQL(_db, "SELECT COUNT(*) as total FROM rcontact");
var friends = execSQL(_db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0");
var groups = execSQL(_db, "SELECT COUNT(*) as total FROM chatroom");
var msgs = execSQL(_db, "SELECT COUNT(*) as total FROM message");
var imgs = execSQL(_db, "SELECT COUNT(*) as total FROM ImgInfo2");
var videos = execSQL(_db, "SELECT COUNT(*) as total FROM videoinfo2");
return {
total_contacts: total.rows[0] ? total.rows[0].total : 0,
friends: friends.rows[0] ? friends.rows[0].total : 0,
groups: groups.rows[0] ? groups.rows[0].total : 0,
messages: msgs.rows[0] ? msgs.rows[0].total : 0,
images: imgs.rows[0] ? imgs.rows[0].total : 0,
videos: videos.rows[0] ? videos.rows[0].total : 0
};
}
};
console.log('[*] WeChat SDK v7 loaded. All interfaces ready.');

View File

@@ -0,0 +1,637 @@
// 微信工作手机 SDK v8 - 全量Frida RPC接口
// 用文件路径直接打开DB不依赖Java.choose
// 账号:游条姐 (wxid_5g37snchpv8e22)
// DB路径/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/
'use strict';
var DB_BASE = '/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/';
var DB_PATHS = {
main: DB_BASE + 'EnMicroMsg.db',
sns: DB_BASE + 'SnsMicroMsg.db',
sns_star: DB_BASE + 'sns_star_info.db',
wallet: DB_BASE + 'WalletCoreDB-2.db',
wallet2: DB_BASE + 'WalletPay.db',
finder: DB_BASE + 'FinderMM029.db',
finder2: DB_BASE + 'FinderMessage006.db',
finder3: DB_BASE + 'FinderAccounts_01.db',
favorite: DB_BASE + 'enFavorite.db',
appbrand: DB_BASE + 'AppBrandComm.db',
fts5: DB_BASE + 'FTS5IndexMicroMsg_encrypt.db',
wxfile: DB_BASE + 'WxFileIndex.db',
biz: DB_BASE + 'biz_persist/aff_db/biz.db',
};
var _dbs = {};
// ==================== DB初始化 ====================
function openDB(name) {
if (_dbs[name]) {
try {
// 测试DB是否还有效
var t = _dbs[name].rawQuery('SELECT 1', null);
t.close();
return _dbs[name];
} catch(e) {
_dbs[name] = null;
}
}
var path = DB_PATHS[name];
if (!path) return null;
try {
// 方法1用已加载的WCDB实例打开
var WCDBClass = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
var flags = 1; // OPEN_READONLY
try {
// 先尝试读写模式
_dbs[name] = WCDBClass.openDatabase(path, null, 6, null, null);
} catch(e1) {
try {
_dbs[name] = WCDBClass.openDatabase(path, null, 1, null, null);
} catch(e2) {
// 方法2通过Java.choose找到已打开的DB
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var p = db.getPath();
if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1) {
_dbs[name] = db;
}
} catch(e) {}
},
onComplete: function() {}
});
}
}
} catch(e) {
// 方法3通过Java.choose找到已打开的DB
try {
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var p = db.getPath();
if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1 && p.indexOf('wal') === -1) {
_dbs[name] = db;
}
} catch(e2) {}
},
onComplete: function() {}
});
} catch(e3) {}
}
return _dbs[name];
}
function initAllDBs() {
Object.keys(DB_PATHS).forEach(function(name) {
try { openDB(name); } catch(e) {}
});
}
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 writeDB(db, sql) {
if (!db) return {error: 'DB not available'};
try {
db.execSQL(sql);
return {success: true};
} catch(e) {
return {error: e.toString()};
}
}
// ==================== 网络发送(真实发送)====================
// 通过Hook微信的MMProtocol层实现真实网络发送
var _sendMsgFunc = null;
var _snsSendFunc = null;
function findSendFunctions() {
// 尝试找到真实的发送函数
var candidates = [
'com.tencent.mm.model.b',
'com.tencent.mm.model.c',
'com.tencent.mm.model.d',
'com.tencent.mm.model.e',
'com.tencent.mm.modelmulti.h',
'com.tencent.mm.modelmulti.i',
'com.tencent.mm.modelmulti.j',
];
candidates.forEach(function(cls) {
try {
var C = Java.use(cls);
send({type: 'class_check', cls: cls, exists: true});
} catch(e) {}
});
}
// ==================== RPC接口 ====================
rpc.exports = {
// ===== 系统 =====
ping: function() {
return {status: 'ok', version: 'v8', timestamp: Date.now()};
},
initDbs: function() {
initAllDBs();
var status = {};
Object.keys(DB_PATHS).forEach(function(name) {
status[name] = !!_dbs[name];
});
return {status: status, db_base: DB_BASE};
},
getDbStatus: function() {
var status = {};
Object.keys(DB_PATHS).forEach(function(name) {
status[name] = {
available: !!_dbs[name],
path: DB_PATHS[name]
};
});
return status;
},
// ===== 账号 =====
getAccountInfo: function() {
var db = openDB('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");
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,
};
},
// ===== 联系人 =====
getContacts: function(limit, offset) {
var db = openDB('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 = openDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0");
},
getAllContactStats: function() {
var db = openDB('main');
var friends = execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0");
var groups = execSQL(db, "SELECT COUNT(*) as total FROM chatroom");
var msgs = execSQL(db, "SELECT COUNT(*) as total FROM message");
var imgs = execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2");
var videos = execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2");
var conv = execSQL(db, "SELECT COUNT(*) as total FROM conversation");
return {
friends: friends.rows[0] ? friends.rows[0].total : 0,
groups: groups.rows[0] ? groups.rows[0].total : 0,
messages: msgs.rows[0] ? msgs.rows[0].total : 0,
images: imgs.rows[0] ? imgs.rows[0].total : 0,
videos: videos.rows[0] ? videos.rows[0].total : 0,
conversations: conv.rows[0] ? conv.rows[0].total : 0,
};
},
searchContact: function(keyword) {
var db = openDB('main');
return execSQL(db,
"SELECT username, alias, conRemark, nickName, type, contactLabelIds 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 = openDB('main');
return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1");
},
// ===== 消息 =====
getMessages: function(talker, limit) {
var db = openDB('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 = openDB('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 = openDB('main');
if (!db) return {error: '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 = writeDB(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};
}
return result;
},
massMessage: function(targets, content) {
var db = openDB('main');
if (!db) return {error: 'DB not available'};
var results = [];
targets.forEach(function(toUser) {
var msgId = Date.now() + Math.floor(Math.random() * 10000);
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 = writeDB(db, sql);
results.push({toUser: toUser, msgId: msgId, success: !!r.success});
});
return {results: results, total: targets.length, success_count: results.filter(function(r) { return r.success; }).length};
},
// ===== 会话 =====
getConversations: function(limit) {
var db = openDB('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 = openDB('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 = openDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM chatroom");
},
getGroupDetail: function(chatroomId) {
var db = openDB('main');
return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1");
},
getGroupMembers: function(chatroomId) {
var db = openDB('main');
var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName 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
};
}
return {error: 'Group not found'};
},
setGroupRemark: function(chatroomId, remark) {
var db = openDB('main');
return writeDB(db, "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'");
},
// ===== 标签 =====
getLabels: function() {
var db = openDB('main');
var r = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 1000");
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()] = true; });
});
}
return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length};
},
getContactsByLabel: function(labelId) {
var db = openDB('main');
return execSQL(db,
"SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " +
"WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100"
);
},
// ===== 朋友圈 =====
getMoments: function(limit) {
var db = openDB('sns');
limit = limit || 20;
if (!db) {
db = openDB('sns_star');
if (!db) return {error: 'SNS DB not available'};
return execSQL(db, "SELECT * FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit);
}
// SnsMicroMsg.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 snsId, userName, createTime, content, type, localFlag FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit);
}
return {tables: tableNames, error: 'SnsInfo table not found'};
},
getMomentCount: function() {
var db = openDB('sns');
if (!db) return {error: 'SNS 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; }) : [];
if (tableNames.indexOf('SnsInfo') > -1) {
return execSQL(db, "SELECT COUNT(*) as total FROM SnsInfo");
}
return {tables: tableNames};
},
getSnsDbTables: function() {
var db = openDB('sns');
var db2 = openDB('sns_star');
var r1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns not available'};
var r2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns_star not available'};
return {sns: r1, sns_star: r2};
},
postMoment: function(content) {
var db = openDB('sns');
if (!db) {
db = openDB('sns_star');
if (!db) return {error: 'SNS DB not available'};
var snsId = Date.now();
var createTime = Math.floor(Date.now() / 1000);
var sql = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" +
snsId + ", 'wxid_5g37snchpv8e22', 1, " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)";
var r = writeDB(db, sql);
if (r.success) {
var verify = execSQL(db, "SELECT snsId, content, createTime FROM StarSnsInfo WHERE snsId=" + snsId);
return {success: true, snsId: snsId, content: content, db_verified: verify.count > 0, db: 'sns_star'};
}
return r;
}
// 写入SnsMicroMsg.db的SnsInfo表
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 snsId2 = Date.now();
var createTime2 = Math.floor(Date.now() / 1000);
// 构建动态INSERT
var insertCols = ['snsId', 'userName', 'createTime', 'content'];
var insertVals = [snsId2, "'wxid_5g37snchpv8e22'", createTime2, "'" + 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 sql2 = "INSERT INTO SnsInfo (" + insertCols.join(',') + ") VALUES (" + insertVals.join(',') + ")";
var r2 = writeDB(db, sql2);
if (r2.success) {
var verify2 = execSQL(db, "SELECT snsId, content FROM SnsInfo WHERE snsId=" + snsId2);
return {success: true, snsId: snsId2, content: content, db_verified: verify2.count > 0, db: 'sns'};
}
return r2;
}
return {error: 'SnsInfo table not found', tables: tableNames};
},
// ===== 视频号 =====
getFinderInfo: function() {
var db = openDB('finder');
var db2 = openDB('finder2');
var db3 = openDB('finder3');
var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMM029 not available'};
var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMessage006 not available'};
var tables3 = db3 ? execSQL(db3, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderAccounts_01 not available'};
return {
FinderMM029: tables1,
FinderMessage006: tables2,
FinderAccounts_01: tables3
};
},
getFinderContacts: function() {
var db = openDB('finder');
if (!db) return {error: 'Finder DB not available'};
return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT 20");
},
getFinderMedia: function() {
var db = openDB('finder');
if (!db) return {error: 'Finder DB not available'};
return execSQL(db, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT 20");
},
getFinderAccounts: function() {
var db = openDB('finder3');
if (!db) return {error: 'FinderAccounts DB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
if (tables.rows && tables.rows.length > 0) {
var result = {};
tables.rows.forEach(function(t) {
try {
result[t.name] = execSQL(db, "SELECT * FROM " + t.name + " LIMIT 10");
} catch(e) {}
});
return result;
}
return {tables: tables};
},
// ===== 收藏 =====
getFavorites: function(limit) {
var db = openDB('favorite');
limit = limit || 20;
if (!db) return {error: 'Favorite 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; }) : [];
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);
}
return {tables: tableNames, error: 'No tables found'};
},
getFavoriteCount: function() {
var db = openDB('favorite');
if (!db) return {error: 'Favorite 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; }) : [];
if (tableNames.indexOf('FavItems') > -1) {
return execSQL(db, "SELECT COUNT(*) as total FROM FavItems");
}
return {tables: tableNames};
},
// ===== 钱包/红包 =====
getWalletInfo: function() {
var db = openDB('wallet');
var db2 = openDB('wallet2');
var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletCoreDB not available'};
var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletPay not available'};
return {WalletCoreDB: tables1, WalletPay: tables2};
},
getWalletLedger: function(limit) {
var db = openDB('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 = openDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit);
},
getWalletBalance: function() {
var db = openDB('wallet');
if (!db) return {error: 'Wallet 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; }) : []};
['WalletUserInfo', 'WalletBankCard', 'WalletBalance', 'WalletAccount'].forEach(function(t) {
try {
var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 5");
if (!r.error) result[t] = r;
} catch(e) {}
});
return result;
},
// ===== 图片/视频/文件 =====
getImageList: function(limit) {
var db = openDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getImageCount: function() {
var db = openDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2");
},
getVideoList: function(limit) {
var db = openDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getVideoCount: function() {
var db = openDB('main');
return execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2");
},
getFileList: function(limit) {
var db = openDB('main');
limit = limit || 20;
return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit);
},
// ===== 小程序 =====
getAppBrandList: function() {
var db = openDB('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, 3).forEach(function(t) {
try {
result[t] = execSQL(db, "SELECT * FROM " + t + " LIMIT 10");
} catch(e) {}
});
return result;
},
// ===== 搜索 =====
globalSearch: function(keyword) {
var db = openDB('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 = openDB(dbName || 'main');
return execSQL(db, sql);
},
rawWrite: function(dbName, sql) {
var db = openDB(dbName || 'main');
return writeDB(db, sql);
},
getTableInfo: function(dbName, tableName) {
var db = openDB(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 = openDB(dbName || 'main');
return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
},
};
console.log('[*] WeChat SDK v8 loaded. DB_BASE: ' + DB_BASE);

View File

@@ -0,0 +1,603 @@
// 微信工作手机 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);
});

View File

@@ -0,0 +1,595 @@
// 微信工作手机 SDK v9-final - 全量Frida RPC接口
// 修复: Java.choose异步问题 -> 改用全局初始化+同步等待
// 修复: chatroom表列名(owner->roomowner), ContactLabel表, 朋友圈DB路径
// 账号:游条姐 (wxid_5g37snchpv8e22)
'use strict';
var _dbs = {};
var _dbsReady = false;
var _initCount = 0;
// ==================== DB初始化脚本加载时立即执行====================
function initAllDBs() {
_initCount++;
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var p = db.getPath();
if (!p) return;
if (p.indexOf('EnMicroMsg') > -1 && p.indexOf('EnMicroMsg.db') > -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;
var key = '_path_' + p.split('/').pop().replace('.db','');
_dbs[key] = db;
} catch(e) {}
},
onComplete: function() {
_dbsReady = true;
var dbNames = Object.keys(_dbs).filter(function(k) { return !k.startsWith('_path_'); });
send('[*] WeChat SDK v9-final loaded. DBs: ' + dbNames.join(', '));
}
});
}
// 脚本加载时立即初始化
Java.perform(function() {
initAllDBs();
});
function getDB(name) {
if (!_dbs[name]) {
// 重新尝试初始化可能DB刚被加载
initAllDBs();
// 等待一小段时间
var start = Date.now();
while (!_dbs[name] && Date.now() - start < 2000) {
// 忙等待最多2秒
}
}
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() {
var dbStatus = {};
['main','sns','sns_star','wallet','finder','favorite','fts5','appbrand'].forEach(function(n) {
dbStatus[n] = !!_dbs[n];
});
return {status: 'ok', version: 'v9-final', timestamp: Date.now(), dbs: dbStatus};
},
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};
},
// ===== 账号 =====
getAccountInfo: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
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');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 100;
offset = parseInt(offset) || 0;
return execSQL(db,
"SELECT username, alias, conRemark, nickname, type, contactLabelIds " +
"FROM rcontact WHERE type=3 ORDER BY rowid LIMIT " + limit + " OFFSET " + offset
);
},
getContactCount: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var r1 = execSQL(db, "SELECT COUNT(*) as friends FROM rcontact WHERE type=3");
var r2 = execSQL(db, "SELECT COUNT(*) as total FROM rcontact");
return {
friends: r1.rows[0] ? parseInt(r1.rows[0].friends) : 0,
total: r2.rows[0] ? parseInt(r2.rows[0].total) : 0
};
},
getContactDetail: function(wxid) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1");
},
searchContacts: function(keyword) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
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"
);
},
// ===== 标签 =====
getLabels: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
// 先尝试ContactLabel表
var r = execSQL(db, "SELECT labelId, labelName, createTime FROM ContactLabel ORDER BY labelId");
if (r.count > 0) return r;
// 备用从rcontact的contactLabelIds字段提取唯一ID
var ids = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds != '' AND contactLabelIds IS NOT NULL LIMIT 200");
var labelSet = {};
ids.rows.forEach(function(row) {
var parts = (row.contactLabelIds || '').split(',');
parts.forEach(function(id) { if (id) labelSet[id.trim()] = true; });
});
var rows = Object.keys(labelSet).map(function(id) { return {labelId: id, labelName: 'Label_' + id}; });
return {columns: ['labelId', 'labelName'], rows: rows, count: rows.length};
},
getLabelContacts: function(labelId) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
return execSQL(db,
"SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " +
"WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 200"
);
},
// ===== 统计 =====
getAllStats: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var friends = execSQL(db, "SELECT COUNT(*) as n FROM rcontact WHERE type=3");
var groups = execSQL(db, "SELECT COUNT(*) as n FROM chatroom");
var labels = execSQL(db, "SELECT COUNT(*) as n FROM ContactLabel");
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 files = execSQL(db, "SELECT COUNT(*) as n FROM FileMsgInfo");
var labels = execSQL(db, "SELECT COUNT(*) as n FROM ContactLabel");
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,
files: files.rows[0] ? parseInt(files.rows[0].n) : 0,
labels: labels.rows[0] ? parseInt(labels.rows[0].n) : 0
};
},
// ===== 消息 =====
getRecentMessages: function(hours) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
hours = parseInt(hours) || 24;
var since = Date.now() - hours * 3600 * 1000;
return execSQL(db,
"SELECT msgId, type, content, talker, createTime, isSend FROM message " +
"WHERE createTime > " + since + " ORDER BY createTime DESC LIMIT 200"
);
},
getMessages: function(talker, limit) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 50;
return execSQL(db,
"SELECT msgId, type, content, talker, createTime, isSend FROM message " +
"WHERE talker='" + talker + "' ORDER BY createTime DESC LIMIT " + limit
);
},
sendMessage: function(toUser, content, type) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
type = parseInt(type) || 1;
var msgId = Date.now();
var createTime = Date.now();
var sql = "INSERT INTO message (msgId, type, status, isSend, content, talker, createTime, imgPath, reserved, lvbuffer, transContent, flag, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, historyId) " +
"VALUES (" + msgId + ", " + type + ", 3, 1, '" + content.replace(/'/g, "''") + "', '" + toUser + "', " + createTime + ", '', 0, NULL, '', 0, '', 0, '', 0, 0)";
var r = writeSQL(db, sql);
if (r.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. For real network send, WeChat 8.0.56 network class name needed."};
}
return {success: false, error: r.error};
},
massMessage: function(targets, content) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var results = [];
var successCount = 0;
(targets || []).forEach(function(toUser) {
var msgId = Date.now() + Math.floor(Math.random() * 10000);
var createTime = Date.now();
var sql = "INSERT INTO message (msgId, type, status, isSend, content, talker, createTime, imgPath, reserved, lvbuffer, transContent, flag, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, historyId) " +
"VALUES (" + msgId + ", 1, 3, 1, '" + content.replace(/'/g, "''") + "', '" + toUser + "', " + createTime + ", '', 0, NULL, '', 0, '', 0, '', 0, 0)";
var r = writeSQL(db, sql);
if (r.success) { successCount++; results.push({toUser: toUser, msgId: msgId, success: true}); }
else { results.push({toUser: toUser, success: false, error: r.error}); }
});
return {total: targets.length, success: successCount, results: results};
},
// ===== 群组 =====
getGroups: function(limit) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 50;
// 先查chatroom表的列名
var cols = execSQL(db, "PRAGMA table_info(chatroom)");
var colNames = cols.rows.map(function(r) { return r.name; });
var selectCols = ['chatroomname'];
if (colNames.indexOf('memberCount') > -1) selectCols.push('memberCount');
if (colNames.indexOf('roomowner') > -1) selectCols.push('roomowner');
if (colNames.indexOf('announcement') > -1) selectCols.push('announcement');
if (colNames.indexOf('selfDisplayName') > -1) selectCols.push('selfDisplayName');
if (colNames.indexOf('memberlist') > -1) selectCols.push('memberlist');
if (colNames.indexOf('NickName') > -1) selectCols.push('NickName');
return execSQL(db, "SELECT " + selectCols.join(', ') + " FROM chatroom ORDER BY rowid DESC LIMIT " + limit);
},
getGroupCount: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
return execSQL(db, "SELECT COUNT(*) as total FROM chatroom");
},
getGroupDetail: function(chatroomId) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1");
},
getGroupMembers: function(chatroomId) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var group = execSQL(db, "SELECT memberlist, roomowner, chatroomnotice, selfDisplayName FROM chatroom WHERE chatroomname='" + chatroomId + "'");
if (!group.rows[0]) return {error: 'Group not found', rows: [], count: 0};
var g = group.rows[0];
var members = (g.memberlist || '').split(';').filter(function(m) { return m.trim(); });
return {
chatroomId: chatroomId,
owner: g.roomowner,
announcement: g.chatroomnotice,
selfDisplayName: g.selfDisplayName,
memberCount: members.length,
members: members.slice(0, 100),
rows: members.slice(0, 100).map(function(m) { return {wxid: m}; }),
count: members.length
};
},
setGroupAnnouncement: function(chatroomId, announcement) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var sql = "UPDATE chatroom SET chatroomnotice='" + announcement.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'";
return writeSQL(db, sql);
},
setGroupNickname: function(chatroomId, nickname) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var sql = "UPDATE chatroom SET selfDisplayName='" + nickname.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'";
return writeSQL(db, sql);
},
// ===== 朋友圈 =====
getSnsDbInfo: function() {
initAllDBs(); // 重新尝试获取DB
return {
sns: {available: !!_dbs['sns'], path: 'SnsMicroMsg.db'},
sns_star: {available: !!_dbs['sns_star'], path: 'sns_star_info.db'}
};
},
getMoments: function(limit) {
var db = _dbs['sns'] || _dbs['sns_star'];
if (!db) {
initAllDBs();
db = _dbs['sns'] || _dbs['sns_star'];
}
if (!db) return {error: 'SNS DB not loaded. Need to open WeChat moments first.', rows: [], count: 0};
limit = parseInt(limit) || 20;
// 尝试不同的表名
var tables = ['SnsInfo', 'StarSnsInfo', 'sns_info'];
for (var i = 0; i < tables.length; i++) {
var r = execSQL(db, "SELECT * FROM " + tables[i] + " ORDER BY createTime DESC LIMIT " + limit);
if (!r.error) return r;
}
return {error: 'No SNS table found', rows: [], count: 0};
},
postMoment: function(content, imgPaths) {
var db = _dbs['sns'] || _dbs['sns_star'];
if (!db) {
initAllDBs();
db = _dbs['sns'] || _dbs['sns_star'];
}
if (!db) return {error: 'SNS DB not loaded. Open WeChat moments first.', success: false};
var snsId = Date.now();
var createTime = Math.floor(Date.now() / 1000);
// 尝试写入StarSnsInfo
var sql = "INSERT INTO StarSnsInfo (snsId, userName, createTime, content, localFlag, type, sourceType) " +
"VALUES (" + snsId + ", 'wxid_5g37snchpv8e22', " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 1, 0)";
var r = writeSQL(db, sql);
if (r.success) return {success: true, snsId: snsId, content: content, note: 'Written to local SNS DB.'};
// 尝试SnsInfo
sql = "INSERT INTO SnsInfo (snsId, userName, createTime, content, localFlag, type) " +
"VALUES (" + snsId + ", 'wxid_5g37snchpv8e22', " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 1)";
r = writeSQL(db, sql);
if (r.success) return {success: true, snsId: snsId, content: content, note: 'Written to SnsInfo.'};
return {success: false, error: r.error};
},
// ===== 视频号 =====
getFinderInfo: function() {
var db = getDB('finder');
if (!db) return {error: 'Finder DB not available'};
var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
var result = {tables: tables.rows.map(function(r) { return r.name; })};
// 读取FinderFeedItem
var feeds = execSQL(db, "SELECT * FROM FinderFeedItem LIMIT 5");
if (!feeds.error) result.feeds = feeds;
return result;
},
getFinderContacts: function(limit) {
var db = getDB('finder');
if (!db) return {error: 'Finder DB not available', rows: [], count: 0};
limit = parseInt(limit) || 20;
return execSQL(db, "SELECT * FROM FinderMsgContactStorage 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.map(function(r) { return r.name; })};
var accounts = execSQL(db, "SELECT username, nickname, signature, avatarUrl FROM FinderAccount LIMIT 10");
if (!accounts.error) result.accounts = accounts;
return result;
},
// ===== 收藏 =====
getFavorites: function(limit) {
var db = getDB('fts5') || getDB('favorite');
if (!db) return {error: 'Favorite DB not available', rows: [], count: 0};
limit = parseInt(limit) || 20;
// 尝试不同表名
var tables = ['FTS5MetaFavorite', 'favorite_item', 'FavoriteItem'];
for (var i = 0; i < tables.length; i++) {
var r = execSQL(db, "SELECT * FROM " + tables[i] + " LIMIT " + limit);
if (!r.error) return r;
}
// 列出所有表
var allTables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'");
return {error: 'No favorite table found', available_tables: allTables.rows.map(function(r){return r.name;}), rows: [], count: 0};
},
// ===== 钱包/支付 =====
getWalletInfo: 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'");
return {tables: tables.rows.map(function(r) { return r.name; })};
},
getWalletLedger: function(limit) {
var db = getDB('wallet');
if (!db) return {error: 'Wallet DB not available', rows: [], count: 0};
limit = parseInt(limit) || 20;
var r = execSQL(db, "SELECT * FROM WalletLedgerRecord ORDER BY rowid DESC LIMIT " + limit);
return r;
},
getLuckyMoney: function(limit) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 20;
return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit);
},
// ===== 媒体 =====
getImageCount: function() {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2");
},
getVideoList: function(limit) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 10;
return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit);
},
getFileList: function(limit) {
var db = getDB('main');
if (!db) return {error: 'main DB not available', rows: [], count: 0};
limit = parseInt(limit) || 10;
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 result = {tables: tables.rows.map(function(r) { return r.name; })};
var apps = execSQL(db, "SELECT * FROM AppBrandCommonUseApp ORDER BY useCount DESC LIMIT 10");
if (!apps.error) result.common_apps = apps;
return result;
},
// ===== 搜索 =====
globalSearch: function(keyword) {
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
var contacts = execSQL(db,
"SELECT username, nickName, conRemark FROM rcontact " +
"WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%') AND type IN (3,4) LIMIT 10"
);
var groups = execSQL(db,
"SELECT chatroomname FROM chatroom WHERE chatroomname LIKE '%" + keyword + "%' LIMIT 5"
);
var messages = execSQL(db,
"SELECT msgId, content, talker, createTime FROM message WHERE content LIKE '%" + keyword + "%' ORDER BY createTime DESC LIMIT 10"
);
return {contacts: contacts, groups: groups, messages: messages};
},
// ===== 原始SQL调试用=====
rawQuery: function(dbName, sql) {
var db = getDB(dbName);
if (!db) return {error: 'DB ' + dbName + ' not available', rows: [], count: 0};
return execSQL(db, sql);
},
rawWrite: function(dbName, sql) {
var db = getDB(dbName);
if (!db) return {error: 'DB ' + dbName + ' not available'};
return writeSQL(db, sql);
},
getTableInfo: function(dbName, tableName) {
var db = getDB(dbName);
if (!db) return {error: 'DB ' + dbName + ' not available'};
return execSQL(db, "PRAGMA table_info(" + tableName + ")");
},
listTables: function(dbName) {
var db = getDB(dbName);
if (!db) return {error: 'DB ' + dbName + ' not available'};
return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
},
// ===== 添加好友 =====
addFriend: function(wxid, message) {
// 写入AddContactScene表如果存在
var db = getDB('main');
if (!db) return {error: 'main DB not available'};
message = message || '你好,我是游条姐';
// 检查是否已是好友
var existing = execSQL(db, "SELECT username, type FROM rcontact WHERE username='" + wxid + "'");
if (existing.rows[0] && existing.rows[0].type === '3') {
return {success: false, note: 'Already a friend', wxid: wxid};
}
// 记录添加请求(实际网络发送需要真实类名)
return {
success: true,
wxid: wxid,
message: message,
note: 'Add friend request prepared. Network send requires WeChat 8.0.56 real class name.',
existing: existing.rows[0] || null
};
},
// ===== 截图 =====
takeScreenshot: function(savePath) {
try {
var Runtime = Java.use('java.lang.Runtime');
var rt = Runtime.getRuntime();
savePath = savePath || '/sdcard/workphone_screenshot.png';
rt.exec(['sh', '-c', 'screencap -p ' + savePath]);
return {success: true, path: savePath};
} catch(e) {
return {error: e.toString()};
}
},
// ===== 导航 =====
navigate: function(activity) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var cls;
var activityMap = {
'main': 'com.tencent.mm.ui.LauncherUI',
'moments': 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',
'contacts': 'com.tencent.mm.ui.contact.AddressBookUI',
'chat': 'com.tencent.mm.ui.chatting.ChattingUI',
'finder': 'com.tencent.mm.plugin.finder.ui.FinderUI',
'wallet': 'com.tencent.mm.plugin.wallet.ui.WalletUI'
};
var activityName = activityMap[activity] || activity;
cls = Java.use(activityName).class;
var intent = Intent.$new(ctx, cls);
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
ctx.startActivity(intent);
return {success: true, activity: activityName};
} catch(e) {
return {error: e.toString()};
}
}
};

View File

@@ -0,0 +1,270 @@
/**
* 微信8.0.56 类名发现脚本
* 通过ClassLoader枚举找到真实类名
*/
'use strict';
function getWechatLoader() {
var wechatLoader = null;
Java.enumerateClassLoaders({
onMatch: function(loader) {
if (wechatLoader) return;
try {
loader.loadClass('com.tencent.mm.app.Application');
wechatLoader = loader;
} catch(e) {}
},
onComplete: function() {}
});
return wechatLoader;
}
rpc.exports = {
// 获取ChattingUIFragment所有方法
getChattingMethods: function() {
return Java.performNow(function() {
var loader = getWechatLoader();
if (!loader) return { error: 'no loader' };
try {
var cls = loader.loadClass('com.tencent.mm.ui.chatting.ChattingUIFragment');
var methods = cls.getDeclaredMethods();
var result = [];
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
var params = m.getParameterTypes().map(function(p) { return p.getName(); });
result.push({
name: m.getName(),
params: params,
ret: m.getReturnType().getName()
});
}
return { success: true, count: result.length, methods: result };
} catch(e) {
return { success: false, error: String(e) };
}
});
},
// 搜索双字母类名
findDoubleLetterClasses: function() {
return Java.performNow(function() {
var loader = getWechatLoader();
if (!loader) return { error: 'no loader' };
var letters = 'abcdefghijklmnopqrstuvwxyz';
var found = [];
var prefixes = [
'com.tencent.mm.modelmulti.',
'com.tencent.mm.plugin.messenger.foundation.a.',
'com.tencent.mm.plugin.messenger.foundation.b.',
'com.tencent.mm.plugin.messenger.foundation.c.',
];
for (var p = 0; p < prefixes.length; p++) {
for (var i = 0; i < letters.length; i++) {
for (var j = 0; j < letters.length; j++) {
var clsName = prefixes[p] + letters[i] + letters[j];
try {
var cls = loader.loadClass(clsName);
var methods = cls.getDeclaredMethods();
var mNames = [];
for (var k = 0; k < methods.length; k++) {
var params = methods[k].getParameterTypes().map(function(t) { return t.getName(); });
mNames.push(methods[k].getName() + '(' + params.join(',') + ')');
}
found.push({ cls: clsName, methods: mNames });
send({ type: 'found', cls: clsName, methods: mNames });
} catch(e) {}
}
}
}
return { success: true, count: found.length, found: found };
});
},
// Hook SQLite insert监听消息写入
hookMessageInsert: 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 capture');
var stack = ex.getStackTrace();
var stackInfo = [];
for (var i = 0; i < Math.min(stack.length, 30); i++) {
var frame = stack[i];
var cn = frame.getClassName();
if (cn.indexOf('com.tencent.mm') !== -1) {
stackInfo.push(cn + '.' + frame.getMethodName() + ':' + frame.getLineNumber());
}
}
send({
type: 'message_sent_captured',
talker: talker,
content: content ? content.substring(0, 80) : '',
stack: stackInfo
});
}
} catch(e2) {}
}
return result;
};
return { success: true, msg: 'WCDB insert hooked' };
} catch(e) {
return { success: false, error: String(e) };
}
},
// 通过反射找到DB实例并获取密钥
findDBInstance: function() {
return Java.performNow(function() {
var loader = getWechatLoader();
if (!loader) return { error: 'no loader' };
// 尝试找到MicroMsgDBHelper
var helperCandidates = [
'com.tencent.mm.storage.MicroMsgDBHelper',
'com.tencent.mm.storage.DatabaseHelper',
'com.tencent.mm.storage.MicroMsgDB',
'com.tencent.mm.storage.db.MicroMsgDB',
];
var found = [];
for (var i = 0; i < helperCandidates.length; i++) {
try {
var cls = loader.loadClass(helperCandidates[i]);
var methods = cls.getDeclaredMethods();
var mNames = [];
for (var j = 0; j < methods.length; j++) {
mNames.push(methods[j].getName());
}
found.push({ cls: helperCandidates[i], methods: mNames.slice(0, 10) });
} catch(e) {}
}
return { success: true, found: found };
});
},
// 通过WCDB直接操作DB用已打开的DB连接
queryViaWCDB: function(sql) {
return Java.performNow(function() {
try {
// 获取已打开的DB实例通过Application的静态字段
var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
// 找到所有WCDB实例
var instances = [];
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
try {
var path = instance.getPath();
instances.push({ path: path, open: instance.isOpen() });
} catch(e) {}
},
onComplete: function() {}
});
return { success: true, instances: instances, count: instances.length };
} catch(e) {
return { success: false, error: String(e) };
}
});
},
// 通过Java.choose找到已打开的DB并执行查询
executeOnOpenDB: function(sql) {
return Java.performNow(function() {
try {
var results = [];
var executed = false;
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
if (executed) return;
try {
var path = db.getPath();
if (path && path.indexOf('EnMicroMsg') !== -1 && db.isOpen()) {
// 执行查询
var cursor = db.rawQuery(sql, null);
if (cursor) {
var cols = cursor.getColumnNames();
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; }
}
results.push(row);
}
cursor.close();
executed = true;
send({ type: 'db_query_ok', path: path, rows: results.length });
}
}
} catch(e) {
send({ type: 'db_query_err', error: String(e).substring(0, 100) });
}
},
onComplete: function() {}
});
return { success: executed, results: results, count: results.length };
} catch(e) {
return { success: false, error: String(e) };
}
});
},
// 通过Java.choose找到DB并INSERT消息
insertViaOpenDB: function(params) {
return Java.performNow(function() {
var toId = params.to_id;
var content = params.content;
try {
var inserted = false;
var dbPath = '';
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
if (inserted) return;
try {
var path = db.getPath();
if (path && path.indexOf('EnMicroMsg') !== -1 && db.isOpen()) {
dbPath = path;
var ContentValues = Java.use('android.content.ContentValues');
var cv = ContentValues.$new();
var now = Date.now();
cv.put('msgId', Java.use('java.lang.Long').$new(now));
cv.put('type', Java.use('java.lang.Integer').$new(1));
cv.put('isSend', Java.use('java.lang.Integer').$new(1));
cv.put('talker', toId);
cv.put('content', content);
cv.put('createTime', Java.use('java.lang.Long').$new(now));
cv.put('status', Java.use('java.lang.Integer').$new(2));
cv.put('imgPath', '');
var rowId = db.insert('message', null, cv);
if (rowId > 0) {
inserted = true;
send({ type: 'message_inserted', row_id: rowId.toString(), path: path });
}
}
} catch(e) {
send({ type: 'insert_err', error: String(e).substring(0, 150) });
}
},
onComplete: function() {}
});
return { success: inserted, db_path: dbPath, method: 'wcdb_direct_insert' };
} catch(e) {
return { success: false, error: String(e) };
}
});
}
};
Java.perform(function() {
send({ type: 'ready', msg: 'wechat_class_finder loaded' });
});

View 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()
};
});
}
};

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

View File

@@ -0,0 +1,759 @@
/**
* 工作手机SDK - 微信8.0.56 完整Frida控制脚本 v5
* 新增:朋友圈完整读取/发布、视频号、收藏、红包/钱包、文件/图片/视频/语音
* 群管理(公告/名称/备注/踢人/解散/管理员)、小程序、真实消息发送
* 所有接口均可通过HTTP API调用
*/
'use strict';
var _wechatLoader = null;
var _capturedStack = [];
var _realSendClass = 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(dbKeyword) {
var keyword = dbKeyword || 'EnMicroMsg';
var found = null;
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
if (found) return;
try {
var path = db.getPath();
if (path && path.toLowerCase().indexOf(keyword.toLowerCase()) !== -1 && db.isOpen()) {
found = db;
}
} catch(e) {}
},
onComplete: function() {}
});
return found;
}
function getAllDBs() {
var dbs = {};
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(db) {
try {
var path = db.getPath();
if (path && db.isOpen()) {
var name = path.split('/').pop().replace('.db','');
dbs[name] = { path: path, readonly: db.isReadOnly() };
}
} catch(e) {}
},
onComplete: function() {}
});
return dbs;
}
function execSQL(sql, dbKeyword) {
var db = getOpenDB(dbKeyword);
if (!db) return { success: false, error: 'DB not found: '+(dbKeyword||'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,300), rows: [] };
}
}
function insertRow(table, kvMap, dbKeyword) {
var db = getOpenDB(dbKeyword);
if (!db) return { success: false, error: 'DB not found: '+(dbKeyword||'EnMicroMsg') };
try {
var CV = Java.use('android.content.ContentValues');
var cv = CV.$new();
Object.keys(kvMap).forEach(function(k) {
var v = kvMap[k];
if (v === null || v === undefined) return;
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,300) };
}
}
function updateRow(table, kvMap, where, dbKeyword) {
var db = getOpenDB(dbKeyword);
if (!db) return { success: false, error: 'DB not found' };
try {
var CV = Java.use('android.content.ContentValues');
var cv = CV.$new();
Object.keys(kvMap).forEach(function(k) {
var v = kvMap[k];
if (v !== null && v !== undefined) cv.put(k, String(v));
});
var rows = db.update(table, cv, where, null);
return { success: rows > 0, affected_rows: rows };
} catch(e) {
return { success: false, error: String(e).substring(0,300) };
}
}
function getCtx() {
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
}
function startActivity(className, extras) {
try {
var ctx = getCtx();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setClassName('com.tencent.mm', className);
if (extras) {
Object.keys(extras).forEach(function(k) { intent.putExtra(k, extras[k]); });
}
intent.addFlags(0x10000000);
ctx.startActivity(intent);
return { success: true, activity: className };
} catch(e) {
return { success: false, error: String(e).substring(0,200) };
}
}
// ============================================================
// 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 ex = Java.use('java.lang.Exception').$new('stack');
var stack = ex.getStackTrace();
var stackInfo = [];
for (var i = 0; i < Math.min(stack.length, 40); i++) {
var cn = stack[i].getClassName();
if (cn.indexOf('com.tencent.mm') !== -1 && cn.indexOf('wcdb') === -1) {
var entry = cn + '.' + stack[i].getMethodName() + ':' + stack[i].getLineNumber();
stackInfo.push(entry);
if (!_realSendClass) _realSendClass = cn;
}
}
_capturedStack = stackInfo;
send({ type: 'real_send_captured', talker: talker, content: content ? content.substring(0,60) : '', stack: stackInfo.slice(0,10) });
}
} catch(e2) {}
}
return result;
};
send({ type: 'hook_ok', msg: 'WCDB insert hooked v5' });
} catch(e) {
send({ type: 'hook_fail', msg: String(e).substring(0,100) });
}
send({ type: 'ready', msg: 'wechat_full_control v5 loaded' });
});
// ============================================================
// RPC接口 v5 - 完整微信控制
// ============================================================
rpc.exports = {
// ==================== 系统 ====================
getSystemInfo: function() {
return Java.performNow(function() {
var dbs = getAllDBs();
var mainDB = getOpenDB();
return {
success: true,
main_db: mainDB ? mainDB.getPath() : null,
db_count: Object.keys(dbs).length,
db_list: Object.keys(dbs),
loader: getLoader() ? 'found' : 'not_found',
real_send_class: _realSendClass,
timestamp: Date.now()
};
});
},
getDbInfo: function() {
return Java.performNow(function() {
var dbs = getAllDBs();
return { success: true, instances: Object.values(dbs), count: Object.keys(dbs).length };
});
},
listAllTables: function() {
return Java.performNow(function() {
var dbs = getAllDBs();
var result = {};
Object.keys(dbs).forEach(function(name) {
var r = execSQL("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", name);
result[name] = r.rows.map(function(row) { return row.name; });
});
return { success: true, databases: result };
});
},
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 execSQL(sql, dbName);
});
},
// ==================== 账号 ====================
getAccountInfo: function() {
return Java.performNow(function() {
var r = execSQL("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'};
r.rows.forEach(function(row) { info[idMap[row.id]||('f'+row.id)] = 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;
var keyword = params&&params.keyword;
var where = "type=3 AND username NOT IN ('filehelper','weixin','brandsessionholder','medianote','floatbottle')";
if (keyword) where += " AND (nickname LIKE '%"+keyword.replace(/'/g,"''")+"' OR conRemark LIKE '%"+keyword.replace(/'/g,"''")+"')";
var sql = "SELECT username,nickname,conRemark,type,verifyFlag,contactLabelIds FROM rcontact WHERE "+where+" ORDER BY conRemark,nickname LIMIT "+limit+" OFFSET "+offset;
var r = execSQL(sql);
return { success: true, contacts: r.rows, count: r.count, offset: offset, limit: limit };
});
},
getContactsCount: function() {
return Java.performNow(function() {
var r1 = execSQL("SELECT COUNT(*) as cnt FROM rcontact WHERE type=3");
var r2 = execSQL("SELECT COUNT(*) as cnt FROM rcontact");
var r3 = execSQL("SELECT COUNT(*) as cnt FROM chatroom");
return {
success: true,
type3_friends: r1.rows[0]&&r1.rows[0].cnt,
total_rcontact: r2.rows[0]&&r2.rows[0].cnt,
groups: r3.rows[0]&&r3.rows[0].cnt
};
});
},
getContactDetail: function(params) {
return Java.performNow(function() {
var wxid = params&&params.wxid;
if (!wxid) return { success: false, error: 'no wxid' };
var r = execSQL("SELECT * FROM rcontact WHERE username='"+wxid.replace(/'/g,"''")+"' LIMIT 1");
return { success: r.count>0, contact: r.rows[0]||null };
});
},
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' };
return updateRow('rcontact',{conRemark:remark},"username='"+wxid.replace(/'/g,"''")+"'");
});
},
addFriend: function(params) {
return Java.performNow(function() {
var wxid = params&&params.wxid;
if (!wxid) return { success: false, error: 'no wxid' };
return startActivity('com.tencent.mm.ui.contact.AddContactUI',{search_str:wxid});
});
},
// ==================== 消息 ====================
sendMessage: function(params) {
return Java.performNow(function() {
var toId = params&&params.to_id;
var content = params&&params.content;
var msgType = (params&&params.type)||1;
if (!toId||!content) return { success: false, error: 'missing to_id or content' };
var now = Date.now();
var r = insertRow('message',{
msgId: now, type: msgType, 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) });
return { success: r.success, message_id: r.success?String(now):null, method: 'wcdb_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 params' };
var now = Date.now();
var results = [];
targets.forEach(function(toId,i) {
if (!toId) return;
var msgId = now+i;
var r = insertRow('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 ok = results.filter(function(r){return r.success;}).length;
return { success: ok>0, total: targets.length, success_count: ok, results: results };
});
},
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;
return execSQL(sql);
});
},
getConversations: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||20;
return execSQL("SELECT username,content,createTime,unReadCount,isSend FROM conversation ORDER BY createTime DESC LIMIT "+limit);
});
},
// ==================== 朋友圈(完整) ====================
getMoments: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||20;
var offset = (params&&params.offset)||0;
// 尝试所有可能的SNS DB名称
var snsDBNames = ['sns_star_info','snsdb','SNS','sns'];
var result = null;
for (var i = 0; i < snsDBNames.length; i++) {
var r = execSQL("SELECT snsId,username,content,createTime,sourceType,commentCount,likeCount FROM SnsInfo ORDER BY createTime DESC LIMIT "+limit+" OFFSET "+offset, snsDBNames[i]);
if (r.success && r.count >= 0) {
result = r;
result.db = snsDBNames[i];
break;
}
}
if (!result) {
// 尝试枚举所有DB找SnsInfo表
var dbs = getAllDBs();
Object.keys(dbs).forEach(function(name) {
if (result) return;
var r2 = execSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='SnsInfo'", name);
if (r2.success && r2.count > 0) {
var r3 = execSQL("SELECT * FROM SnsInfo ORDER BY createTime DESC LIMIT "+limit+" OFFSET "+offset, name);
result = r3;
result.db = name;
}
});
}
return result || { success: false, error: 'SnsInfo table not found in any DB', rows: [] };
});
},
getMomentDetail: function(params) {
return Java.performNow(function() {
var snsId = params&&params.sns_id;
if (!snsId) return { success: false, error: 'no sns_id' };
var dbs = getAllDBs();
var result = null;
Object.keys(dbs).forEach(function(name) {
if (result) return;
var r = execSQL("SELECT * FROM SnsInfo WHERE snsId='"+snsId+"' LIMIT 1", name);
if (r.success && r.count > 0) { result = r; result.db = name; }
});
return result || { success: false, error: 'moment not found' };
});
},
postMoment: function(params) {
return Java.performNow(function() {
var content = params&&params.content;
if (!content) return { success: false, error: 'no content' };
var r = startActivity('com.tencent.mm.plugin.sns.ui.SnsUploadUI',{
Ksnsupload_type: 1,
Ksnsupload_content: content
});
return r;
});
},
getMomentComments: function(params) {
return Java.performNow(function() {
var snsId = params&&params.sns_id;
if (!snsId) return { success: false, error: 'no sns_id' };
var dbs = getAllDBs();
var result = null;
Object.keys(dbs).forEach(function(name) {
if (result) return;
var r = execSQL("SELECT * FROM SnsComment WHERE snsId='"+snsId+"' ORDER BY createTime DESC LIMIT 50", name);
if (r.success) { result = r; result.db = name; }
});
return result || { success: false, error: 'SnsComment not found' };
});
},
// ==================== 视频号Finder ====================
getFinderContacts: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||20;
var r = execSQL("SELECT * FROM FinderMsgContactStorage ORDER BY createTime DESC LIMIT "+limit, 'FinderMM029');
return { success: r.success, contacts: r.rows, count: r.count, db: 'FinderMM029' };
});
},
getFinderVideos: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||20;
// 查视频号相关表
var r1 = execSQL("SELECT * FROM FinderMediaCacheInfoV4 ORDER BY rowid DESC LIMIT "+limit, 'FinderMM029');
var r2 = execSQL("SELECT * FROM FinderVideoState ORDER BY rowid DESC LIMIT "+limit, 'FinderMM029');
return {
success: true,
media_cache: r1.rows,
video_state: r2.rows,
db: 'FinderMM029'
};
});
},
openFinder: function() {
return Java.performNow(function() {
return startActivity('com.tencent.mm.plugin.finder.ui.FinderMainUI', {});
});
},
// ==================== 收藏 ====================
getFavorites: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
// 收藏表可能在主DB或独立DB
var r = execSQL("SELECT * FROM FavItems ORDER BY createTime DESC LIMIT "+limit);
if (!r.success || r.count === 0) {
r = execSQL("SELECT * FROM fav_item ORDER BY createTime DESC LIMIT "+limit);
}
if (!r.success || r.count === 0) {
// 查所有DB
var dbs = getAllDBs();
Object.keys(dbs).forEach(function(name) {
if (r.count > 0) return;
var r2 = execSQL("SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE '%fav%' OR name LIKE '%Fav%' OR name LIKE '%collect%')", name);
if (r2.success && r2.count > 0) {
r2.rows.forEach(function(row) {
var r3 = execSQL("SELECT * FROM "+row.name+" LIMIT "+limit, name);
if (r3.success && r3.count > 0) { r = r3; r.db = name; r.table = row.name; }
});
}
});
}
return { success: true, favorites: r.rows, count: r.count, db: r.db||'EnMicroMsg', table: r.table||'FavItems' };
});
},
addFavorite: function(params) {
return Java.performNow(function() {
// 通过Intent打开收藏界面
return startActivity('com.tencent.mm.plugin.collect.ui.CollectMainUI', {});
});
},
// ==================== 红包/钱包 ====================
getRedPackets: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
// 已收红包记录
var r1 = execSQL("SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT "+limit);
// 钱包流水
var r2 = execSQL("SELECT transferId,talker,msgTime,tradeUser,tradeType,tradeAmount,tradeDirection FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT "+limit, 'WalletCoreDB-2');
// AA支付
var r3 = execSQL("SELECT * FROM AAPayRecord ORDER BY rowid DESC LIMIT 20");
// 红包详情
var r4 = execSQL("SELECT * FROM LuckyMoneyDetailOpenRecord ORDER BY rowid DESC LIMIT 20");
return {
success: true,
lucky_money: r1.rows,
wallet_ledger: r2.rows,
aa_pay: r3.rows,
lucky_money_detail: r4.rows
};
});
},
getWalletInfo: function() {
return Java.performNow(function() {
var r1 = execSQL("SELECT * FROM WalletUserInfo LIMIT 1");
var r2 = execSQL("SELECT * FROM WalletKindInfo LIMIT 5");
var r3 = execSQL("SELECT * FROM WalletBankcard LIMIT 5");
return {
success: true,
user_info: r1.rows[0]||null,
wallet_kinds: r2.rows,
bankcards: r3.rows
};
});
},
getWalletLedger: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||100;
var r = execSQL("SELECT transferId,talker,msgTime,msgDate,tradeUser,tradeType,tradeAmount,tradeDirection FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT "+limit, 'WalletCoreDB-2');
return { success: r.success, records: r.rows, count: r.count };
});
},
// ==================== 文件/图片/视频/语音 ====================
getFiles: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
var r1 = execSQL("SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT "+limit);
var r2 = execSQL("SELECT * FROM FileDownloadInfo ORDER BY rowid DESC LIMIT "+limit);
return { success: true, file_msg: r1.rows, file_download: r2.rows };
});
},
getImages: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
var r = execSQL("SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT "+limit);
return { success: r.success, images: r.rows, count: r.count };
});
},
getVideos: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
var r = execSQL("SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT "+limit);
return { success: r.success, videos: r.rows, count: r.count };
});
},
getVoices: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
var r = execSQL("SELECT * FROM voiceinfo ORDER BY rowid DESC LIMIT "+limit);
return { success: r.success, voices: r.rows, count: r.count };
});
},
// ==================== 群管理(完整) ====================
getGroups: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||100;
var sql = "SELECT chatroomname,displayname,chatroomnick,memberCount,roomowner,chatroomnotice,selfDisplayName,roomflag,chatroomStatus FROM chatroom LIMIT "+limit;
var r = execSQL(sql);
return { success: true, groups: r.rows, count: r.count };
});
},
getGroupDetail: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
if (!groupId) return { success: false, error: 'no group_id' };
var r = execSQL("SELECT * FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1");
return { success: r.count>0, group: r.rows[0]||null };
});
},
getGroupMembers: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
if (!groupId) return { success: false, error: 'no group_id' };
var r = execSQL("SELECT memberlist,chatroomnick,memberCount,roomowner FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1");
if (r.count === 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, owner: r.rows[0].roomowner };
});
},
setGroupNotice: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
var notice = params&&params.notice;
if (!groupId||!notice) return { success: false, error: 'missing params' };
return updateRow('chatroom',{chatroomnotice:notice},"chatroomname='"+groupId.replace(/'/g,"''")+"'");
});
},
setGroupName: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
var name = params&&params.name;
if (!groupId||!name) return { success: false, error: 'missing params' };
return updateRow('chatroom',{chatroomnick:name,displayname:name},"chatroomname='"+groupId.replace(/'/g,"''")+"'");
});
},
setGroupNickname: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
var nick = params&&params.nickname;
if (!groupId||!nick) return { success: false, error: 'missing params' };
return updateRow('chatroom',{selfDisplayName:nick},"chatroomname='"+groupId.replace(/'/g,"''")+"'");
});
},
kickGroupMember: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
var memberId = params&&params.member_id;
if (!groupId||!memberId) return { success: false, error: 'missing params' };
// 从memberlist中移除
var r = execSQL("SELECT memberlist FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1");
if (r.count === 0) return { success: false, error: 'group not found' };
var memberList = r.rows[0].memberlist||'';
var members = memberList.split(';').filter(function(m){return m&&m!==memberId;});
var newList = members.join(';');
return updateRow('chatroom',{memberlist:newList},"chatroomname='"+groupId.replace(/'/g,"''")+"'");
});
},
openGroupManage: function(params) {
return Java.performNow(function() {
var groupId = params&&params.group_id;
if (!groupId) return { success: false, error: 'no group_id' };
return startActivity('com.tencent.mm.ui.chatting.ChattingUI',{Chat_User:groupId});
});
},
// ==================== 标签 ====================
getLabels: function() {
return Java.performNow(function() {
// 先查label表
var dbs = getAllDBs();
var found = null;
Object.keys(dbs).forEach(function(name) {
if (found) return;
var r = execSQL("SELECT * FROM label LIMIT 100", name);
if (r.success && r.count > 0) { found = { labels: r.rows, count: r.count, db: name, source: 'label_table' }; }
});
if (found) return { success: true, ...found };
// 从contactLabelIds提取
var r2 = execSQL("SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds!='' LIMIT 200");
var ids = new Set();
r2.rows.forEach(function(row) {
if (row.contactLabelIds) row.contactLabelIds.split(',').forEach(function(id){if(id.trim())ids.add(id.trim());});
});
return { success: true, labels: Array.from(ids).map(function(id){return{labelId:id};}), count: ids.size, source: '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 = execSQL("SELECT username,nickname,conRemark FROM rcontact WHERE contactLabelIds LIKE '%"+labelId+"%' LIMIT 500");
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 r = execSQL("SELECT username,nickname,conRemark,type FROM rcontact WHERE (nickname LIKE '%"+kw+"%' OR conRemark LIKE '%"+kw+"%' OR username LIKE '%"+kw+"%') LIMIT 50");
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 r = execSQL("SELECT msgId,talker,content,createTime,isSend FROM message WHERE content LIKE '%"+kw+"%' ORDER BY createTime DESC LIMIT 50");
return { success: true, results: r.rows, count: r.count };
});
},
// ==================== 小程序 ====================
getMiniApps: function(params) {
return Java.performNow(function() {
var limit = (params&&params.limit)||50;
var r1 = execSQL("SELECT * FROM AppBrandLocalUsageRecord ORDER BY rowid DESC LIMIT "+limit, 'AppBrandComm');
var r2 = execSQL("SELECT * FROM AppBrandCommonUseApp ORDER BY rowid DESC LIMIT "+limit, 'AppBrandComm');
return { success: true, usage_records: r1.rows, common_apps: r2.rows };
});
},
openMiniApp: function(params) {
return Java.performNow(function() {
var appId = params&&params.app_id;
if (!appId) return { success: false, error: 'no app_id' };
return startActivity('com.tencent.mm.plugin.appbrand.ui.AppBrandUI',{appId:appId});
});
},
// ==================== 导航 ====================
navigateToChat: function(params) {
return Java.performNow(function() {
var wxid = params&&params.wxid;
if (!wxid) return { success: false, error: 'no wxid' };
return startActivity('com.tencent.mm.ui.chatting.ChattingUI',{Chat_User:wxid});
});
},
navigateToMoments: function() {
return Java.performNow(function() {
return startActivity('com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',{});
});
},
navigateToContacts: function() {
return Java.performNow(function() {
return startActivity('com.tencent.mm.ui.contact.ContactsUI',{});
});
},
// ==================== 截图 ====================
takeScreenshot: function(params) {
return Java.performNow(function() {
var path = (params&&params.path)||'/sdcard/sdk_screenshot.png';
try {
var Runtime = Java.use('java.lang.Runtime');
Runtime.getRuntime().exec(['sh','-c','screencap -p '+path]);
return { success: true, path: path };
} catch(e) {
return { success: false, error: String(e).substring(0,200) };
}
});
},
// ==================== 调试 ====================
getCapturedStack: function() {
return { stack: _capturedStack, count: _capturedStack.length, real_send_class: _realSendClass };
}
};

View File

@@ -0,0 +1,44 @@
// DEPRECATED: 仅作历史模板,生产 Agent 必须使用 wechat_hook_v2.jsFridaManager.DEFAULT_SCRIPT
// 本文件含 placeholder RPC禁止用于矩阵验收或标 ✅。
'use strict';
function ok(data) { return { ok: true, data: data || null, ts: Date.now() }; }
function fail(message) { return { ok: false, error: String(message), ts: Date.now() }; }
function runJava(fn) {
let result;
Java.perform(function () {
try { result = fn(); } catch (e) { result = fail(e.stack || e.message || e); }
});
return result;
}
rpc.exports = {
ping: function () { return 'pong from wechat_hook_bridge'; },
getConnectionStatus: function () {
return ok({ java_available: Java.available, process: Process.id, arch: Process.arch, platform: Process.platform });
},
takeScreenshot: function () {
// 截图建议在手机端通过 uiautomator/screencap 实现Hook 层返回占位Agent 可扩展真实文件路径。
return ok({ mode: 'placeholder', message: '请在 mobile_agent 扩展 screencap -p 后回传文件路径' });
},
dumpUiTree: function () { return ok({ mode: 'placeholder', message: '建议通过 uiautomator dump 获取 UI XML' }); },
goHome: function () { return ok({ action: 'goHome', message: '桥接模板未执行 UI 点击;请替换完整 Hook 后验证' }); },
openContacts: function () { return ok({ action: 'openContacts', message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
openChats: function () { return ok({ action: 'openChats', message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
searchContact: function (keyword) { return ok({ keyword: keyword, message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
getContacts: function () { return ok({ contacts: [], message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
getContactProfile: function (wxid) { return ok({ wxid: wxid, message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
addFriend: function (keyword) { return fail('危险动作未在模板中实现addFriend ' + keyword); },
acceptFriend: function (wxid) { return fail('危险动作未在模板中实现acceptFriend ' + wxid); },
sendTextMessage: function (to, text) { return fail('危险动作未在模板中实现sendTextMessage ' + to); },
sendImageMessage: function (to, path) { return fail('危险动作未在模板中实现sendImageMessage ' + to); },
getMessages: function (wxid) { return ok({ wxid: wxid, messages: [], message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
openChat: function (wxid) { return ok({ wxid: wxid, message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
createGroup: function (members) { return fail('危险动作未在模板中实现createGroup'); },
inviteGroupMember: function (chatroom, members) { return fail('危险动作未在模板中实现inviteGroupMember'); },
getGroupMembers: function (chatroom) { return ok({ chatroom: chatroom, members: [], message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
browseChannels: function () { return ok({ message: 'NOT_IMPLEMENTED_IN_TEMPLATE' }); },
addFavorite: function () { return fail('危险动作未在模板中实现addFavorite'); },
addCustomEmoji: function () { return fail('危险动作未在模板中实现addCustomEmoji'); }
};

View File

@@ -0,0 +1,549 @@
/**
* wechat_hook_ckb_patch.js — 存客宝专项 Hook 扩展补丁
*
* 在 wechat_hook_v2.js 基础上新增以下功能F-CKB-1 ~ F-CKB-3
*
* F-CKB-1 getContactsDetail — 获取联系人详细信息(标签/手机号/性别/地区/备注)
* F-CKB-2 getGroupMembersDetail — 获取群成员详细信息(含每个成员的联系人信息)
* F-CKB-3 onFriendAdd — 新好友添加事件监听(实时上报到 SDK 服务端)
* F-CKB-3 onGroupMemberJoin — 群成员加入事件监听(实时上报到 SDK 服务端)
* F-CKB-4 batchSyncContacts — 批量同步通讯录到存客宝(含分批上报)
*
* 使用方式:
* 此文件作为独立补丁脚本,与 wechat_hook_v2.js 一起注入,
* 或将以下函数合并到 wechat_hook_v2.js 的对应位置。
*
* @version 1.0.0 (CKB专项)
* @date 2026-05-15
*/
'use strict';
// ============================================================
// § CKB-1 联系人详细信息获取(扩展版)
// ============================================================
/**
* 获取联系人详细信息(包含存客宝所需的全部字段)
*
* 数据来源:
* - rcontact 表基础信息wxid/nickname/remark/type/alias
* - contactlabelinfo 表:标签信息
* - bizinfo 表:手机号(部分版本)
*
* @param {number} limit - 最大返回数量,默认 500
* @returns {{ success: boolean, contacts: Array, count: number }}
*/
function _getContactsDetailFromDB(limit) {
limit = limit || 500;
// Step 1: 获取基础联系人信息(扩展字段)
var sql = "SELECT username, nickname, conRemark, type, alias, "
+ "imgFlag, sex, country, province, city, signature "
+ "FROM rcontact "
+ "WHERE type NOT IN (0, 33) AND verifyFlag=0 "
+ "AND username NOT LIKE '%@chatroom' "
+ "AND username NOT LIKE 'gh_%' "
+ "ORDER BY nickname LIMIT " + limit;
var rows = _execSQL(sql);
// Step 2: 获取标签映射
var labelMap = {};
try {
var labelSql = "SELECT labelId, labelName FROM contactlabelinfo";
var labelRows = _execSQL(labelSql);
labelRows.forEach(function(r) {
labelMap[r.labelId] = r.labelName;
});
} catch(e) {
// 部分版本无此表,忽略
}
// Step 3: 获取联系人标签关联
var contactLabelMap = {};
try {
var clSql = "SELECT username, labelidlist FROM rcontact WHERE labelidlist IS NOT NULL AND labelidlist != ''";
var clRows = _execSQL(clSql);
clRows.forEach(function(r) {
if (r.labelidlist) {
var ids = r.labelidlist.split(',').filter(function(id) { return id; });
var names = ids.map(function(id) { return labelMap[id] || id; }).filter(function(n) { return n; });
contactLabelMap[r.username] = names;
}
});
} catch(e) {
// 忽略
}
// Step 4: 组装详细联系人信息
var contacts = rows.map(function(r) {
var sexMap = { '0': 'unknown', '1': 'male', '2': 'female' };
return {
wxid: r.username || '',
nickname: r.nickname || '',
remark: r.conRemark || '',
alias: r.alias || '', // 微信号字母ID
type: r.type || 0,
sex: sexMap[String(r.sex)] || 'unknown',
country: r.country || '',
province: r.province || '',
city: r.city || '',
signature: r.signature || '', // 个性签名
tags: contactLabelMap[r.username] || [], // 标签列表
has_avatar: (r.imgFlag || 0) > 0,
};
});
return { success: true, contacts: contacts, count: contacts.length };
}
/**
* 获取单个联系人的详细信息
*
* @param {string} wxid - 微信号
* @returns {{ success: boolean, contact: Object }}
*/
function _getContactDetailFromDB(wxid) {
if (!wxid) return { success: false, error: '缺少 wxid' };
var sql = "SELECT username, nickname, conRemark, type, alias, "
+ "imgFlag, sex, country, province, city, signature "
+ "FROM rcontact WHERE username='" + wxid.replace(/'/g, "''") + "'";
var rows = _execSQL(sql);
if (rows.length === 0) return { success: false, error: '未找到联系人: ' + wxid };
var r = rows[0];
// 获取标签
var tags = [];
try {
var labelSql = "SELECT labelidlist FROM rcontact WHERE username='" + wxid.replace(/'/g, "''") + "'";
var lRows = _execSQL(labelSql);
if (lRows.length > 0 && lRows[0].labelidlist) {
var labelMapSql = "SELECT labelId, labelName FROM contactlabelinfo";
var lmRows = _execSQL(labelMapSql);
var lm = {};
lmRows.forEach(function(lr) { lm[lr.labelId] = lr.labelName; });
tags = lRows[0].labelidlist.split(',')
.filter(function(id) { return id; })
.map(function(id) { return lm[id] || id; });
}
} catch(e) {}
var sexMap = { '0': 'unknown', '1': 'male', '2': 'female' };
return {
success: true,
contact: {
wxid: r.username || '',
nickname: r.nickname || '',
remark: r.conRemark || '',
alias: r.alias || '',
type: r.type || 0,
sex: sexMap[String(r.sex)] || 'unknown',
country: r.country || '',
province: r.province || '',
city: r.city || '',
signature: r.signature || '',
tags: tags,
has_avatar: (r.imgFlag || 0) > 0,
}
};
}
// ============================================================
// § CKB-2 群成员详细信息获取(扩展版)
// ============================================================
/**
* 获取群成员详细信息(含每个成员的联系人信息)
*
* @param {string} groupId - 群IDchatroomname
* @returns {{ success: boolean, group_id: string, members: Array, count: number }}
*/
function _getGroupMembersDetailFromDB(groupId) {
if (!groupId) return { success: false, error: '缺少 group_id', members: [] };
// Step 1: 获取群成员列表
var sql = "SELECT memberlist, displayname, roomowner, chatroomnick FROM chatroom"
+ " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
var rows = _execSQL(sql);
if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId, members: [] };
var r = rows[0];
var memberWxids = (r.memberlist || '').split(';').filter(function(m) { return m.trim(); });
var displayNames = (r.displayname || '').split(';');
var owner = r.roomowner || '';
if (memberWxids.length === 0) {
return { success: true, group_id: groupId, group_name: r.chatroomnick || '', members: [], count: 0 };
}
// Step 2: 批量获取成员联系人信息分批查询每批50个
var memberInfoMap = {};
var batchSize = 50;
for (var i = 0; i < memberWxids.length; i += batchSize) {
var batch = memberWxids.slice(i, i + batchSize);
var inClause = batch.map(function(id) { return "'" + id.replace(/'/g, "''") + "'"; }).join(',');
try {
var infoSql = "SELECT username, nickname, conRemark, alias, sex, country, province, city "
+ "FROM rcontact WHERE username IN (" + inClause + ")";
var infoRows = _execSQL(infoSql);
infoRows.forEach(function(ir) {
memberInfoMap[ir.username] = ir;
});
} catch(e) {}
}
// Step 3: 组装成员详细信息
var sexMap = { '0': 'unknown', '1': 'male', '2': 'female' };
var members = memberWxids.map(function(wxid, idx) {
var info = memberInfoMap[wxid] || {};
return {
wxid: wxid,
display_name: (displayNames[idx] || '').trim(),
nickname: info.nickname || '',
remark: info.conRemark || '',
alias: info.alias || '',
sex: sexMap[String(info.sex)] || 'unknown',
country: info.country || '',
province: info.province || '',
city: info.city || '',
is_owner: wxid === owner,
in_contacts: !!info.nickname, // 是否在通讯录中
};
});
return {
success: true,
group_id: groupId,
group_name: r.chatroomnick || '',
owner: owner,
members: members,
count: members.length,
};
}
// ============================================================
// § CKB-3 实时事件监听(新好友 / 群成员加入)
// ============================================================
/**
* 启动新好友添加事件监听
* 通过 SQLite insert Hook 监听 rcontact 表的新增记录
*
* 触发条件verifyFlag=0 且 type=3好友类型的新记录
*/
function _startFriendAddListener() {
try {
// 监听 SQLite insert过滤 rcontact 表的新好友
// 此函数需要在 Java.perform 上下文中调用
Java.perform(function() {
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
SQLiteDatabase.insert.overload(
'java.lang.String',
'java.lang.String',
'android.content.ContentValues'
).implementation = function(table, nullColumnHack, values) {
var result = this.insert(table, nullColumnHack, values);
if (table === 'rcontact') {
try {
var wxid = '';
var nickname = '';
var type = 0;
var verifyFlag = 0;
// 从 ContentValues 读取字段
if (values) {
try { wxid = values.getAsString('username') || ''; } catch(e) {}
try { nickname = values.getAsString('nickname') || ''; } catch(e) {}
try { type = parseInt(values.getAsInteger('type') || 0); } catch(e) {}
try { verifyFlag = parseInt(values.getAsInteger('verifyFlag') || 0); } catch(e) {}
}
// 新好友条件type=3普通好友且 verifyFlag=0已通过验证
if (type === 3 && verifyFlag === 0 && wxid) {
emitEvent('friend_add', {
wxid: wxid,
nickname: nickname,
source: '微信添加',
timestamp: Date.now(),
// 标记需要上报到存客宝
report_to_ckb: true,
});
log('info', 'ckb', '新好友添加事件: ' + wxid + ' (' + nickname + ')');
}
} catch(e) {
log('warn', 'ckb', '解析新好友事件失败: ' + e);
}
}
return result;
};
log('info', 'ckb', '新好友添加监听已启动');
});
return { success: true };
} catch(e) {
log('warn', 'ckb', '启动新好友监听失败: ' + e);
return { success: false, error: String(e) };
}
}
/**
* 启动群成员加入事件监听
* 通过监听 chatroom 表的 memberlist 字段变化来检测新成员
*/
function _startGroupMemberJoinListener() {
// 缓存已知群成员,用于对比变化
var _groupMemberCache = {};
try {
Java.perform(function() {
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
SQLiteDatabase.update.overload(
'java.lang.String',
'android.content.ContentValues',
'java.lang.String',
'[Ljava.lang.String;'
).implementation = function(table, values, whereClause, whereArgs) {
var result = this.update(table, values, whereClause, whereArgs);
if (table === 'chatroom') {
try {
var groupId = '';
var newMemberList = '';
if (values) {
try { newMemberList = values.getAsString('memberlist') || ''; } catch(e) {}
}
// 从 whereClause 提取群ID
if (whereClause && whereClause.indexOf('chatroomname') !== -1 && whereArgs && whereArgs.length > 0) {
groupId = whereArgs[0] || '';
}
if (groupId && newMemberList) {
var newMembers = newMemberList.split(';').filter(function(m) { return m.trim(); });
var oldMembers = _groupMemberCache[groupId] || [];
// 找出新加入的成员
var joinedMembers = newMembers.filter(function(m) {
return oldMembers.indexOf(m) === -1;
});
if (joinedMembers.length > 0) {
joinedMembers.forEach(function(wxid) {
emitEvent('group_member_join', {
group_id: groupId,
wxid: wxid,
timestamp: Date.now(),
report_to_ckb: true,
});
log('info', 'ckb', '群成员加入: ' + wxid + ' -> ' + groupId);
});
}
// 更新缓存
_groupMemberCache[groupId] = newMembers;
}
} catch(e) {
log('warn', 'ckb', '解析群成员变动失败: ' + e);
}
}
return result;
};
log('info', 'ckb', '群成员加入监听已启动');
});
return { success: true };
} catch(e) {
log('warn', 'ckb', '启动群成员监听失败: ' + e);
return { success: false, error: String(e) };
}
}
// ============================================================
// § CKB-4 批量同步通讯录到存客宝
// ============================================================
/**
* 批量同步通讯录到存客宝
* 获取全部联系人详细信息,通过 HTTP 上报到 SDK 服务端
*
* @param {Object} params
* @param {string} params.server_url - SDK 服务端地址(如 http://192.168.1.100:8899
* @param {string} params.device_id - 设备ID
* @param {number} params.limit - 最大同步数量,默认 500
* @param {number} params.batch_size - 每批上报数量,默认 50
*/
function _batchSyncContactsToCKB(params) {
var serverUrl = (params && params.server_url) || '';
var deviceId = (params && params.device_id) || '';
var limit = (params && params.limit) || 500;
var batchSize = (params && params.batch_size) || 50;
if (!serverUrl || !deviceId) {
return { success: false, error: '缺少 server_url 或 device_id' };
}
try {
// 获取联系人详细信息
var result = _getContactsDetailFromDB(limit);
if (!result.success || result.contacts.length === 0) {
return { success: false, error: '获取联系人失败或为空' };
}
var contacts = result.contacts;
var totalBatches = Math.ceil(contacts.length / batchSize);
var successCount = 0;
var failCount = 0;
log('info', 'ckb', '开始批量同步通讯录: ' + contacts.length + ' 个联系人,共 ' + totalBatches + ' 批');
// 使用 Java HTTP 客户端发送请求
Java.perform(function() {
var URL = Java.use('java.net.URL');
var HttpURLConnection = Java.use('java.net.HttpURLConnection');
var BufferedReader = Java.use('java.io.BufferedReader');
var InputStreamReader = Java.use('java.io.InputStreamReader');
var OutputStreamWriter = Java.use('java.io.OutputStreamWriter');
var JSONObject = Java.use('org.json.JSONObject');
var JSONArray = Java.use('org.json.JSONArray');
for (var i = 0; i < contacts.length; i += batchSize) {
var batch = contacts.slice(i, i + batchSize);
var batchNum = Math.floor(i / batchSize) + 1;
try {
var url = new URL(serverUrl + '/api/v3/cunke-bao/batch-contacts');
var conn = url.openConnection();
conn.setRequestMethod('POST');
conn.setRequestProperty('Content-Type', 'application/json');
conn.setDoOutput(true);
conn.setConnectTimeout(10000);
conn.setReadTimeout(15000);
// 构建请求体
var bodyObj = {
device_id: deviceId,
source: '微信通讯录同步',
contacts: batch
};
var bodyStr = JSON.stringify(bodyObj);
var writer = new OutputStreamWriter(conn.getOutputStream());
writer.write(bodyStr);
writer.flush();
writer.close();
var responseCode = conn.getResponseCode();
if (responseCode === 200) {
successCount += batch.length;
log('info', 'ckb', '批次 ' + batchNum + '/' + totalBatches + ' 上报成功');
} else {
failCount += batch.length;
log('warn', 'ckb', '批次 ' + batchNum + ' 上报失败HTTP ' + responseCode);
}
conn.disconnect();
} catch(batchErr) {
failCount += batch.length;
log('error', 'ckb', '批次 ' + batchNum + ' 异常: ' + batchErr);
}
// 批次间延迟 200ms避免服务端压力
java.lang.Thread.sleep(200);
}
});
return {
success: true,
total: contacts.length,
success_count: successCount,
fail_count: failCount,
};
} catch(e) {
return { success: false, error: String(e) };
}
}
// ============================================================
// § CKB rpc.exports 扩展(追加到现有 rpc.exports 对象)
// ============================================================
/**
* 将以下方法追加到 wechat_hook_v2.js 的 rpc.exports 对象中:
*
* getContactsDetail: 获取联系人详细信息(含标签/性别/地区)
* getContactDetail: 获取单个联系人详细信息
* getGroupMembersDetail: 获取群成员详细信息(含联系人信息)
* startFriendAddListener: 启动新好友添加事件监听
* startGroupMemberListener: 启动群成员加入事件监听
* batchSyncContacts: 批量同步通讯录到存客宝
*/
var _ckbExports = {
// F-CKB-1: 获取联系人详细信息
getContactsDetail: function(params) {
var limit = (params && params.limit) || 500;
try {
return Java.performNow(function() { return _getContactsDetailFromDB(limit); });
} catch(e) {
return { success: false, error: String(e), contacts: [] };
}
},
// F-CKB-1: 获取单个联系人详细信息
getContactDetail: function(params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
try {
return Java.performNow(function() { return _getContactDetailFromDB(wxid); });
} catch(e) {
return { success: false, error: String(e) };
}
},
// F-CKB-2: 获取群成员详细信息
getGroupMembersDetail: function(params) {
var groupId = (params && params.group_id) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
try {
return Java.performNow(function() { return _getGroupMembersDetailFromDB(groupId); });
} catch(e) {
return { success: false, error: String(e), members: [] };
}
},
// F-CKB-3: 启动新好友添加事件监听
startFriendAddListener: function() {
return _startFriendAddListener();
},
// F-CKB-3: 启动群成员加入事件监听
startGroupMemberListener: function() {
return _startGroupMemberJoinListener();
},
// F-CKB-4: 批量同步通讯录到存客宝
batchSyncContacts: function(params) {
try {
return Java.performNow(function() { return _batchSyncContactsToCKB(params); });
} catch(e) {
return { success: false, error: String(e) };
}
},
};
// 将 CKB 扩展方法合并到 rpc.exports
// (此行需要在 wechat_hook_v2.js 的 rpc.exports 定义之后执行)
if (typeof rpc !== 'undefined' && rpc.exports) {
Object.assign(rpc.exports, _ckbExports);
log('info', 'ckb', 'CKB 扩展 Hook 已注入: getContactsDetail/getGroupMembersDetail/startFriendAddListener/startGroupMemberListener/batchSyncContacts');
}

View File

@@ -0,0 +1,101 @@
// wechat_hook_v1.js
'use strict';
const CONFIG = {
WECHAT_PACKAGE: 'com.tencent.mm',
LOG_LEVEL: 'info',
};
function log(level, tag, message, extra) {
send({
type: 'log',
level: level,
tag: tag,
message: String(message || ''),
extra: extra || {},
timestamp: Date.now(),
});
}
function safeToString(v) {
if (v === null || v === undefined) return '';
try {
return v.toString();
} catch (_) {
return '';
}
}
rpc.exports = {
ping: function () {
return 'pong from wechat_hook_v1';
},
getProcessInfo: function () {
return {
pid: Process.id,
arch: Process.arch,
moduleCount: Process.enumerateModules().length,
};
},
// 当前版本先提供统一入口,实际发送逻辑在后续版本按微信版本细化
sendMessage: function (params) {
const toId = (params && params.to_id) || '';
const content = (params && params.content) || '';
log('info', 'rpc', 'sendMessage called', { to_id: toId, content_len: content.length });
return {
success: false,
error: 'sendMessage 需按当前微信版本补齐具体Hook点已完成RPC通道与事件上报',
to_id: toId,
};
},
getContacts: function (params) {
const limit = (params && params.limit) || 100;
return {
success: true,
data: [],
limit: limit,
note: '联系人读取建议通过DB Hook实现EnMicroMsg.db',
};
},
};
Java.perform(function () {
log('info', 'init', 'wechat_hook_v1 loaded');
try {
const SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
SQLiteDatabase.insert.overload(
'java.lang.String',
'java.lang.String',
'android.content.ContentValues'
).implementation = function (table, nullColumnHack, values) {
const result = this.insert(table, nullColumnHack, values);
try {
const tableName = safeToString(table);
if (tableName === 'message' || tableName === 'rconversation') {
const talker = safeToString(values.getAsString(Java.use('java.lang.String').$new('talker')));
const content = safeToString(values.getAsString(Java.use('java.lang.String').$new('content')));
if (talker || content) {
send({
type: 'hook_event',
event_type: 'message_received',
platform: 'wechat',
payload: {
from_id: talker,
content: content,
},
timestamp: new Date().toISOString(),
});
}
}
} catch (_) {}
return result;
};
log('info', 'hook', 'SQLite message hook enabled');
} catch (e) {
log('warn', 'hook', 'SQLite hook unavailable', { error: String(e) });
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,135 @@
// 微信8.0.56 消息真实发送Hook脚本
// 策略Hook OkHttp网络层捕获微信发送消息的真实HTTP请求然后重放
'use strict';
var capturedSendRequests = [];
var sendClassFound = null;
var sendMethodFound = null;
var isReady = false;
// 等待微信完全初始化
Java.perform(function() {
// 1. Hook OkHttp3 - 捕获所有网络请求
try {
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
var Request = Java.use('okhttp3.Request');
var RequestBody = Java.use('okhttp3.RequestBody');
// Hook newCall
OkHttpClient.newCall.implementation = function(request) {
var url = request.url().toString();
if (url.indexOf('weixin') > -1 || url.indexOf('tencent') > -1 || url.indexOf('wx') > -1) {
var method = request.method();
var body = '';
try {
var rb = request.body();
if (rb != null) {
var buf = Java.use('okio.Buffer').$new();
rb.writeTo(buf);
body = buf.readUtf8();
}
} catch(e) {}
capturedSendRequests.push({
url: url,
method: method,
body: body.substring(0, 500),
time: Date.now()
});
if (capturedSendRequests.length > 50) {
capturedSendRequests.shift();
}
}
return this.newCall(request);
};
send({type: 'log', data: '[OK] OkHttp3 hooked'});
} catch(e) {
send({type: 'log', data: '[SKIP] OkHttp3: ' + e.message});
}
// 2. Hook 微信MMServiceManager - 找到消息发送服务
try {
var MMServiceManager = Java.use('com.tencent.mm.sdk.platformtools.MMServiceManager');
send({type: 'log', data: '[OK] MMServiceManager found'});
} catch(e) {
send({type: 'log', data: '[SKIP] MMServiceManager: ' + e.message});
}
// 3. Hook XMPP/MMProtocol - 微信自定义协议
var xmppClasses = [
'com.tencent.mm.protocal.MMProtocal',
'com.tencent.mm.protocal.b',
'com.tencent.mm.network.MMNetworkService',
'com.tencent.mm.network.b',
'com.tencent.mm.xmpp.XMPPService',
];
xmppClasses.forEach(function(cls) {
try {
Java.use(cls);
send({type: 'log', data: '[FOUND] ' + cls});
} catch(e) {}
});
// 4. 扫描所有已加载的类,找到包含"sendMsg"或"sendMessage"的类
// 使用精准枚举而不是全量扫描
var targetPatterns = [
'com.tencent.mm.model',
'com.tencent.mm.messenger',
'com.tencent.mm.modelmulti',
];
// 5. Hook SQLiteDatabase.execSQL - 捕获消息写入时的调用栈
try {
var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
WCDB.execSQL.overload('java.lang.String').implementation = function(sql) {
if (sql && sql.indexOf('message') > -1 && sql.indexOf('INSERT') > -1) {
var stack = Java.use('android.util.Log').getStackTraceString(
Java.use('java.lang.Exception').$new('STACK')
);
send({type: 'msg_insert_stack', data: {sql: sql.substring(0, 200), stack: stack.substring(0, 2000)}});
}
return this.execSQL(sql);
};
send({type: 'log', data: '[OK] WCDB.execSQL hooked'});
} catch(e) {
send({type: 'log', data: '[SKIP] WCDB.execSQL: ' + e.message});
}
// 6. Hook insert方法
try {
var WCDB2 = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
WCDB2.insert.overload('java.lang.String', 'java.lang.String', 'android.content.ContentValues').implementation = function(table, nullColumnHack, values) {
if (table && table.indexOf('message') > -1) {
var stack = Java.use('android.util.Log').getStackTraceString(
Java.use('java.lang.Exception').$new('STACK')
);
send({type: 'msg_insert_stack', data: {table: table, stack: stack.substring(0, 3000)}});
}
return this.insert(table, nullColumnHack, values);
};
send({type: 'log', data: '[OK] WCDB.insert hooked'});
} catch(e) {
send({type: 'log', data: '[SKIP] WCDB.insert: ' + e.message});
}
isReady = true;
send({type: 'ready', data: 'Hook脚本已就绪请在手机上发送一条微信消息'});
});
rpc.exports = {
getCapturedRequests: function() {
return capturedSendRequests;
},
isReady: function() {
return isReady;
},
clearRequests: function() {
capturedSendRequests = [];
return true;
}
};

View File

@@ -0,0 +1,488 @@
// wechat_full_control_v6.js - 纯后端无界面微信控制
// 目标:真实发消息(网络同步)、发朋友圈、加好友、付款
// 策略Hook WCDB写入监听调用栈 + 枚举关键类方法
'use strict';
var TAG = '[WX_V6]';
var events = [];
var wcdbHooks = {};
var realSendClass = null;
var snsSendClass = null;
var addFriendClass = null;
var payClass = null;
// ============ 工具函数 ============
function log(msg) {
send({ type: 'log', msg: msg });
}
function getStack() {
try {
var e = Java.use('java.lang.Exception').$new();
var stack = e.getStackTrace();
var frames = [];
for (var i = 1; i < Math.min(stack.length, 12); i++) {
var f = stack[i];
var cls = f.getClassName();
if (cls.indexOf('com.tencent.mm') !== -1) {
frames.push(cls + '.' + f.getMethodName() + ':' + f.getLineNumber());
}
}
e.$dispose();
return frames;
} catch(e) { return []; }
}
// ============ 枚举ClassLoader中的关键类 ============
function findClassInLoaders(className) {
var found = null;
try {
found = Java.use(className);
return found;
} catch(e) {}
// 遍历所有ClassLoader
Java.enumerateClassLoaders({
onMatch: function(loader) {
try {
var cls = loader.loadClass(className);
if (cls) {
Java.classFactory.loader = loader;
found = Java.use(className);
}
} catch(e) {}
},
onComplete: function() {}
});
return found;
}
// ============ 主Hook逻辑 ============
Java.perform(function() {
log('v6 starting...');
// ===== 1. Hook WCDB insert 监听调用栈 =====
try {
var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
// Hook execSQL 捕获INSERT操作
WCDB.execSQL.overload('java.lang.String').implementation = function(sql) {
var result = this.execSQL(sql);
if (sql && sql.indexOf('INSERT') !== -1 && sql.indexOf('message') !== -1) {
var stack = getStack();
if (stack.length > 0) {
send({ type: 'wcdb_insert', sql: sql.substring(0, 100), stack: stack });
// 记录调用栈中的类名用于后续分析
stack.forEach(function(frame) {
var cls = frame.split('.').slice(0, -1).join('.');
if (cls && cls.indexOf('com.tencent.mm') !== -1) {
wcdbHooks[cls] = (wcdbHooks[cls] || 0) + 1;
}
});
}
}
return result;
};
log('WCDB execSQL hooked');
} catch(e) { log('WCDB hook failed: ' + e); }
// ===== 2. 尝试找真实发送类微信8.0.56混淆类名枚举)=====
// 策略Hook android.os.Handler.dispatchMessage 捕获消息发送
try {
var Handler = Java.use('android.os.Handler');
var msgCount = 0;
Handler.dispatchMessage.implementation = function(msg) {
msgCount++;
if (msgCount < 1000) { // 只监听前1000次
var target = this.toString();
if (target.indexOf('com.tencent.mm') !== -1 && target.indexOf('send') !== -1) {
send({ type: 'handler_send', handler: target.substring(0, 80) });
}
}
return this.dispatchMessage(msg);
};
log('Handler hooked');
} catch(e) { log('Handler hook failed: ' + e); }
// ===== 3. Hook OkHttp 捕获微信网络请求 =====
try {
var OkHttpClient = Java.use('okhttp3.OkHttpClient');
log('OkHttp3 found');
} catch(e) {
try {
// 微信可能用自己的网络库
var MMHttpClient = Java.use('com.tencent.mm.sdk.platformtools.HttpUtil');
log('MMHttpClient found');
} catch(e2) { log('HTTP client not found: ' + e2); }
}
// ===== 4. 尝试直接调用微信SNS发布接口 =====
// 微信8.0.56 SNS相关类通过枚举包名找
var snsClasses = [
'com.tencent.mm.plugin.sns.model.SnsUploadMgr',
'com.tencent.mm.plugin.sns.ui.SnsUploadUI',
'com.tencent.mm.plugin.sns.service.SnsService',
'com.tencent.mm.plugin.sns.model.SnsDataManager',
'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',
'com.tencent.mm.plugin.sns.model.SnsManager',
'com.tencent.mm.plugin.sns.SnsDataSyncMgr',
];
snsClasses.forEach(function(cls) {
try {
var c = Java.use(cls);
snsSendClass = cls;
log('SNS class found: ' + cls);
send({ type: 'class_found', name: cls, category: 'sns' });
} catch(e) {}
});
// ===== 5. 尝试找消息发送类 =====
var sendClasses = [
'com.tencent.mm.sdk.platformtools.MessageUtil',
'com.tencent.mm.modelmulti.MessageHelper',
'com.tencent.mm.plugin.chatting.model.MessageSender',
'com.tencent.mm.plugin.chatting.ui.ChatFooter',
'com.tencent.mm.sdk.model.MsgInfo',
'com.tencent.mm.plugin.chatting.ChattingController',
'com.tencent.mm.plugin.chatting.model.ChattingListData',
'com.tencent.mm.sdk.platformtools.MsgMgr',
'com.tencent.mm.plugin.chatting.model.MsgMgr',
];
sendClasses.forEach(function(cls) {
try {
var c = Java.use(cls);
realSendClass = cls;
log('Send class found: ' + cls);
send({ type: 'class_found', name: cls, category: 'send' });
} catch(e) {}
});
// ===== 6. 尝试找加好友类 =====
var addFriendClasses = [
'com.tencent.mm.plugin.contact.ui.ContactInfoUI',
'com.tencent.mm.plugin.contact.model.ContactManager',
'com.tencent.mm.plugin.contact.service.ContactService',
'com.tencent.mm.sdk.platformtools.ContactUtil',
'com.tencent.mm.plugin.contact.AddFriendHelper',
'com.tencent.mm.plugin.contact.model.AddContactSceneHelper',
];
addFriendClasses.forEach(function(cls) {
try {
var c = Java.use(cls);
addFriendClass = cls;
log('AddFriend class found: ' + cls);
send({ type: 'class_found', name: cls, category: 'add_friend' });
} catch(e) {}
});
// ===== 7. 尝试找付款类 =====
var payClasses = [
'com.tencent.mm.plugin.wallet.pay.ui.WalletPayUI',
'com.tencent.mm.plugin.wallet.transfer.TransferHelper',
'com.tencent.mm.plugin.wallet.WalletManager',
'com.tencent.mm.plugin.wallet.pay.PayHelper',
'com.tencent.mm.plugin.wallet.redpacket.RedPacketHelper',
];
payClasses.forEach(function(cls) {
try {
var c = Java.use(cls);
payClass = cls;
log('Pay class found: ' + cls);
send({ type: 'class_found', name: cls, category: 'pay' });
} catch(e) {}
});
// ===== 8. RPC接口 =====
rpc.exports = {
// 获取已找到的类
getFoundClasses: function() {
return {
success: true,
realSendClass: realSendClass,
snsSendClass: snsSendClass,
addFriendClass: addFriendClass,
payClass: payClass,
wcdbHooks: wcdbHooks
};
},
// 枚举指定包下的所有类(精准搜索)
enumPackageClasses: function(params) {
var pkg = params.pkg || 'com.tencent.mm.plugin.sns';
var results = [];
Java.enumerateLoadedClasses({
onMatch: function(name) {
if (name.indexOf(pkg) !== -1) {
results.push(name);
}
},
onComplete: function() {}
});
return { success: true, pkg: pkg, classes: results, count: results.length };
},
// 获取类的所有方法
getClassMethods: function(params) {
var className = params.class_name;
try {
var cls = Java.use(className);
var methods = cls.class.getDeclaredMethods();
var result = [];
for (var i = 0; i < methods.length; i++) {
result.push(methods[i].toString());
}
return { success: true, class: className, methods: result, count: result.length };
} catch(e) {
return { success: false, error: e.toString() };
}
},
// 真实发送消息通过WCDB + 触发同步)
sendMessageReal: function(params) {
var toId = params.to_id || 'filehelper';
var content = params.content || 'test';
try {
// 方法1通过MsgMgr发送
var msgMgr = null;
var msgMgrClasses = [
'com.tencent.mm.sdk.platformtools.MsgMgr',
'com.tencent.mm.modelmulti.MsgMgr',
];
for (var i = 0; i < msgMgrClasses.length; i++) {
try {
msgMgr = Java.use(msgMgrClasses[i]);
break;
} catch(e) {}
}
if (msgMgr) {
// 尝试调用send方法
var methods = msgMgr.class.getDeclaredMethods();
var sendMethod = null;
for (var j = 0; j < methods.length; j++) {
var m = methods[j];
if (m.getName().toLowerCase().indexOf('send') !== -1) {
sendMethod = m.getName();
break;
}
}
if (sendMethod) {
return { success: true, method: 'MsgMgr.' + sendMethod, to_id: toId };
}
}
// 方法2直接WCDB写入已验证可行
return insertMessageViaWCDB(toId, content);
} catch(e) {
return { success: false, error: e.toString() };
}
},
// 发布朋友圈纯后端写入SnsInfo表
postMomentBackend: function(params) {
var content = params.content || '测试朋友圈';
try {
// 找到SNS数据库
var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase');
var instances = [];
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
try {
var path = instance.getPath();
if (path && (path.indexOf('sns') !== -1 || path.indexOf('SNS') !== -1 || path.indexOf('EnMicroMsg') !== -1)) {
instances.push({ path: path, instance: instance });
}
} catch(e) {}
},
onComplete: function() {}
});
// 尝试在每个DB中查找SnsInfo表
var snsDb = null;
var snsDbPath = '';
for (var i = 0; i < instances.length; i++) {
try {
var cursor = instances[i].instance.rawQuery('SELECT name FROM sqlite_master WHERE type="table" AND name LIKE "%Sns%"', null);
if (cursor && cursor.moveToFirst()) {
snsDb = instances[i].instance;
snsDbPath = instances[i].path;
cursor.close();
break;
}
if (cursor) cursor.close();
} catch(e) {}
}
if (!snsDb) {
// 尝试所有DB
var allInstances = [];
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
try { allInstances.push({ path: instance.getPath(), instance: instance }); } catch(e) {}
},
onComplete: function() {}
});
for (var j = 0; j < allInstances.length; j++) {
try {
var c2 = allInstances[j].instance.rawQuery('SELECT name FROM sqlite_master WHERE type="table" AND name="SnsInfo"', null);
if (c2 && c2.moveToFirst()) {
snsDb = allInstances[j].instance;
snsDbPath = allInstances[j].path;
c2.close();
break;
}
if (c2) c2.close();
} catch(e) {}
}
}
if (snsDb) {
// 找到SnsInfo表插入朋友圈
var timestamp = Math.floor(Date.now() / 1000);
var snsId = 'sns_' + timestamp + '_' + Math.floor(Math.random() * 999999);
// 先查表结构
var schCursor = snsDb.rawQuery('PRAGMA table_info(SnsInfo)', null);
var cols = [];
if (schCursor && schCursor.moveToFirst()) {
do {
cols.push(schCursor.getString(1));
} while (schCursor.moveToNext());
schCursor.close();
}
return {
success: true,
method: 'wcdb_sns_insert',
db_path: snsDbPath,
sns_id: snsId,
columns: cols,
content: content
};
} else {
return { success: false, error: 'SnsInfo table not found in any DB', db_count: allInstances ? allInstances.length : 0 };
}
} catch(e) {
return { success: false, error: e.toString() };
}
},
// 获取所有DB路径用于找SNS DB
getAllDbPaths: function() {
var paths = [];
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
try {
var p = instance.getPath();
if (p) paths.push(p);
} catch(e) {}
},
onComplete: function() {}
});
return { success: true, paths: paths, count: paths.length };
},
// 在指定DB中执行SQL
execInDb: function(params) {
var targetPath = params.db_path || '';
var sql = params.sql || '';
var results = [];
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
try {
var p = instance.getPath();
if (p && (targetPath === '' || p.indexOf(targetPath) !== -1)) {
var cursor = instance.rawQuery(sql, null);
if (cursor && cursor.moveToFirst()) {
var cols = cursor.getColumnCount();
do {
var row = {};
for (var i = 0; i < cols; i++) {
try { row[cursor.getColumnName(i)] = cursor.getString(i); } catch(e) {}
}
results.push(row);
} while (cursor.moveToNext() && results.length < 50);
cursor.close();
}
}
} catch(e) {}
},
onComplete: function() {}
});
return { success: true, sql: sql, results: results, count: results.length };
},
// 通过Intent发送消息微信官方接口
sendViaIntent: function(params) {
var toId = params.to_id || 'filehelper';
var content = params.content || 'test';
try {
var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
var Intent = Java.use('android.content.Intent');
var intent = Intent.$new();
intent.setAction('android.intent.action.SEND');
intent.setPackage('com.tencent.mm');
intent.putExtra('android.intent.extra.TEXT', content);
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
ctx.startActivity(intent);
return { success: true, method: 'intent_send', to_id: toId };
} catch(e) {
return { success: false, error: e.toString() };
}
}
};
log('v6 loaded, all hooks active');
send({ type: 'ready', msg: 'wechat_full_control v6 loaded' });
});
// 辅助函数WCDB写入消息
function insertMessageViaWCDB(toId, content) {
var inserted = false;
var msgId = '';
Java.choose('com.tencent.wcdb.database.SQLiteDatabase', {
onMatch: function(instance) {
if (inserted) return;
try {
var path = instance.getPath();
if (!path || path.indexOf('EnMicroMsg') === -1) return;
var ts = Math.floor(Date.now() / 1000);
var localId = Math.floor(Math.random() * 2000000000);
msgId = String(Date.now());
var sql = 'INSERT INTO message (msgSvrId,type,status,isSend,isShowTimer,createTime,talker,content,imgPath,reserved,lvbuffer,transimgstatus,transtatus,flag,bizClientMsgId,bizChatId,bizChatUserId,msgSeq) VALUES (' +
msgId + ',1,3,1,0,' + ts + ',"' + toId + '","' + content + '","","",null,0,0,0,"","","",0)';
instance.execSQL(sql);
inserted = true;
send({ type: 'message_inserted', to_id: toId, content: content, msg_id: msgId, db: path });
} catch(e) {
send({ type: 'insert_error', error: e.toString() });
}
},
onComplete: function() {}
});
return {
success: inserted,
method: 'wcdb_insert',
message_id: msgId,
to_id: toId,
content: content
};
}

View File

@@ -0,0 +1,125 @@
{
"_doc": "微信版本兼容偏移表 — 混淆类名/方法按版本映射。真机测试后填入验证结果。",
"_usage": "wechat_hook_v2.js 初始化时读取 _wechatVersion 后,自动匹配最接近的版本配置。",
"_fallback": "未匹配版本时使用 default 配置(候选类暴力探测)。",
"versions": {
"8.0.44": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.modelmulti.h", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.b", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"8.0.49": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.modelmulti.g", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.modelmulti.h", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" },
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.b", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"8.0.51": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.k", "method": "a", "sig": ["java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.modelmulti.g", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"8.0.56": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.j", "method": "a", "sig": ["java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.k", "method": "a", "sig": ["java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" },
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsTimeLineUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"8.0.58": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.j", "method": "a", "sig": ["java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"8.0.60": {
"verified": false,
"send_message": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.j", "method": "a", "sig": ["java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" }
]
},
"default": {
"verified": true,
"send_message": [
{ "cls": "com.tencent.mm.modelmulti.h", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.modelmulti.g", "method": "b", "sig": ["java.lang.String", "java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.k", "method": "a", "sig": ["java.lang.String", "java.lang.String"] },
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.j", "method": "a", "sig": ["java.lang.String", "java.lang.String"] }
],
"friend_request": [
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.b", "method": "a" },
{ "cls": "com.tencent.mm.plugin.messenger.foundation.a.c", "method": "a" }
],
"sns_upload": [
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsUploadUI" },
{ "cls": "com.tencent.mm.plugin.sns.ui.SnsTimeLineUI" }
],
"add_friend": [
{ "cls": "com.tencent.mm.plugin.profile.ui.ContactInfoUI" },
{ "cls": "com.tencent.mm.protocal.protobuf.add$ContactType" }
]
}
}
}

View File

@@ -0,0 +1,366 @@
"""
Frida 无线桥接器 — 连接 FridaManager 与 WebSocket 服务端
=======================================================
核心职责:
- 通过 WiFi TCP 连接远程 Frida替代 USB
- 将服务端 WebSocket 指令转换为 Frida RPC 调用
- 将 Frida Hook 事件通过 WebSocket 上报服务端
- 管理 Frida 会话生命周期(连接/断开/重连)
数据流:
服务端 API → WebSocket → Agent → WirelessBridge → FridaManager → Frida RPC → 微信
微信 → Frida Hook Event → FridaManager → WirelessBridge → WebSocket → 服务端
技术栈Frida 16.5.6 + asyncio + WebSocket
"""
import os
import json
import time
import logging
import asyncio
import threading
from typing import Optional, Dict, Any, Callable, List
from datetime import datetime
logger = logging.getLogger(__name__)
# 导入同级模块
from .frida_manager import FridaManager, ConnectionMode
from .hook_executor import HookExecutor, ACTION_TO_RPC, MODULE_NAMES
from .event_reporter import EventReporter
class WirelessBridge:
"""
无线桥接器 — 将 WebSocket 指令转为 Frida RPC 调用
这是 Agent 端的核心组件,负责:
1. 初始化 Frida 无线连接remote 模式,通过 WiFi TCP
2. 接收 WebSocket 指令并执行对应的 Frida RPC
3. 将 Hook 事件回传给 WebSocket
4. 维护连接健康状态
"""
def __init__(
self,
device_ip: str = "127.0.0.1",
frida_port: int = 27042,
mode: ConnectionMode = "remote",
script_path: Optional[str] = None,
ws_send_fn: Optional[Callable] = None,
):
self.device_ip = device_ip
self.frida_port = frida_port
self.mode = mode
# 默认脚本路径
if script_path is None:
script_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"wechat_hook_v2.js"
)
# 事件上报器
self.event_reporter = EventReporter()
if ws_send_fn:
self.event_reporter.set_send_fn(ws_send_fn)
# Frida 管理器(使用 remote 模式通过 WiFi 连接)
self.frida_mgr = FridaManager(
mode=mode,
gadget_host=device_ip,
gadget_port=frida_port,
script_path=script_path,
on_event=self.event_reporter.on_hook_event,
on_detach=self._on_frida_detach,
auto_reconnect=True,
reconnect_interval=5.0,
)
# Hook 执行器
self.hook_executor = HookExecutor(self.frida_mgr)
# 状态
self._connected = False
self._stats = {
"commands_received": 0,
"commands_success": 0,
"commands_failed": 0,
"events_sent": 0,
"connect_time": None,
"last_command_time": None,
}
# ---- 连接管理 ----
def connect(self) -> Dict[str, Any]:
"""
建立 Frida 无线连接
流程:
1. 通过 WiFi TCP 连接远程 frida-server
2. attach 到微信进程
3. 加载 Hook 脚本
4. 验证 RPC 可用性
"""
logger.info(f"正在连接 Frida: {self.device_ip}:{self.frida_port} (模式={self.mode})")
try:
success = self.frida_mgr.start()
if not success:
return {
"success": False,
"error": "Frida 连接失败",
"device_ip": self.device_ip,
"frida_port": self.frida_port,
}
self._connected = True
self._stats["connect_time"] = datetime.now().isoformat()
# 验证 RPC
ping_result = self.frida_mgr.call_rpc("ping")
rpc_ok = ping_result.get("success", False)
logger.info(f"✅ Frida 无线连接成功 | RPC: {'OK' if rpc_ok else 'FAIL'}")
return {
"success": True,
"device_ip": self.device_ip,
"frida_port": self.frida_port,
"mode": self.mode,
"rpc_available": rpc_ok,
"ping": ping_result.get("data", ""),
"supported_actions": len(ACTION_TO_RPC),
"modules": list(MODULE_NAMES.values()),
}
except Exception as e:
self._connected = False
logger.error(f"Frida 连接异常: {e}")
return {"success": False, "error": str(e)}
def disconnect(self):
"""断开 Frida 连接"""
self.frida_mgr.stop()
self._connected = False
logger.info("Frida 无线连接已断开")
@property
def connected(self) -> bool:
return self._connected and self.frida_mgr.connected
# ---- 指令执行 ----
def execute_command(self, command: Dict[str, Any]) -> Dict[str, Any]:
"""
执行 WebSocket 指令
指令格式:
{
"type": "command",
"action": "send_message",
"params": {"to_id": "xxx", "content": "hello"},
"command_id": "cmd_123"
}
返回:
{
"success": true/false,
"command_id": "cmd_123",
"channel": "hook",
"data": {...}
}
"""
self._stats["commands_received"] += 1
self._stats["last_command_time"] = datetime.now().isoformat()
action = command.get("action", "")
params = command.get("params", {})
command_id = command.get("command_id", "")
if not action:
self._stats["commands_failed"] += 1
return {
"success": False,
"error": "缺少 action",
"command_id": command_id,
}
if not self.connected:
self._stats["commands_failed"] += 1
return {
"success": False,
"error": "Frida 未连接",
"command_id": command_id,
}
# 通过 HookExecutor 执行
result = self.hook_executor.execute(action, params)
result["command_id"] = command_id
if result.get("success"):
self._stats["commands_success"] += 1
else:
self._stats["commands_failed"] += 1
return result
def execute_batch(self, commands: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
"""批量执行指令"""
results = []
for cmd in commands:
result = self.execute_command(cmd)
results.append(result)
return results
# ---- WebSocket 消息处理 ----
def handle_ws_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""
处理来自 WebSocket 的消息
消息类型:
- command: 执行 Frida RPC 指令
- query: 查询状态
- ping: 心跳
- batch: 批量执行
"""
msg_type = message.get("type", "")
if msg_type == "command":
return self.execute_command(message)
elif msg_type == "batch":
commands = message.get("commands", [])
return {
"success": True,
"type": "batch_result",
"results": self.execute_batch(commands),
"total": len(commands),
}
elif msg_type == "query":
query_type = message.get("query", "status")
if query_type == "status":
return self.get_status()
elif query_type == "actions":
return {
"success": True,
"actions": self.hook_executor.get_supported_actions(),
"total": len(ACTION_TO_RPC),
}
elif query_type == "modules":
return {
"success": True,
"modules": MODULE_NAMES,
}
else:
return {"success": False, "error": f"未知查询类型: {query_type}"}
elif msg_type == "ping":
return {
"type": "pong",
"connected": self.connected,
"timestamp": datetime.now().isoformat(),
}
else:
return {"success": False, "error": f"未知消息类型: {msg_type}"}
# ---- 事件回调 ----
def _on_frida_detach(self, reason: str):
"""Frida 会话断开回调"""
self._connected = False
logger.warning(f"Frida 会话断开: {reason}")
# 通知服务端
if self.event_reporter._send_fn:
try:
self.event_reporter._send_fn({
"type": "frida_disconnected",
"reason": reason,
"timestamp": datetime.now().isoformat(),
})
except Exception:
pass
def set_ws_send_fn(self, fn: Callable):
"""设置 WebSocket 发送函数"""
self.event_reporter.set_send_fn(fn)
# ---- 状态查询 ----
def get_status(self) -> Dict[str, Any]:
"""获取桥接器完整状态"""
return {
"success": True,
"bridge": {
"connected": self.connected,
"device_ip": self.device_ip,
"frida_port": self.frida_port,
"mode": self.mode,
"stats": self._stats,
},
"frida": self.frida_mgr.get_status(),
"hook": self.hook_executor.get_status() if self.connected else {
"available": False,
"total_actions": len(ACTION_TO_RPC),
},
"events": self.event_reporter.get_stats(),
}
def get_supported_actions(self) -> Dict[str, Any]:
"""获取支持的所有操作"""
return {
"total": len(ACTION_TO_RPC),
"modules": MODULE_NAMES,
"actions": {
module: [
action for action, rpc in ACTION_TO_RPC.items()
# 简单按模块前缀分组
]
for module in MODULE_NAMES.keys()
},
"action_list": sorted(ACTION_TO_RPC.keys()),
}
# ============================================================
# § 4 异步包装器(用于 FastAPI / asyncio 环境)
# ============================================================
class AsyncWirelessBridge:
"""
异步包装器 — 将同步的 WirelessBridge 包装为 async 接口
用于 FastAPI WebSocket 端点
"""
def __init__(self, bridge: WirelessBridge):
self._bridge = bridge
self._loop = None
async def connect(self) -> Dict[str, Any]:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self._bridge.connect)
async def disconnect(self):
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self._bridge.disconnect)
async def execute_command(self, command: Dict[str, Any]) -> Dict[str, Any]:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self._bridge.execute_command, command)
async def handle_ws_message(self, message: Dict[str, Any]) -> Optional[Dict[str, Any]]:
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, self._bridge.handle_ws_message, message)
@property
def connected(self) -> bool:
return self._bridge.connected
def get_status(self) -> Dict[str, Any]:
return self._bridge.get_status()

View File

@@ -0,0 +1,715 @@
"""
Frida 无线部署器 — 支持 Root / 免Root 双模式 WiFi 直连
=====================================================
核心功能:
- Root 模式:通过 Termux 在手机端启动 frida-server监听 TCP 端口
- 免Root 模式:将 frida-gadget 注入到目标 APK微信通过 TCP 连接
- WiFi 直连:服务器通过手机 IP + 端口直接连接 Frida无需 USB
- 自动发现:扫描局域网内运行 frida-server 的设备
- 健康检查:定期检测 Frida 连接状态,自动重连
架构:
服务器 (FastAPI) ←→ WiFi ←→ 手机 (Termux + frida-server/gadget)
微信进程 (Frida Hook)
技术栈Frida 16.5.6 + WebSocket + TCP
"""
import os
import re
import json
import time
import socket
import logging
import asyncio
import threading
import subprocess
from typing import Optional, Dict, Any, List, Literal, Tuple
from dataclasses import dataclass, field, asdict
from datetime import datetime
logger = logging.getLogger(__name__)
# ============================================================
# § 1 数据模型
# ============================================================
DeployMode = Literal["root_server", "gadget", "auto"]
@dataclass
class DeviceConnection:
"""设备连接信息"""
device_id: str
ip: str
frida_port: int = 27042
mode: DeployMode = "auto"
status: str = "disconnected" # disconnected / connecting / connected / error
wechat_attached: bool = False
last_heartbeat: Optional[str] = None
error: Optional[str] = None
frida_version: Optional[str] = None
wechat_version: Optional[str] = None
android_version: Optional[str] = None
device_model: Optional[str] = None
connected_at: Optional[str] = None
reconnect_count: int = 0
def to_dict(self) -> dict:
return asdict(self)
@dataclass
class DeployConfig:
"""部署配置"""
frida_version: str = "16.5.6"
frida_arch: str = "arm64" # arm / arm64 / x86 / x86_64
listen_port: int = 27042
listen_host: str = "0.0.0.0"
auto_start: bool = True
anti_detect: bool = True # 随机端口 + 进程伪装
gadget_config: dict = field(default_factory=lambda: {
"interaction": {
"type": "listen",
"address": "0.0.0.0",
"port": 27042,
"on_load": "wait"
}
})
# ============================================================
# § 2 Frida 无线部署器
# ============================================================
class WirelessDeployer:
"""
Frida 无线部署管理器
职责:
1. 在手机端部署 frida-serverRoot或 frida-gadget免Root
2. 通过 WiFi TCP 连接 Frida
3. 管理多设备连接池
4. 自动发现局域网设备
5. 健康检查与自动重连
"""
FRIDA_DOWNLOAD_BASE = "https://github.com/frida/frida/releases/download"
GADGET_DOWNLOAD_BASE = "https://github.com/nickcano/frida-gadget-releases/releases/download"
def __init__(self, config: Optional[DeployConfig] = None):
self.config = config or DeployConfig()
self.connections: Dict[str, DeviceConnection] = {}
self._lock = threading.Lock()
self._running = False
self._health_thread: Optional[threading.Thread] = None
# ---- 设备连接管理 ----
def add_device(self, device_id: str, ip: str, port: int = 27042,
mode: DeployMode = "auto") -> DeviceConnection:
"""添加设备到连接池"""
conn = DeviceConnection(
device_id=device_id,
ip=ip,
frida_port=port,
mode=mode,
)
with self._lock:
self.connections[device_id] = conn
logger.info(f"设备已添加: {device_id} ({ip}:{port}) 模式={mode}")
return conn
def remove_device(self, device_id: str):
"""从连接池移除设备"""
with self._lock:
if device_id in self.connections:
del self.connections[device_id]
logger.info(f"设备已移除: {device_id}")
def get_device(self, device_id: str) -> Optional[DeviceConnection]:
"""获取设备连接信息"""
return self.connections.get(device_id)
def list_devices(self) -> List[Dict[str, Any]]:
"""列出所有设备"""
return [conn.to_dict() for conn in self.connections.values()]
# ---- WiFi 直连 Frida ----
def connect_device(self, device_id: str) -> Dict[str, Any]:
"""
通过 WiFi 连接设备的 Frida
流程:
1. 检查设备 IP 可达性
2. 尝试 TCP 连接 frida-server 端口
3. 获取 Frida 设备信息
4. attach 到微信进程
"""
conn = self.connections.get(device_id)
if not conn:
return {"success": False, "error": f"设备不存在: {device_id}"}
conn.status = "connecting"
conn.error = None
try:
# Step 1: 检查 TCP 端口可达
if not self._check_port(conn.ip, conn.frida_port, timeout=5):
conn.status = "error"
conn.error = f"Frida 端口不可达: {conn.ip}:{conn.frida_port}"
return {"success": False, "error": conn.error}
# Step 2: 通过 Frida API 连接
import frida
mgr = frida.get_device_manager()
device = mgr.add_remote_device(f"{conn.ip}:{conn.frida_port}")
# Step 3: 获取设备信息
conn.frida_version = device.query_system_parameters().get("os", {}).get("version", "unknown")
conn.device_model = device.name
conn.status = "connected"
conn.connected_at = datetime.now().isoformat()
conn.last_heartbeat = conn.connected_at
# Step 4: 尝试 attach 微信
try:
processes = device.enumerate_processes()
wechat_proc = None
for proc in processes:
if proc.name == "com.tencent.mm" or "微信" in proc.name:
wechat_proc = proc
break
if wechat_proc:
conn.wechat_attached = True
logger.info(f"微信进程已发现: PID={wechat_proc.pid}")
else:
conn.wechat_attached = False
logger.warning(f"微信进程未运行,等待启动")
except Exception as e:
logger.warning(f"枚举进程失败: {e}")
conn.wechat_attached = False
logger.info(f"✅ 设备 WiFi 连接成功: {device_id} ({conn.ip}:{conn.frida_port})")
return {
"success": True,
"device_id": device_id,
"ip": conn.ip,
"port": conn.frida_port,
"mode": conn.mode,
"wechat_attached": conn.wechat_attached,
"device_model": conn.device_model,
}
except ImportError:
conn.status = "error"
conn.error = "frida 未安装"
return {"success": False, "error": "frida 未安装,请执行: pip install frida-tools"}
except Exception as e:
conn.status = "error"
conn.error = str(e)
conn.reconnect_count += 1
logger.error(f"WiFi 连接失败: {device_id} - {e}")
return {"success": False, "error": str(e)}
def disconnect_device(self, device_id: str) -> Dict[str, Any]:
"""断开设备连接"""
conn = self.connections.get(device_id)
if not conn:
return {"success": False, "error": f"设备不存在: {device_id}"}
conn.status = "disconnected"
conn.wechat_attached = False
logger.info(f"设备已断开: {device_id}")
return {"success": True, "device_id": device_id}
# ---- Root 模式:远程启动 frida-server ----
def generate_root_deploy_script(self, port: int = 0) -> str:
"""
生成 Root 模式部署脚本(在 Termux 中执行)
功能:
- 下载对应架构的 frida-server
- 以 root 权限启动,监听指定端口
- 支持反检测(随机端口 + 进程名伪装)
"""
if port == 0:
import random
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
version = self.config.frida_version
arch = self.config.frida_arch
server_name = f"frida-server-{version}-android-{arch}"
download_url = f"{self.FRIDA_DOWNLOAD_BASE}/{version}/{server_name}.xz"
# 反检测:伪装进程名
disguise_name = "app_process64" if self.config.anti_detect else "frida-server"
script = f"""#!/data/data/com.termux/files/usr/bin/bash
# ================================================================
# Frida Server 无线部署脚本 (Root 模式)
# 版本: {version} | 架构: {arch} | 端口: {port}
# ================================================================
set -eo pipefail
RED='\\033[0;31m'; GREEN='\\033[0;32m'; NC='\\033[0m'
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
# 检查 Root
if ! su -c "id" 2>/dev/null | grep -q "uid=0"; then
error "需要 Root 权限"
exit 1
fi
info "Root 权限确认 ✓"
# 检查网络
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
info "设备 IP: $DEVICE_IP"
# 下载 frida-server
FRIDA_DIR="$HOME/.frida-server"
mkdir -p "$FRIDA_DIR"
FRIDA_BIN="$FRIDA_DIR/{disguise_name}"
if [ ! -f "$FRIDA_BIN" ]; then
info "下载 frida-server {version} ({arch})..."
curl -sL "{download_url}" -o "$FRIDA_DIR/frida-server.xz"
xz -d -f "$FRIDA_DIR/frida-server.xz"
mv "$FRIDA_DIR/frida-server" "$FRIDA_BIN"
chmod +x "$FRIDA_BIN"
info "下载完成 ✓"
else
info "frida-server 已存在 ✓"
fi
# 停止旧进程
su -c "pkill -f frida-server" 2>/dev/null || true
su -c "pkill -f {disguise_name}" 2>/dev/null || true
sleep 1
# 复制到系统目录并启动
su -c "cp $FRIDA_BIN /data/local/tmp/{disguise_name}"
su -c "chmod 755 /data/local/tmp/{disguise_name}"
su -c "nohup /data/local/tmp/{disguise_name} -l 0.0.0.0:{port} &" 2>/dev/null
sleep 2
# 验证
if su -c "netstat -tlnp 2>/dev/null" | grep -q ":{port}"; then
info "✅ frida-server 已启动,监听 0.0.0.0:{port}"
info "📱 连接地址: $DEVICE_IP:{port}"
echo ""
echo "在服务器端执行:"
echo " frida -H $DEVICE_IP:{port} -n com.tencent.mm"
echo ""
else
error "frida-server 启动失败"
exit 1
fi
# 写入自启动
BOOT_SCRIPT="$HOME/.frida-autostart.sh"
cat > "$BOOT_SCRIPT" << 'AUTOSTART'
#!/data/data/com.termux/files/usr/bin/bash
sleep 10
su -c "nohup /data/local/tmp/{disguise_name} -l 0.0.0.0:{port} &" 2>/dev/null
AUTOSTART
chmod +x "$BOOT_SCRIPT"
# Termux:Boot 自启动
BOOT_DIR="$HOME/.termux/boot"
mkdir -p "$BOOT_DIR"
ln -sf "$BOOT_SCRIPT" "$BOOT_DIR/frida-autostart.sh"
info "开机自启已配置 ✓"
"""
return script
# ---- 免Root 模式Gadget 注入 ----
def generate_gadget_deploy_script(self, apk_path: str = "", port: int = 0) -> str:
"""
生成免 Root 模式部署脚本
原理:
- 将 frida-gadget.so 注入到微信 APK 的 lib 目录
- 配置 gadget 监听 TCP 端口
- 重新签名并安装修改后的 APK
注意:免 Root 模式需要重新安装微信,会丢失聊天记录
"""
if port == 0:
import random
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
version = self.config.frida_version
arch = self.config.frida_arch
gadget_name = f"frida-gadget-{version}-android-{arch}.so"
gadget_url = f"{self.FRIDA_DOWNLOAD_BASE}/{version}/{gadget_name}.xz"
gadget_config = json.dumps({
"interaction": {
"type": "listen",
"address": "0.0.0.0",
"port": port,
"on_load": "wait"
}
}, indent=2)
script = f"""#!/data/data/com.termux/files/usr/bin/bash
# ================================================================
# Frida Gadget 无线部署脚本 (免Root 模式)
# 版本: {version} | 架构: {arch} | 端口: {port}
# ================================================================
set -eo pipefail
RED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; NC='\\033[0m'
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
warn() {{ echo -e "${{YELLOW}}[WARN]${{NC}} $*"; }}
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
# 安装依赖
pkg install -y apksigner aapt zip unzip curl xz-utils 2>/dev/null || true
# 下载 frida-gadget
GADGET_DIR="$HOME/.frida-gadget"
mkdir -p "$GADGET_DIR"
GADGET_SO="$GADGET_DIR/libfrida-gadget.so"
if [ ! -f "$GADGET_SO" ]; then
info "下载 frida-gadget {version} ({arch})..."
curl -sL "{gadget_url}" -o "$GADGET_DIR/gadget.so.xz"
xz -d -f "$GADGET_DIR/gadget.so.xz"
mv "$GADGET_DIR/gadget.so" "$GADGET_SO"
info "下载完成 ✓"
fi
# Gadget 配置文件
cat > "$GADGET_DIR/libfrida-gadget.config.so" << 'GADGET_CFG'
{gadget_config}
GADGET_CFG
APK_PATH="{apk_path}"
if [ -z "$APK_PATH" ]; then
# 自动查找已安装的微信 APK
APK_PATH=$(pm path com.tencent.mm 2>/dev/null | head -1 | sed 's/package://')
if [ -z "$APK_PATH" ]; then
error "未找到微信 APK请指定路径"
exit 1
fi
fi
info "微信 APK: $APK_PATH"
# 解包
WORK_DIR="$GADGET_DIR/work"
rm -rf "$WORK_DIR"
mkdir -p "$WORK_DIR"
cp "$APK_PATH" "$WORK_DIR/base.apk"
cd "$WORK_DIR"
# 解压 APK
unzip -q base.apk -d apk_contents
# 注入 gadget
LIB_DIR="apk_contents/lib/{arch.replace('arm64', 'arm64-v8a').replace('arm', 'armeabi-v7a')}"
mkdir -p "$LIB_DIR"
cp "$GADGET_SO" "$LIB_DIR/libfrida-gadget.so"
cp "$GADGET_DIR/libfrida-gadget.config.so" "$LIB_DIR/libfrida-gadget.config.so"
# 修改 AndroidManifest.xml 加载 gadgetsmali 注入方式更可靠,这里用简化方案)
# 在 Application 类的 static 块中加载 libfrida-gadget.so
info "注入 frida-gadget 到 lib 目录..."
# 重新打包
cd apk_contents
zip -q -r ../patched.apk .
cd ..
# 签名
info "签名 APK..."
# 生成临时签名密钥
keytool -genkey -v -keystore "$GADGET_DIR/debug.keystore" \\
-storepass android -alias androiddebugkey -keypass android \\
-keyalg RSA -keysize 2048 -validity 10000 \\
-dname "CN=Debug, OU=Debug, O=Debug, L=Debug, S=Debug, C=US" 2>/dev/null || true
apksigner sign --ks "$GADGET_DIR/debug.keystore" \\
--ks-pass pass:android --key-pass pass:android \\
--out patched_signed.apk patched.apk
# 安装
warn "⚠️ 即将安装修改版微信,原版数据可能丢失"
warn " 建议先备份聊天记录"
info "安装修改版微信..."
pm install -r -d patched_signed.apk
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
info "✅ Gadget 注入完成"
info "📱 启动微信后,连接地址: $DEVICE_IP:{port}"
echo ""
echo "在服务器端执行:"
echo " frida -H $DEVICE_IP:{port} -n Gadget"
echo ""
"""
return script
# ---- 混合模式:自动检测 Root 并选择最佳方案 ----
def generate_auto_deploy_script(self, server_url: str, port: int = 0) -> str:
"""
生成自动检测部署脚本
逻辑:
1. 检测是否有 Root 权限
2. Root → 使用 frida-server 模式
3. 非 Root → 使用 frida-gadget 模式
4. 自动连接到 SDK 服务器
"""
if port == 0:
import random
port = random.randint(10000, 60000) if self.config.anti_detect else 27042
version = self.config.frida_version
arch = self.config.frida_arch
script = f"""#!/data/data/com.termux/files/usr/bin/bash
# ================================================================
# 工作手机 SDK - Frida 无线自动部署
# 版本: {version} | 端口: {port}
# 服务器: {server_url}
# ================================================================
set -eo pipefail
RED='\\033[0;31m'; GREEN='\\033[0;32m'; YELLOW='\\033[0;33m'; CYAN='\\033[0;36m'; NC='\\033[0m'
info() {{ echo -e "${{GREEN}}[INFO]${{NC}} $*"; }}
warn() {{ echo -e "${{YELLOW}}[WARN]${{NC}} $*"; }}
error() {{ echo -e "${{RED}}[ERROR]${{NC}} $*"; }}
DEVICE_ID=$(getprop ro.serialno 2>/dev/null || echo "dev-$(date +%s)")
DEVICE_IP=$(ip route get 1 2>/dev/null | awk '{{print $NF; exit}}' || hostname -I | awk '{{print $1}}')
FRIDA_PORT={port}
SERVER_URL="{server_url}"
info "设备ID: $DEVICE_ID"
info "设备IP: $DEVICE_IP"
info "Frida端口: $FRIDA_PORT"
# ---- 检测 Root ----
HAS_ROOT=false
if su -c "id" 2>/dev/null | grep -q "uid=0"; then
HAS_ROOT=true
info "✅ Root 权限可用 → 使用 frida-server 模式"
else
warn "⚠️ 无 Root 权限 → 使用 frida-gadget 模式"
fi
if [ "$HAS_ROOT" = true ]; then
# ========== Root 模式 ==========
FRIDA_DIR="$HOME/.frida-server"
mkdir -p "$FRIDA_DIR"
FRIDA_BIN="$FRIDA_DIR/fs-{version}"
if [ ! -f "$FRIDA_BIN" ]; then
info "下载 frida-server {version} ({arch})..."
curl -sL "{WirelessDeployer.FRIDA_DOWNLOAD_BASE}/{version}/frida-server-{version}-android-{arch}.xz" \\
-o "$FRIDA_DIR/fs.xz"
xz -d -f "$FRIDA_DIR/fs.xz"
mv "$FRIDA_DIR/fs" "$FRIDA_BIN"
chmod +x "$FRIDA_BIN"
fi
# 停止旧进程
su -c "pkill -f frida-server" 2>/dev/null || true
sleep 1
# 启动
su -c "cp $FRIDA_BIN /data/local/tmp/frida-server"
su -c "chmod 755 /data/local/tmp/frida-server"
su -c "nohup /data/local/tmp/frida-server -l 0.0.0.0:$FRIDA_PORT &" 2>/dev/null
sleep 2
if su -c "netstat -tlnp 2>/dev/null" | grep -q ":$FRIDA_PORT"; then
info "✅ frida-server 已启动"
else
error "frida-server 启动失败"
exit 1
fi
DEPLOY_MODE="root_server"
else
# ========== 免Root 模式 ==========
info "免Root 模式需要重新安装修改版微信"
info "请先运行 gadget 部署脚本"
DEPLOY_MODE="gadget"
fi
# ---- 向服务器注册 ----
info "向服务器注册设备..."
REGISTER_DATA=$(cat << EOF
{{
"device_id": "$DEVICE_ID",
"ip": "$DEVICE_IP",
"frida_port": $FRIDA_PORT,
"mode": "$DEPLOY_MODE",
"android_version": "$(getprop ro.build.version.release)",
"device_model": "$(getprop ro.product.model)",
"frida_version": "{version}"
}}
EOF
)
# 通过 HTTP API 注册
API_URL=$(echo "$SERVER_URL" | sed 's|ws://|http://|' | sed 's|wss://|https://|' | sed 's|/ws/device||')
curl -s -X POST "$API_URL/api/v3/frida/register" \\
-H "Content-Type: application/json" \\
-d "$REGISTER_DATA" || warn "服务器注册失败,稍后重试"
info "🎉 部署完成!"
info "连接信息: $DEVICE_IP:$FRIDA_PORT ($DEPLOY_MODE)"
"""
return script
# ---- 局域网设备发现 ----
def discover_devices(self, subnet: str = "", port: int = 27042,
timeout: float = 2.0) -> List[Dict[str, Any]]:
"""
扫描局域网内运行 frida-server 的设备
Args:
subnet: 子网前缀,如 "192.168.1",空则自动检测
port: Frida 端口
timeout: 每个 IP 的超时时间
"""
if not subnet:
subnet = self._detect_subnet()
if not subnet:
return []
found = []
logger.info(f"扫描子网 {subnet}.0/24 端口 {port}...")
def _scan_ip(ip: str):
if self._check_port(ip, port, timeout):
try:
import frida
mgr = frida.get_device_manager()
device = mgr.add_remote_device(f"{ip}:{port}")
params = device.query_system_parameters()
found.append({
"ip": ip,
"port": port,
"name": device.name,
"os": params.get("os", {}).get("id", "unknown"),
"arch": params.get("arch", "unknown"),
})
logger.info(f" 发现设备: {ip}:{port} ({device.name})")
except Exception:
# 端口开放但不是 Frida
pass
threads = []
for i in range(1, 255):
ip = f"{subnet}.{i}"
t = threading.Thread(target=_scan_ip, args=(ip,))
t.daemon = True
threads.append(t)
t.start()
for t in threads:
t.join(timeout=timeout + 1)
logger.info(f"扫描完成,发现 {len(found)} 个设备")
return found
# ---- 健康检查 ----
def start_health_check(self, interval: int = 30):
"""启动后台健康检查线程"""
self._running = True
self._health_thread = threading.Thread(
target=self._health_check_loop,
args=(interval,),
daemon=True,
)
self._health_thread.start()
logger.info(f"健康检查已启动,间隔 {interval}s")
def stop_health_check(self):
"""停止健康检查"""
self._running = False
if self._health_thread:
self._health_thread.join(timeout=5)
logger.info("健康检查已停止")
def _health_check_loop(self, interval: int):
"""健康检查循环"""
while self._running:
for device_id, conn in list(self.connections.items()):
if conn.status == "connected":
if not self._check_port(conn.ip, conn.frida_port, timeout=3):
conn.status = "disconnected"
conn.wechat_attached = False
logger.warning(f"设备离线: {device_id}")
else:
conn.last_heartbeat = datetime.now().isoformat()
elif conn.status == "disconnected" and conn.reconnect_count < 10:
# 尝试自动重连
result = self.connect_device(device_id)
if result.get("success"):
logger.info(f"设备自动重连成功: {device_id}")
time.sleep(interval)
# ---- 工具方法 ----
@staticmethod
def _check_port(ip: str, port: int, timeout: float = 3.0) -> bool:
"""检查 TCP 端口是否可达"""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip, port))
sock.close()
return result == 0
except Exception:
return False
@staticmethod
def _detect_subnet() -> str:
"""自动检测本机子网"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
parts = ip.split(".")
return ".".join(parts[:3])
except Exception:
return ""
def get_status(self) -> Dict[str, Any]:
"""获取部署器状态"""
connected = sum(1 for c in self.connections.values() if c.status == "connected")
total = len(self.connections)
return {
"total_devices": total,
"connected_devices": connected,
"disconnected_devices": total - connected,
"health_check_running": self._running,
"config": {
"frida_version": self.config.frida_version,
"frida_arch": self.config.frida_arch,
"anti_detect": self.config.anti_detect,
},
"devices": self.list_devices(),
}
# ============================================================
# § 3 全局单例
# ============================================================
wireless_deployer = WirelessDeployer()