package com.example.wechathook; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; /** * WeChatSendHook: Route A — 通过 WeChat 内部 API 发送文本消息。 * * 调用链(逆向 WeChat 8.0.74): * 外部 am broadcast → BroadcastReceiver (WeChat 进程内) * → w11.s1.a(talker) → w11.r1 (SendMsgCgiFactory, p1.TEXT pipeline) * → r1.e(content) → 设置文本 * → r1.b() → executeByPPC * → w11.r0(talker, content, type=1, ...) * → message 表写入 (status=SENDING) * → /cgi-bin/micromsg-bin/newsendmsg over MMTLS * * 触发: * adb shell am broadcast -a com.hermes.wechat.SEND_MSG_INTERNAL \ * --es to "wxid_xxx" --es text "Hello from hook" * * Talker 格式: * 1 对 1: wxid_xxx (对方 WeChat ID) * 群聊: 12345678901@chatroom * * 注意:发送在 WeChat 主线程执行,与用户正常发送路径完全一致。 * 不触发 A11y,不走 UI 自动化,消息经 MMTLS 加密发出。 * * 类位置(WeChat 8.0.74 多 DEX): * w11.s1 — classes9.dex * w11.r1 — classes11.dex * w11.r0 — classes12.dex * p1.TEXT — classes9.dex (enum value 'd') */ public class WeChatSendHook implements IXposedHookLoadPackage { private static final String ACTION = "com.hermes.wechat.SEND_MSG_INTERNAL"; private static ClassLoader sWeChat; @Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable { if (!"com.tencent.mm".equals(lpparam.packageName)) return; // Only hook the main process (not :push/:tools sub-processes) if (!lpparam.processName.equals("com.tencent.mm")) return; sWeChat = lpparam.classLoader; XposedBridge.log("[WeChatSendHook] Injected, registering receiver"); // Hook Application.onCreate to get Context for receiver registration. // android.app.Application.onCreate() is a framework call — never AOT-inlined. XposedHelpers.findAndHookMethod( "android.app.Application", null, "onCreate", new de.robv.android.xposed.XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Context ctx = (Context) param.thisObject; if (ctx.getPackageName().equals("com.tencent.mm")) { registerSendReceiver(ctx); } } }); } private static void registerSendReceiver(Context ctx) { BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String talker = intent.getStringExtra("to"); String text = intent.getStringExtra("text"); if (talker == null || talker.isEmpty() || text == null || text.isEmpty()) { XposedBridge.log("[WeChatSendHook] Received intent missing 'to' or 'text'"); return; } XposedBridge.log("[WeChatSendHook] Queuing send to=" + talker + " len=" + text.length()); sendOnMainThread(talker, text); } }; IntentFilter filter = new IntentFilter(ACTION); // NOT_EXPORTED (0x4) — only accept from root/same-uid callers via am broadcast ctx.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); XposedBridge.log("[WeChatSendHook] Receiver registered for " + ACTION); } private static void sendOnMainThread(String talker, String text) { // WeChat's send path touches SQLite and the network dispatcher from the main thread. new Handler(Looper.getMainLooper()).post(() -> { try { sendTextInternal(talker, text); } catch (Throwable t) { XposedBridge.log("[WeChatSendHook] Send failed: " + t); } }); } /** * Core send via w11.s1 factory (SendMsgCgiFactory): * s1.a(talker) → r1 with pipeline=p1.TEXT, talker pre-set * r1.e(content) → set field d (content) * r1.b() → executeByPPC (no callback) * * r1 field mapping (WeChat 8.0.74): * field b = talker (set by g()) * field d = content (set by e()) * field e = type (default 1=text, set by h()) * field l = pipeline (set to p1.TEXT by s1.a()) */ private static void sendTextInternal(String talker, String text) throws Throwable { Class s1 = XposedHelpers.findClass("w11.s1", sWeChat); // s1.a(talker) → r1 with g(talker) pre-called and l=p1.TEXT Object r1 = XposedHelpers.callStaticMethod(s1, "a", talker); // r1.e(content) → sets field d XposedHelpers.callMethod(r1, "e", text); // r1.b() → d(this,null,1,null) → c(q1.d) → executeByPPC XposedHelpers.callMethod(r1, "b"); XposedBridge.log("[WeChatSendHook] Sent: to=" + talker); } }