# WeChat 息屏自动发送方案汇总 > 设备:Pixel 10 Pro Fold (Android 15) > 微信:8.0.72 > 目标:息屏状态下成功发送消息给 yibooo > 时间:2026-05-23 --- ## 一、整体架构(当前部署) ``` 息屏检测 消息生成 消息发送 ----------> 三种方案并行尝试 ----------> 1. sqlcipher DB 轮询 ←── 检测新消息 ──→ hermes API ←── 生成 AI 回复 2. termux-notification-list(息屏兜底唤醒) 3. LSPosed hook(WCDB insert 监控) ``` **当前部署路径**:`/data/data/com.termux/files/home/hermes-wechat/` --- ## 二、已尝试的发送方案(5 种) ### 方案 1:LSPosed Xposed 模块 + `am broadcast` **原理**: - 安装 LSPosed 模块到 WeChat 进程 - 模块注册 BroadcastReceiver,监听 `com.hermes.wechat.SEND_MSG` - 收到广播后,反射调用微信内部 API:`u01.r1.a(talker) → q1.e(content) → q1.a() → m1.a()` - 将 NetScene 提交到主线程执行 **代码位置**: - Java 源码:`wechat_plain/scripts/lsposed-wechat-sendmsg/app/src/main/java/com/hermes/wechat/xposed/HermesWeChatModule.java` - Smali(已编译):`/tmp/lsposed-mod/smali/com/hermes/wechat/xposed/HermesWeChatModule.smali` - 签名 APK:`/tmp/lsposed-mod/dist/lsposed-wechat-sendmsg.apk` **已做的 bypass**: - `ExtContentProviderBase.h()` → return 1(auth bypass) - `ExtContentProviderBase.b()` → return true(login bypass) - `AESUtil.b()` / `AESUtil.a()` 跳过加密 - NetScene submit 改为 `Handler(Looper.getMainLooper()).post()` 主线程执行 **测试结果**: - 亮屏:`am broadcast` 可以执行,但消息未成功写入 DB(怀疑模块未正确 hook 或 `m1.a()` 不是真正的发送方法) - 息屏:`am broadcast` 可能被系统拦截,未验证 - **状态:❌ 未成功** **失败原因分析**: 1. 模块重新签名安装后,LSPosed 数据库中路径未更新,可能未正确加载 2. `u01.m1.a()` 返回 true 但无 DB 写入,说明 `a()` 可能不是真正的提交方法 3. Android 15 后台广播限制可能阻止息屏时的 `am broadcast` --- ### 方案 2:Frida 直接调用微信内部 API **原理**: - Frida attach 到 WeChat 主进程,直接执行 JavaScript 调用微信内部类 - 调用链:`u01.r1.a("yibooo") → q1.e("content") → q1.a() → m1.a()` - 使用 `Java.scheduleOnMainThread()` 确保在主线程执行 **Frida 脚本**: - `/tmp/send_msg3.js` — 基本发送脚本(主线程提交) - `/tmp/send_msg4.js` — 测试 `q1.b()` 作为替代发送方法(尚未运行) - `/tmp/inspect_all.js` — 类方法枚举工具 **已发现的关键类签名**: ``` u01.r1.a(java.lang.String) → u01.q1 # 创建 builder u01.q1.e(java.lang.String) → u01.q1 # 设置消息内容(链式) u01.q1.g(java.lang.String) → u01.q1 # 可能是设置 talker u01.q1.h(int) → u01.q1 # 可能是设置 type u01.q1.a() → u01.m1 # build NetScene u01.q1.b() → void # ??? 可能是真正发送 u01.q1.d(u01.q1, qw5.l, int, Object) → void # 静态方法,可能是 dispatch u01.m1.a() → boolean # 返回 true 但无实际效果 ``` **测试结果**: - `m1.a()` 在主线程执行,返回 `true`,但 DB 中没有新的 `isSend=1` 记录 - msgId 846 之后无新消息写入 - **状态:❌ 亮屏也未成功** **关键疑点**: `u01.m1.a()` 可能不是 `doScene()`/`dispatch()`,而是: - 预检查/校验方法 - 状态查询方法 - 需要配合其他参数才能触发真正的发送 **下一步待验证**: 1. `u01.q1.b()` — 返回 void,更像执行方法 2. `u01.q1.d()` — 静态方法,参数 `(q1, l, int, Object)` 可能是 dispatcher 3. hook `u01.m1.a()` 内部实现,追踪它到底调用了什么 4. hook WCDB `insert()`,确认调用 API 后是否有任何 DB 写入尝试 --- ### 方案 3:`input text` + `input keyevent 66` 模拟键盘输入 **原理**: - 使用 Android `input text "消息内容"` 向当前焦点窗口输入文本 - `input keyevent 66` 模拟回车发送 **测试结果**: - 亮屏:简单英文数字可用,某些字符导致 `NullPointerException` - 息屏:无法输入到没有焦点的前台窗口 - **状态:❌ 不适用** **失败原因**: 1. 微信聊天窗口需要是前台焦点才能接收 `input text` 2. 息屏状态下系统不会分发输入事件到应用 3. 特殊字符(中文、标点)容易崩溃 --- ### 方案 4:直接启动 ChattingUI (`am start -n`) **原理**: - `am start -n com.tencent.mm/.ui.chatting.ChattingUI` 直接打开聊天窗口 - 然后配合 `input text` 发送 **测试结果**: - `am start` 抛出 `SecurityException: Not exported` - ChattingUI 未在 manifest 中声明 exported=true - **状态:❌ 无法启动** --- ### 方案 5:`content insert` → ContentProvider **原理**: - 通过 `content insert --uri content://com.hermes.wechat.xposed.provider/send` 发送 - 绕过了广播,直接走 ContentProvider **代码位置**:`android-wechat-sender.js::sendTextMessage()` **测试结果**: - 未实际测试(依赖 LSPosed 模块的 ContentProvider) - 模块的 Provider 可能需要同样 bypass auth - **状态:❓ 未测试** --- ## 三、消息检测方案(2 种,均可用) ### 检测 1:sqlcipher DB 轮询 ✅ **原理**: - 直接用 sqlcipher 打开 `EnMicroMsg.db`(key: `e03ce81`) - 轮询 `message` 表,查 `msgId > lastMsgId AND isSend = 0` **代码位置**:`android-wechat-tracker-sqlcipher.js` **已修复的问题**: - temp 文件路径从 `/tmp/` 改为 `/data/data/com.termux/files/usr/tmp/`(Termux 无 `/tmp` 权限) **状态**:✅ **工作正常**,可检测新消息 ### 检测 2:termux-notification-list 息屏兜底 ✅ **原理**: - 每 3 秒执行 `termux-notification-list` - 过滤 `packageName === 'com.tencent.mm'` 的通知 - 检测到通知后 → `input keyevent 26` 点亮屏幕 → `am force-stop` + `am start` 重启微信 → 等待前台确认 **代码位置**:`auto-reply.js::pollNotifications()` **已修复的问题**: - `pkgName` → `packageName` 字段名修正 - 添加 `input keyevent 26` 唤醒屏幕(息屏时 `am start` 不执行) - 添加 `ensureWechatForeground()` 轮询确认前台 + 模拟按键保持活跃 **状态**:✅ **检测+唤醒工作正常**,但发送卡住了 --- ## 四、当前代码文件状态 | 文件 | 位置 | 状态 | 说明 | |------|------|------|------| | `auto-reply.js` | `server/services/wechat/` | ✅ 已部署 | 主逻辑,通知兜底+sqlcipher轮询+hermes调用,但发送调用的是 `sendTextMessageBroadcast()` | | `android-wechat-tracker-sqlcipher.js` | `server/services/wechat/` | ✅ 已部署 | sqlcipher DB 轮询,检测新消息正常 | | `android-wechat-sender.js` | `server/services/wechat/` | ✅ 已部署 | 提供 `sendTextMessageBroadcast()` 和 `sendTextMessage()`,但底层依赖 LSPosed 模块 | | `HermesWeChatModule.java` | `wechat_plain/scripts/lsposed-wechat-sendmsg/...` | ✅ 已修改 | 主线程提交 NetScene,但 APK 重新签名后 LSPosed 可能未正确加载 | | `lsposed-wechat-sendmsg.apk` | `/tmp/lsposed-mod/dist/` | ⚠️ 已编译 | 新签名,LSPosed DB 中路径可能未更新 | | Frida 脚本 | `/tmp/send_msg*.js` | ✅ 可用 | `send_msg3.js` 亮屏测试未成功,`send_msg4.js` 待测试 `q1.b()` | --- ## 五、核心卡点总结 ### 卡点 1:为什么 `u01.m1.a()` 返回 true 但消息没发出去? **现象**: - Frida 调用 `netScene.a()` 返回 `true` - DB 中没有新的 `isSend=1` 记录 - 没有异常抛出 **假设**: 1. `a()` 不是 `doScene()`,而是其他方法(如 `check()` / `preSend()`) 2. 真正的发送方法是 `u01.q1.b()` 或 `u01.q1.d()` 3. NetScene 需要额外的初始化(如 `MsgInfo` 的某些字段未设置) 4. 微信在息屏/后台时 NetSceneDispatcher 被冻结,即使调用也不执行 **待验证**: - [ ] 测试 `u01.q1.b()` 是否能触发 DB 写入 - [ ] 测试 `u01.q1.d()` 静态方法 - [ ] hook `u01.m1.a()` 内部,看其调用链 - [ ] hook WCDB insert,确认 API 调用后是否有任何 DB 操作 ### 卡点 2:息屏状态下后台执行限制 **现象**: - Android 15 对后台 Activity 启动、后台广播有严格限制 - `am start` 在息屏锁定时不会立即执行 - 需要先 `input keyevent 26` 点亮屏幕 **当前 workaround**: - 检测到通知 → 唤醒屏幕 → force-stop + start 微信 → 轮询确认前台 - 这个流程已验证可以工作 **剩余风险**: - 即使微信在前台,如果 NetSceneDispatcher 在息屏时被系统挂起,消息仍可能无法发送 - 需要确认亮屏后微信网络层是否完全恢复 ### 卡点 3:LSPosed 模块加载问题 **现象**: - 重新编译签名 APK 后安装 - LSPosed 管理器中显示已启用,但日志中没有模块 hook 的输出 - 可能原因:新签名导致 LSPosed 数据库中路径/签名不匹配 **已尝试**: - 直接修改 `/data/adb/lspd/config/modules_config.db` 更新路径 - 重启 Zygote (`setprop ctl.restart zygote`) 和 lspd **状态**:⚠️ 不确定模块是否真正加载 --- ## 六、下一步建议(优先级排序) ### P0:确认真正发送方法(Frida 验证) 用 Frida 在亮屏状态下测试以下方法,找到真正能触发 DB 写入的调用: ```javascript // 测试 1:q1.b() var builder = r1.a("yibooo"); builder.e("test msg"); builder.b(); // 返回 void,可能是真正发送 // 测试 2:q1.d() 静态方法 q1.d(builder, null, 0, null); // 参数猜测 // 测试 3:hook m1.a() 内部 // 看 m1.a() 到底调用了什么,追踪其调用链 ``` ### P1:验证亮屏发送 只有在亮屏状态下能稳定发送,才有意义测试息屏。 当前亮屏都无法发送,说明调用链不正确。 ### P2:息屏发送验证 亮屏发送成功后: 1. 息屏 2. 等待 5 秒 3. Frida 执行相同脚本 4. 检查 DB 是否有写入 ### P3:修复 LSPosed 模块(备用方案) 如果 Frida 方案因息屏限制不可行,回到 LSPosed 模块: 1. 确认模块正确加载(查看 LSPosed 日志) 2. 修正 `m1.a()` 为正确的方法调用 3. 验证息屏时广播是否能被接收 ### P4:集成到 auto-reply.js 发送方法确定后: 1. 修改 `auto-reply.js` 中的发送调用 2. 确保息屏唤醒流程与发送流程衔接 3. 测试完整闭环:收到消息 → AI 回复 → 成功发送 --- ## 七、附录:Frida 调试命令速查 ```bash # 连接设备 adb connect 192.168.x.x:5555 # 启动微信并 Frida 注入 frida -U -f com.tencent.mm -l /tmp/send_msg3.js --no-pause # attach 到已运行的微信 frida -U -n "com.tencent.mm" -l /tmp/send_msg3.js # 查看微信进程 PID adb shell ps | grep com.tencent.mm # 息屏/亮屏 adb shell input keyevent 26 # 查询 DB 最新发送消息 adb shell su -c "sqlcipher /data/data/.../EnMicroMsg.db 'PRAGMA key=e03ce81; SELECT msgId, talker, content FROM message WHERE isSend=1 ORDER BY msgId DESC LIMIT 3;'" ``` --- ## 八、关键发现记录 | 发现 | 时间 | 影响 | |------|------|------| | WeChat 8.0.72 使用 u01.r1/q1/m1 作为发送 API | 2026-05-23 | 确认了调用链 | | `m1.a()` 返回 true 但无 DB 写入 | 2026-05-23 | 怀疑 `a()` 不是真正发送方法 | | `termux-notification-list` 字段是 `packageName` 非 `pkgName` | 2026-05-23 | 修复了息屏检测 | | Termux 无法写 `/tmp`,需用 `/data/data/com.termux/files/usr/tmp/` | 2026-05-23 | 修复了 sqlcipher 查询 | | `input keyevent 26` 可在息屏时点亮屏幕 | 2026-05-23 | 实现了息屏唤醒 | | ChattingUI 未 exported,无法 `am start` | 2026-05-23 | 排除了直接启动聊天窗口方案 | | `u01.q1.b()` 和 `d()` 可能是真正发送方法 | 2026-05-23 | 待验证 |