任务:修通 WeChat Xposed 内部发送接口
🔒 安全约束(不得违反)
只能用 filehelper(文件传输助手)测试,绝对不能给真实联系人发消息。
测试命令固定格式:
su -c 'am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to "filehelper" --es text "测试"'
目标
让 LSPosed 模块 com.example.wechathook 能通过 broadcast 真正把消息发进微信 message 数据库(EnMicroMsg.db),在文件传输助手收到消息。
设备:Pixel 10 Pro Fold,Android 16,WeChat 8.0.74,LSPosed JingMatrix Vector v1.11.0。
工作目录
/sdcard/wechathook/
WeChatHook.java ← 当前源码(已是最新版)
termux_build.sh ← Termux 上编译+安装脚本
analyze_wechathook.py ← DEX 字节码分析工具(Python,读 Tinker patch)
XposedBridgeApi.jar ← 编译依赖
android.jar ← 编译依赖(API 36)
d8.jar ← DEX 转换工具
wechathook_current.apk ← 当前安装版本
当前状态(已做了什么、失败在哪)
已实现
模块已注入所有 WeChat 进程(main / push / appbrand0 / appbrand1)。每个进程里:
- 注册了
com.hermes.wechat.SEND_MSG_INTERNALbroadcast receiver - 挂钩了
w11.r0的 4 种构造器((String,String,int,int,long) 等) - 挂钩了
w11.r0.H(String)(发送 dispatch 方法) - 挂钩了
gm0.j1.g(km0.c)(DI 内核初始化检测) - 挂钩了 WCDB
insertWithOnConflict(诊断用)
关键发现(失败原因)
发现 1:w11.r0 构造器永远不被 WeChat 自己调用。
用户在 UI 点发送时,w11.r0 的构造器 hook 从未触发过。说明 WeChat 8.0.74(Tinker patch 版)的文字发送路径根本不经过 w11.r0 这个类,或走的是完全不同的入口(native / 另一个类名)。
发现 2:WCDB Java hook 从未触发。
com.tencent.wcdb.database.SQLiteDatabase.insertWithOnConflict 在发消息时从未被调用。说明消息写库走的是纯 native 路径(JNI),绕开了 Java 层的 WCDB wrapper。
发现 3:gm0.j1.g(km0.c) hook 注册成功但从不触发。
WeChat 正常运行(包括收发消息)期间,gm0.j1.g() 这个我们以为是 DI 初始化点的方法从未被调用过。说明 WeChat 用了另一条路初始化 DI 内核(或者混淆后方法名已变)。
发现 4:我们调用 w11.r0(String,String,int,int,long) 时:
- main 进程:
NullPointerException(某个 Context 对象是 null) - 其他进程:
iz5.b: Kernel not initialized by MMApplication!(gm0.j1.i()抛出,说明gm0.j1.m静态字段是 null)
需要你做什么
第 1 步:用 Frida 找 WeChat 真正的发送路径(最重要)
我们不知道 WeChat 8.0.74 Tinker 版实际用哪个 Java 方法发文字消息。用 Frida 在用户点「发送」按钮时抓调用栈:
# 在 Mac 端 Frida bridge 调用,或者直接在设备上用 frida-ps / frida-trace
# 目标:com.tencent.mm(主进程)
# 找到发送文字消息时调用的 Java 方法
具体建议:hook com.tencent.wcdb.database.SQLiteDatabase 的 native 层,或者用 Frida 的 Java.enumerateMethods 搜含 "sendMsg" / "sendTextMsg" 关键词的方法,在用户点发送时看 logcat。
如果 Frida 不方便,另一条路是用 Xposed hook android.os.MessageQueue.next() + 过滤调用栈,找 WeChat 发消息时进入的第一个 WeChat 内部类。
第 2 步:运行 DEX 分析脚本
# 在 Termux 里(需要 root)
python3 /sdcard/wechathook/analyze_wechathook.py
这个脚本会从 Tinker patch 的 classes12.dex 里提取:
gm0.j1的所有字段和方法签名w11.r0的所有构造器签名gm0.j1.i()的字节码(关键调用)gm0.j1.g()的字节码(看它究竟做了什么)
看输出里 gm0.j1.i() 的字节码,找它检查的是哪个字段(sget-object 指令);找谁会真正设置那个字段(sput-object 指令在哪个方法里)。
第 3 步:根据发现修改 WeChatHook.java
修改路径两选一:
路径 A(推荐):换用 WeChat 真实的发送方法
如果 Frida 或字节码分析找到了真实发送入口(比如 xxx.sendTextMessage(talker, text) 这种),直接在 hook 里反射调用它,完全绕开 w11.r0。
路径 B:修复 DI 初始化
找到真正初始化 gm0.j1.m 的方法(替代 gm0.j1.g()),hook 那个方法,在它完成后触发发送。
第 4 步:编译 + 安装 + 测试
# 修改 WeChatHook.java 后,在 Termux 里:
cd /sdcard/wechathook
bash termux_build.sh
# 脚本会自动:编译 → DEX → 签名 → pm install -r → 更新 LSPosed DB
# 完成后重启 WeChat(脚本末尾会提示命令)
# 然后测试(只用 filehelper):
su -c 'am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to "filehelper" --es text "测试123"'
# 看 LSPosed 日志确认有没有走到 Send OK:
su -c 'logcat -d -s WeChatHook:* WeChatHookEarly:* | tail -50'
关键类名参考(WeChat 8.0.74 Tinker patch,classes12.dex)
| 类 | 作用 |
|---|---|
gm0.j1 |
DI 容器单例;i()→Lgm0/j1; 取单例;m 是静态字段 |
km0.c |
WeChat kernel/application 对象,传给 gm0.j1.g() |
gm0.b0 |
mCoreStorage,从 gm0.j1.u() 取 |
w11.r0 |
疑似 NetSceneSendMsg,但 WeChat 自己不走这里 |
iz5.b |
异常类:Kernel not initialized by MMApplication! |
fs.g |
DI enum;a[0] 是当前实现 |
fs.k2 |
DI 实现枚举;e 是目标值 |
Tinker patch APK(root 权限才能访问):
/data/user/0/com.tencent.mm/tinker/patch-cf87f687/dex/tinker_classN.apk
包含 classes2.dex ~ classes17.dex,关键类在 classes12.dex。
当前 WeChatHook.java 的核心逻辑(方便你快速理解)
broadcast 到达
→ trySendWithRetry(talker, text, 0, procName)
→ isDIReady()? 调 gm0.j1.i() 看是否抛异常
→ 如果不 ready:挂 pending,等 gm0.j1.g() hook 触发(但 g() 从没触发过!)
→ 如果 ready:sendViaR0(talker, text)
→ new w11.r0(talker, text, 1, 0, ts) ← 在所有进程都失败
→ r0.H("") ← 从没执行到
关键 bug:整条路的前提假设(gm0.j1.g() 是初始化点,w11.r0 是发送入口)可能都是错的。需要从真实调用栈重新定位。
输出期望
完成后,以下命令应该能让文件传输助手收到消息:
su -c 'am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL --es to "filehelper" --es text "hello from hermes"'
LSPosed 日志里应该看到 [WeChatHook] Send OK to=filehelper。
把修改后的 WeChatHook.java 回写到 /sdcard/wechathook/WeChatHook.java,方便 Mac 端同步。