Name Last Update
..
AGENTS.md Loading commit data...
CLAUDE.md Loading commit data...
HANDOFF.md Loading commit data...
HANDOFF_PROMPTS.md Loading commit data...
README.md Loading commit data...
SKILL.md Loading commit data...
android-wechat-sender-frida.js Loading commit data...
android-wechat-sender.js Loading commit data...
android-wechat-tracker-sqlcipher.js Loading commit data...
android-wechat-tracker-sqlite.js Loading commit data...
android-wechat-tracker.js Loading commit data...
auto-reply.js Loading commit data...
deploy-to-phone.sh Loading commit data...
extract-wechat-avatars.py Loading commit data...
index.js Loading commit data...
keepalive.sh Loading commit data...
scan-msginfo.js Loading commit data...
test-phone.js Loading commit data...
wechat-contacts.json Loading commit data...
wechat-contacts.md Loading commit data...
wechat-importer.js Loading commit data...
wechat-reader.js Loading commit data...
wechat-sender.js Loading commit data...
wechat-tracker.js Loading commit data...

README.md

WeChat 交互服务 — 发送与跟踪

本目录提供从 Mac 和 Pixel 手机 两个端点与微信进行交互的能力:发送消息、实时跟踪收发消息。

底层依赖自研的 Android 模块 HermesWeChatSendMsg(位于 wechat_plain/scripts/lsposed-wechat-sendmsg):

  • 发送:模块内的 HermesAccessibilityService 监听广播 com.hermes.wechat.SEND_MSG,触发后纯 UI 自动化——启动微信 → 搜索 displayName → 进入聊天 → 设置 EditText → 点发送按钮。这是当前唯一能把消息真正写入 message 表的路径。
  • 跟踪:通过 sqlcipher 轮询解密后的 EnMicroMsg.db(默认 key e03ce81),或备用方式 tail LSPosed 日志。

⚠️ 模块同名的 LSPosed 入口类 HermesWeChatModule(反射 NetSceneSendMsg)以及 Frida daemon 队列(android-wechat-sender-frida.js)已确认无法把消息真正发出去——前者 Send result: true 是误报、后者注入会触发微信反调试崩溃。所有 sender 调用最终都汇聚到 am broadcast → A11y service 这一条路径。


整体架构

┌─────────────────────────────────────────────────────────────────────┐
│                         用户 / hermes agent                          │
│                                                                      │
│   Mac 端                        │   Pixel 手机端 (Termux/Debian)     │
│   ┌──────────────────────┐      │   ┌──────────────────────────┐     │
│   │ wechat-sender.js     │      │   │ android-wechat-sender.js │     │
│   │ wechat-tracker.js    │ adb  │   │ android-wechat-tracker.js│     │
│   └──────────┬───────────┘──────┘   └────────────┬─────────────┘     │
│              │                                   │                   │
│              │        adb shell su -c            │   su -c           │
│              │        "content insert ..."        │   "content ..."   │
│              │        "cat /data/adb/lspd/..."    │   "tail -f ..."   │
│              │                                   │                   │
│              ▼                                   ▼                   │
│   ┌──────────────────────────────────────────────────────┐          │
│   │              Pixel 10 Pro Fold (Rooted)               │          │
│   │  ┌────────────────────────────────────────────────┐   │          │
│   │  │  HermesWeChatSendMsg (LSPosed/Vector Module)   │   │          │
│   │  │  ┌──────────────┐      ┌──────────────────┐    │   │          │
│   │  │  │ ContentProvider│─────▶│ Broadcast Intent │    │   │          │
│   │  │  │   (Provider)   │      │ com.hermes.wechat│    │   │          │
│   │  │  └──────────────┘      │     .SEND_MSG     │    │   │          │
│   │  │         │              └────────┬───────────┘    │   │          │
│   │  │         │                       │                │   │          │
│   │  │         ▼                       ▼                │   │          │
│   │  │  ┌───────────────────────────────────────────┐   │   │          │
│   │  │  │         微信主进程 (com.tencent.mm)        │   │   │          │
│   │  │  │  ┌─────────────────────────────────────┐  │   │   │          │
│   │  │  │  │  Hook 点:                            │  │   │   │          │
│   │  │  │  │  • ExtContentProviderBase.h() → 1   │  │   │   │          │
│   │  │  │  │  • ExtContentProviderBase.b() → true│  │   │   │          │
│   │  │  │  │  • AESUtil.b/a → 明文 wxid          │  │   │   │          │
│   │  │  │  │  • MsgInfo.c1() → 消息日志           │  │   │   │          │
│   │  │  │  └─────────────────────────────────────┘  │   │   │          │
│   │  │  │  ┌─────────────────────────────────────┐  │   │   │          │
│   │  │  │  │  广播接收器 → NetSceneSendMsg       │  │   │   │          │
│   │  │  │  │  u01.r1.a() → Builder → m1.a()      │  │   │   │          │
│   │  │  │  └─────────────────────────────────────┘  │   │   │          │
│   │  │  └───────────────────────────────────────────┘   │   │          │
│   │  └────────────────────────────────────────────────┘   │          │
│   └──────────────────────────────────────────────────────┘          │
└─────────────────────────────────────────────────────────────────────┘

