wechat-screen-off-send-attempts.md
11.7 KB
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可能被系统拦截,未验证 - 状态:❌ 未成功
失败原因分析:
- 模块重新签名安装后,LSPosed 数据库中路径未更新,可能未正确加载
u01.m1.a()返回 true 但无 DB 写入,说明a()可能不是真正的提交方法- 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(),而是:
- 预检查/校验方法
- 状态查询方法
- 需要配合其他参数才能触发真正的发送
下一步待验证:
u01.q1.b()— 返回 void,更像执行方法u01.q1.d()— 静态方法,参数(q1, l, int, Object)可能是 dispatcher- hook
u01.m1.a()内部实现,追踪它到底调用了什么 - hook WCDB
insert(),确认调用 API 后是否有任何 DB 写入尝试
方案 3:input text + input keyevent 66 模拟键盘输入
原理:
- 使用 Android
input text "消息内容"向当前焦点窗口输入文本 input keyevent 66模拟回车发送
测试结果:
- 亮屏:简单英文数字可用,某些字符导致
NullPointerException - 息屏:无法输入到没有焦点的前台窗口
- 状态:❌ 不适用
失败原因:
- 微信聊天窗口需要是前台焦点才能接收
input text - 息屏状态下系统不会分发输入事件到应用
- 特殊字符(中文、标点)容易崩溃
方案 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记录 - 没有异常抛出
假设:
a()不是doScene(),而是其他方法(如check()/preSend())- 真正的发送方法是
u01.q1.b()或u01.q1.d() - NetScene 需要额外的初始化(如
MsgInfo的某些字段未设置) - 微信在息屏/后台时 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 写入的调用:
// 测试 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:息屏发送验证
亮屏发送成功后:
- 息屏
- 等待 5 秒
- Frida 执行相同脚本
- 检查 DB 是否有写入
P3:修复 LSPosed 模块(备用方案)
如果 Frida 方案因息屏限制不可行,回到 LSPosed 模块:
- 确认模块正确加载(查看 LSPosed 日志)
- 修正
m1.a()为正确的方法调用 - 验证息屏时广播是否能被接收
P4:集成到 auto-reply.js
发送方法确定后:
- 修改
auto-reply.js中的发送调用 - 确保息屏唤醒流程与发送流程衔接
- 测试完整闭环:收到消息 → AI 回复 → 成功发送
七、附录:Frida 调试命令速查
# 连接设备
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 | 待验证 |