wechat-auto-reply-status.md 10.1 KB

WeChat 自动回复项目 — 完整状态报告

日期:2026-05-23 设备: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 数据库(成功 — 已验证)

方案:通过 sqlcipher CLI 直接查询 EnMicroMsg.db 的 message 表。

结果:✅ 已验证,可直接解密并查询

关键参数:

  • SQLCipher key: e03ce81
  • PRAGMA cipher_compatibility = 1(WCDB 兼容模式)
  • PRAGMA cipher_page_size = 1024
  • 无需复制 WAL(sqlcipher 可直接打开运行中的 DB,但可能遇到 database is locked)

使用方式(在 auto-reply.js 中通过 AndroidWechatTrackerSQLCipher):

sqlcipher /data/data/com.tencent.mm/MicroMsg/.../EnMicroMsg.db <<EOF
.headers off
.mode line
PRAGMA key="e03ce81";
PRAGMA cipher_compatibility=1;
PRAGMA cipher_page_size=1024;
SELECT msgId, type, isSend, talker, content, createTime FROM message WHERE msgId > ${lastId} AND isSend = 0 AND type IN (1, 10002) ORDER BY msgId ASC;
EOF

结论:SQLCipher 解密成功,已成为主消息检测方案(替代 LSPosed 日志 tail)。


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_key hook 成功并捕获了 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(备用)
发送消息(Frida) ✅ 可用 Frida daemon 队列注入(主推)
消息追踪 ✅ 可用 SQLCipher DB 轮询(主推)
数据库解密 ✅ 可用 sqlcipher CLI + WCDB 兼容参数
自动回复 daemon ✅ 可用 auto-reply.js 主流程已跑通
息屏唤醒 ✅ 可用 notification-list 兜底 + 自动亮屏解锁

当前主推方案:Node.js plugin (auto-reply.js)

  • 消息检测:AndroidWechatTrackerSQLCipher(sqlcipher 轮询)
  • 回复生成:callHermesApi() → hermes-agent OpenAI-compatible API
  • 消息发送:sendTextMessageFrida()(Frida daemon 队列)
  • 息屏兜底:pollNotifications()(通知监听 → 唤醒微信)

6. 下一步计划

已完成(当前主流程)

  1. SQLCipher DB 轮询 — AndroidWechatTrackerSQLCipher 已实现并验证,替代 LSPosed 日志 tail
  2. Frida Daemon 发送 — sendTextMessageFrida 已实现并验证,替代 am broadcast(绕过 Android 10+ 后台限制)
  3. hermes-agent API — auto-reply.js 已切换到 OpenAI-compatible /v1/chat/completions
  4. 息屏自动唤醒 — 通知监听 + wakeWechat() 点亮/解锁/启动微信流程已验证

待优化

  1. 息屏发送稳定性 — 当前息屏时 Frida daemon 可能因微信进程被冻结而失效,需要进一步验证
  2. 群聊 @识别精度 — 依赖昵称匹配,考虑加入 wxid 匹配
  3. Frida daemon 持久化 — 目前需要手动注入,考虑 Termux:Boot 自动注入
  4. 消息去重 — 息屏唤醒后 tracker 可能捕获到已处理过的消息,需优化 state 管理
  5. 本地 LLM 部署 — 减少 API 延迟和网络依赖

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:0x29fe1c
  • sqlite3_open_v2:0x2a03f8
  • sqlite3_key:0x2687d8
  • sqlite3_key_v2:0x26888c
  • sqlite3_exec:0x29a310
  • sqlite3_prepare_v2:0x29a5d0
  • sqlite3_close:0x29fbcc
  • sqlite3_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. 待决策事项

  1. LSPosed 模块重新编译:用户是否有 Android Studio 环境或可以安装 JDK?
  2. AccessibilityService 方案:用户是否接受手动授权无障碍权限?
  3. :push 进程 hook:用户是否愿意让 Frida 持续运行在后台?
  4. 降级方案:如果以上均不可行,是否接受仅检测"有新消息"而不获取内容的简化版?