模块原理

主路径:A11y UI 自动化(实际工作的)

外部请求
    │
    ├── content insert ──▶ ContentProvider ──▶ Broadcast (com.hermes.wechat.SEND_MSG)
    │                                              │
    └── am broadcast ────────────────────────────┘
                                                   │
                                                   ▼
                              HermesAccessibilityService (BIND_ACCESSIBILITY_SERVICE)
                                                   │
                                                   ▼
                              1. startActivity(LauncherUI, FLAG_ACTIVITY_CLEAR_TASK)
                                                   │
                                                   ▼
                              2. 等 3.5s 后判断是否已在目标聊天页
                                 (id=obn 的标题 == toUserDisplayName?)
                                                   │
                          ┌─────────────否─────────┴────────────是─────────┐
                          ▼                                                ▼
                  3a. 点击搜索按钮 (id=jha)                          直接到步骤 5
                  3b. 在搜索框 (id=d98) ACTION_SET_TEXT
                      = toUserDisplayName (微信 nickname,必须是
                      "火星人" 而不是 "yibooo")
                  3c. 在搜索结果中找匹配文本的 clickable ancestor 并点击
                          │
                          ▼
                  4. 等待聊天界面加载 (id=bkk 容器存在)
                          │
                          ▼
                  5. 在 bkk 内的 EditText ACTION_SET_TEXT = content
                          │
                          ▼
                  6. 点击发送按钮 (id=bql,或 fallback 找文本 "发送")
                          │
                          ▼
                  微信走自己正常的发送流程 → 服务器 → message 表 isSend=1

关键 UI 节点 ID(WeChat 8.0.72):

用途 resource-id
主界面搜索按钮 com.tencent.mm:id/jha
搜索输入框 com.tencent.mm:id/d98
聊天输入框容器(FrameLayout,内含 EditText) com.tencent.mm:id/bkk
发送按钮 com.tencent.mm:id/bql
聊天页标题(验证进入了正确的会话) com.tencent.mm:id/obn

代码位置:wechat_plain/scripts/lsposed-wechat-sendmsg/app/src/main/java/com/hermes/wechat/xposed/accessibility/HermesAccessibilityService.java

已废弃路径 1:LSPosed 反射 NetSceneSendMsg

模块同名入口类 HermesWeChatModule 通过 hook 反射调用:

目标类 方法 作用
ExtContentProviderBase h() → return 1 绕过 appId + UID + 签名校验
ExtContentProviderBase b() → return true 绕过登录状态检查
AESUtil b(long) → 返回原值 跳过联系人 ID 加密
AESUtil a(String) → 返回原值 跳过联系人 ID 解密
MsgInfo (f9) c1(String) 监控消息内容变更,记录日志

调用链 u01.r1.a(toUser) → builder.e(content) → builder.a() → m1.a()。sendTextMessageDirect 同步返回 true 然后在主线程异步提交 NetScene——但目前主线程提交触发 NullPointerException 被 swallow,消息不进 message 表。日志特征:

[HermesWX] Send result: true
[HermesWX] NetScene submit on main thread failed: null

