WeChat 自动回复项目 — 完整状态报告
日期:2026-05-22 设备:Pixel 10 Pro Fold 微信版本:8.0.72 环境:Termux + LSPosed (Zygisk) + Frida Server 17.9.10
1. 项目目标
在 Pixel 手机上实现微信自动回复守护进程:
- 单聊消息 → 自动回复
- 群聊 @我 → 自动回复
- 群聊引用我的消息 → 自动回复
- 运行在 Termux 环境,作为持久 daemon
2. 已验证可行的部分
2.1 发送消息(完全可用)
方案:LSPosed 模块 hook ExtContentProviderBase 绕过认证,通过 broadcast 触发内部 NetSceneSendMsg API。
状态:✅ 已验证,可稳定发送文本消息到任意联系人/群聊。
触发方式:
adb shell "am broadcast -a com.hermes.wechat.SEND_MSG --es toUser 'filehelper' --es content 'hello'"
技术细节:
- Hook
ExtContentProviderBase.h()→ 返回 1(绕过权限检查) - Hook
ExtContentProviderBase.b()→ 返回 true(绕过登录检查) - Hook
AESUtil→ 跳过 contact ID 加密 - NetSceneSendMsg 调用链:
u01.r1.a(toUser)→ builder →builder.e(content)→builder.h(0)→builder.a()→m1.a()(主线程提交)
3. 已尝试但不可行的方案
3.1 直接查询 SQLite 数据库(失败)
方案:通过 sqlite3 命令行直接查询 EnMicroMsg.db 的 message 表。
结果:❌ 完全失败
原因:
- 数据库使用 SQLCipher 加密,需要正确的 key 才能打开
- 通过 Frida hook
sqlite3_key(offset 0x2687d8)捕获到 5 个 key - 确认 EnMicroMsg.db 使用的 key 为
e03ce81 - 但使用 sqlcipher CLI 尝试 11 种不同配置(不同 KDF 迭代次数、HMAC 设置、page size),全部返回 "file is not a database"
- 通过 Frida 直接调用
libWCDB.so的sqlite3_key/sqlite3_key_v2后执行PRAGMA schema_version,同样返回错误码 26
结论:捕获的 key 可能并非真正用于解密,或者 SQLCipher 参数(salt、KDF 算法、加密页大小等)与标准配置完全不同。
3.2 Frida Hook WCDB Java 层 insert(失败)
方案:Hook com.tencent.wcdb.database.SQLiteDatabase 的 insert() / insertWithOnConflict() / replace() / replaceOrThrow() 方法。
结果:❌ 未捕获到任何 message 表写入
原因:
- WeChat 8.0.72 的消息写入没有走这些 Java 层方法
- 可能使用了 WCDB 的 C++ 底层 API(如
SQLiteStatement)直接操作 B-tree - 或消息写入发生在
:push子进程而非主进程
3.3 Frida Hook Native sqlite3_exec / sqlite3_prepare_v2(失败)
方案:Hook libWCDB.so 中导出的 sqlite3_exec(offset 0x29a310)和 sqlite3_prepare_v2(offset 0x29a5d0)。
结果:❌ 未捕获到任何 message 相关 SQL
原因:
- 虽然
sqlite3_keyhook 成功并捕获了 key,但sqlite3_exec在消息收发时没有被调用 - WeChat 可能使用 WCDB 内部的 C++ API(
Statement::execute()等),绕过了标准 C API
3.4 Frida Hook Notification.Builder(失败)
方案:Hook android.app.Notification$Builder.setContentText() 和 setContentTitle() 捕获通知内容。
结果:❌ 未触发
原因:
- 测试时发送消息到 File Transfer Assistant,该场景不产生通知
- 实际收到外部消息时可能触发,但未验证
- 即使能捕获,通知内容通常被截断,不包含完整消息和群聊信息
3.5 ContentProvider 外部查询(失败)
方案:通过 adb shell content query --uri content://com.tencent.mm.plugin.ext.message/ 查询消息。
结果:❌ Permission Denial
原因:
- Android 系统层的权限检查无法通过 LSPosed 绕过
- 需要
com.tencent.mm.ext.permission.READ权限,adb shell (uid=2000) 没有该权限
3.6 WAL 文件监控(失败)
方案:监控 EnMicroMsg.db-wal 文件大小变化来检测新消息。
结果:❌ WAL 文件不存在
原因:
- WeChat/WCDB 可能使用 DELETE 日志模式而非 WAL 模式
- 或 WAL 已被 checkpoint 合并
3.7 LSPosed Hook MsgInfo String 方法(失败)
方案:Hook com.tencent.mm.storage.f9(MsgInfo)的所有单参数 String 方法。
结果:❌ 方法在消息收发时均不触发
原因:
- WeChat 8.0.72 的消息处理流程可能已变更,不再通过这些 Java 方法设置字段
- 消息数据可能通过 JNI / 反射 / protobuf 直接构造
4. 待验证 / 正在尝试的方案
4.1 LSPosed Hook WCDB insert(当前方向)
方案:修改 LSPosed 模块,hook com.tencent.wcdb.database.SQLiteDatabase.insert(),当表名包含 "message" 时提取 ContentValues 并写入共享文件 /sdcard/hermes/wechat_msgs.jsonl。
状态:🔄 代码已修改,但未重新编译 APK
阻塞:本地无 Java Runtime,无法编译 APK。需要用户在手机上(Android Studio)或找有 Java 环境的地方编译。
备注:此方案在 Frida 中测试时未捕获到写入,但 LSPosed 的 hook 时机和进程覆盖范围与 Frida 不同,仍有验证价值。
4.2 Frida Hook :push 进程(待尝试)
方案:微信消息推送和存储可能发生在 :push 子进程(PID 18531)。尝试在 push 进程中 hook WCDB 或 sqlite3 方法。
状态:⏳ 待尝试
风险:push 进程可能频繁重启,Frida 需要持续 attach。
4.3 Android AccessibilityService(备选方案)
方案:实现无障碍服务,监听微信消息列表界面的内容变化。当收到新消息时,微信 UI 会更新消息列表,AccessibilityService 可以捕获这些文本。
状态:⏳ 待评估
优点:
- 不依赖 hook,稳定性高
- 可以获取发送者昵称、消息内容、群聊名称
缺点:
- 需要用户手动授权无障碍服务权限
- 需要微信在前台或消息列表可见(或锁屏通知)
- 实现较复杂,需要解析 AccessibilityNodeInfo 树
4.4 dumpsys notification 轮询(备选方案)
方案:定期执行 adb shell dumpsys notification 提取微信通知内容。
状态:⏳ 待验证
问题:
- 当前
dumpsys notification输出中未找到微信通知的具体内容(标题、文本) - 可能需要
--noredact权限或其他参数 - 通知内容通常被截断,不适合群聊场景
4.5 Frida Native Hook sqlite3BtreeInsert(高难度)
方案:Hook libWCDB.so 中更底层的 sqlite3BtreeInsert 或 sqlite3VdbeExec 函数。
状态:⏳ 待研究
难点:
- 这些函数未导出,需要动态定位
- 签名和参数随 SQLite 版本变化
- 即使捕获到,也需要解析 B-tree 页数据,极为复杂
5. 当前状态总结
| 功能 | 状态 | 备注 |
|---|---|---|
| 发送消息 | ✅ 可用 | LSPosed broadcast |
| 消息追踪 | ❌ 阻塞 | 所有已知 hook 点均失败 |
| 数据库解密 | ❌ 阻塞 | Key 正确但参数不匹配 |
| 自动回复 daemon | ⏳ 待联调 | 发送端可用,但缺少输入源 |
核心瓶颈:消息追踪。没有可靠的消息来源,auto-reply.js 无法判断何时回复、回复给谁。
6. 下一步计划
方案 A:LSPosed 模块扩展(推荐优先尝试)
- 在 Android Studio 中重新编译 LSPosed 模块(含 WCDB insert hook)
- 安装新模块,重启 WeChat
- 发送测试消息,检查
/sdcard/hermes/wechat_msgs.jsonl是否有数据写入 - 如果有数据,修改 auto-reply.js 读取该文件实现消息追踪
方案 B:Frida :push 进程 hook
- 对
:push进程(PID 18531)运行 Frida,hook WCDB insert - 发送消息到该设备,观察 push 进程是否有数据库操作
- 如果成功,考虑将 hook 逻辑转为 LSPosed 模块(持久化)
方案 C:AccessibilityService
- 开发一个 Android AccessibilityService
- 监听微信通知栏或消息列表的文本变化
- 将捕获的消息写入文件供 Termux 读取
- 需要用户手动授权无障碍权限
方案 D:妥协方案 — 基于通知的简化版
- 使用
dumpsys notification定期轮询 - 仅检测"有新消息"事件(不获取具体内容)
- 收到通知后,通过 ContentProvider 或数据库查询尝试获取完整内容
- 如仍无法获取,可回退到"收到消息后固定回复"模式
7. 关键发现记录
7.1 SQLCipher Key
- 捕获到的 key:
e03ce81(7 字节) - 对应数据库路径:
/data/user/0/com.tencent.mm/MicroMsg/653cdc2320abb089e149a3f7a7683fcf/EnMicroMsg.db - 该 key 无法通过 sqlcipher CLI 或 Frida 直接调用解密
7.2 libWCDB.so 导出函数偏移
sqlite3_open:0x29fe1csqlite3_open_v2:0x2a03f8sqlite3_key:0x2687d8sqlite3_key_v2:0x26888csqlite3_exec:0x29a310sqlite3_prepare_v2:0x29a5d0sqlite3_close:0x29fbccsqlite3_errmsg:0x29efd8
7.3 NetSceneSendMsg 调用链(WeChat 8.0.72)
u01.r1.a(toUser) // 创建 builder
builder.e(content) // 设置消息内容
builder.h(0) // 设置 subtype
builder.a() // 构建 NetSceneSendMsg 对象
m1.a() // 在主线程提交到 NetSceneQueue
7.4 进程结构
- 主进程:
com.tencent.mm(PID 18340)— UI + 消息发送 - Push 进程:
com.tencent.mm:push(PID 18531)— 消息接收 + 推送 - AppBrand 进程:
com.tencent.mm:appbrand0/1
8. 待决策事项
- LSPosed 模块重新编译:用户是否有 Android Studio 环境或可以安装 JDK?
- AccessibilityService 方案:用户是否接受手动授权无障碍权限?
- :push 进程 hook:用户是否愿意让 Frida 持续运行在后台?
- 降级方案:如果以上均不可行,是否接受仅检测"有新消息"而不获取内容的简化版?