注:这些 hook(auth bypass / AESUtil)对 A11y 路径没有影响,是否保留无关紧要。

已废弃路径 2:Frida daemon 队列

android-wechat-sender-frida.js 写入 ~/.frida_queue/queue.jsonl,由常驻微信进程的 frida daemon 读取并反射调用 u01.r1.a。Frida 17 + 微信 8.0.72 注入会触发反调试,进程崩溃。已不在生产使用。

跨进程通信

HermesAccessibilityService 运行在模块 APK 自身进程,由系统的 AccessibilityManager 绑定。外部进程通过 Android 广播(com.hermes.wechat.SEND_MSG)触发:

hermes / adb shell content / am broadcast
        │
        ▼
  Android 系统 broadcast manager
        │
        ▼
  HermesAccessibilityService (模块进程)
        │
        │  AccessibilityNodeInfo / performAction
        ▼
  微信主进程的 UI (作为前台被自动化点击)
        │
        ▼
  微信走自己正常的发送流程 → 服务器

A11y service 必须由用户在「设置 → 辅助功能」中手动启用,或者用 root 通过 settings put secure enabled_accessibility_services 启用。重启 / 系统更新 / 应用更新都可能导致它被关闭。


文件清单

文件 用途 运行端
wechat-sender.js 发送消息(adb) Mac
wechat-tracker.js 跟踪消息(adb) Mac
android-wechat-sender.js 主推 发送消息(am broadcast → A11y service UI 自动化) Pixel
android-wechat-tracker-sqlcipher.js 主推 跟踪消息(sqlcipher 轮询 DB) Pixel
android-wechat-tracker.js 备用跟踪消息(tail -f LSPosed 日志) Pixel
android-wechat-sender-frida.js ⚠️ 已废弃:Frida daemon 队列。Frida 17 + 微信 8.0.72 注入会崩溃,未在生产使用 Pixel
auto-reply.js Pixel 自动回复 plugin(hermes-agent API) Pixel
index.js 统一入口,自动检测环境 通用
test-phone.js 手机端一键测试 Pixel
deploy-to-phone.sh 推送到手机 Mac
SKILL.md hermes agent skill 文档 通用
wechat-reader.js 读取解密后的 SQLite DB Mac
wechat-importer.js 导入联系人到知识图谱 Mac

前置条件

1. 手机环境

  • Pixel 10 Pro Fold(或其他已 root Android 设备)
  • Magisk 30.7+,Zygisk 已启用
  • LSPosed/Vector 2.0+ 框架已安装
  • 微信 8.0.72
  • Termux 或 Debian chroot(hermes agent 运行环境)

2. 模块安装

# 在 Mac 上编译(详见 lsposed-wechat-sendmsg/README.md)
cd lsposed-wechat-sendmsg
./gradlew assembleRelease

# 签名并安装
adb install app/build/outputs/apk/release/app-release.apk

# 在手机上:
# 1. 打开 Vector Manager → 模块 → 启用 HermesWeChatSendMsg
# 2. 作用域中选择「微信(com.tencent.mm)」
# 3. 强行停止微信,重新打开

3. 启用 AccessibilityService(必须)

# 启用 A11y service
adb shell "su -c 'settings put secure enabled_accessibility_services com.hermes.wechat.xposed/com.hermes.wechat.xposed.accessibility.HermesAccessibilityService; settings put secure accessibility_enabled 1'"

# 验证已 bind
adb shell "dumpsys accessibility | grep -A3 Hermes"
# 应看到: Enabled services:{{com.hermes.wechat.xposed/.../HermesAccessibilityService}}

# 验证模块(可选,对 A11y 路径不是必需)
adb shell "su -c \"logcat -s HermesA11y:* -d -t 30\""

A11y service 一旦关闭(重启 / 系统更新 / 用户在设置中关闭),发送就会失效。enabled_accessibility_services = null 是最常见的故障原因。


Mac 端使用

发送消息

const { sendTextMessage, sendBatch } = require('./wechat-sender');

// 单条
const r = await sendTextMessage('filehelper', 'Hello from Mac');
console.log(r); // { ok: true, uri: 'content://.../send/0' }

// 批量
await sendBatch([
  { toUser: 'filehelper', content: 'msg1' },
  { toUser: 'wxid_xxx', content: 'msg2' },
], 2000);

跟踪消息

const { WechatTracker } = require('./wechat-tracker');
const tracker = new WechatTracker();

tracker.on('message', msg => {
  console.log(`[${msg.type}] ${msg.talker}: ${msg.content}`);
});

tracker.startPolling(3000);  // 轮询模式
// tracker.startStream();     // 流式模式(更实时)

手机端使用

一键部署

cd server/services/wechat
./deploy-to-phone.sh /data/data/com.termux/files/home/hermes-wechat

发送消息

const { sendTextMessage, sendTextMessageBroadcast } = require('./android-wechat-sender');

// 方式1:ContentProvider(与 Mac API 一致)
await sendTextMessage('filehelper', 'Hello from Pixel');

// 方式2:直接广播(更可靠)
await sendTextMessageBroadcast('wxid_xxx', 'Test');

跟踪消息

const { AndroidWechatTracker } = require('./android-wechat-tracker');
const tracker = new AndroidWechatTracker();

tracker.on('message', msg => console.log(msg));
tracker.startPolling(3000);

运行测试

cd /data/data/com.termux/files/home/hermes-wechat
node test-phone.js filehelper "hello from pixel"

统一入口

const wechat = require('./index');

// 自动检测环境:Mac 用 adb,Android 用 su
console.log(wechat.platform); // 'mac' | 'android'

// 发送
await wechat.sendTextMessage('filehelper', 'test');

// 跟踪
const tracker = new wechat.WechatTracker();
tracker.startPolling(3000);

API 参考

发送

函数 参数 返回值 说明
sendTextMessage(toUser, content) string, string {ok, uri, error} 通过 ContentProvider
sendTextMessageBroadcast(toUser, content) string, string {ok, error} 直接广播
queryStatus() — {status, error} 查询发送结果
sendBatch(messages, delayMs) Array, number Array 批量发送

跟踪

方法 说明
fetchRecent(n) 拉取最近 N 条
startPolling(ms) 轮询模式
startStream() 流式模式(tail -f)
stopPolling() / stopStream() 停止

事件

事件名 数据
message {type, talker, msgType, msgId, content, raw, timestamp}
sendResult {success, raw}
sendRequest {toUser, raw}
error Error
close —

故障排查

模块层面

现象 原因 解决
消息没进 message 表 A11y service 未启用 adb shell "settings get secure enabled_accessibility_services",为 null 就用上面的命令重新启用
A11y 日志显示 Root package=com.termux 然后退出 广播触发时微信不在前台,启动 intent 被前台 termux 抢占 broadcast 前先 adb shell am start -n com.tencent.mm/.ui.LauncherUI
Search button not found displayName 字段空 / 错误,或当前页面不是 LauncherUI broadcast 必须传 --es toUserDisplayName,且值必须是 WeChat nickname(如 火星人),不能是 wxid
No clickable search result found nickname 写错 / 联系人不在好友列表 在微信里直接搜索这个 nickname 验证是否能找到
Send result: true 但消息没发出去 这是 LSPosed 反射路径的误报,正常 看 A11y 日志而不是 HermesWX 日志
模块未出现在 LSPosed Manager APK 未签名 重新签名安装

Mac 端

现象 原因 解决
adb: no devices 手机未连接 检查 USB 调试和 adb 授权
adb exit 255 未 root 确认 Magisk 已授权 adb shell 的 su

手机端

现象 原因 解决
exit 1: permission denied 未 root 检查 Magisk root 状态
content: not found 不在 Android 环境 确保在 Termux 或指定 /system/bin/content
发送成功但对方没收到 微信没在前台 确保微信主进程存活
跟踪不到消息 日志级别不够 Vector Manager → 日志 → 设置详细级别
Background execution not allowed Android 10+ 限制 使用 content insert 替代 am broadcast

安全提示

  • 本模块仅用于个人设备上的自动化测试和学习研究
  • 微信账号存在被封风险,建议在测试用小号上操作
  • 不要在群聊中高频发送消息,避免触发风控

相关文档