diff --git a/wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md b/wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md index 3ccd605..9a410e8 100644 --- a/wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md +++ b/wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md @@ -319,15 +319,29 @@ Pixel 10 Pro Fold 有两块屏(outer/cover 屏 + inner 屏),`adb exec-out 这是本轮最有价值的新信息:从截图流程看,选完图后有一步"编辑"(`page_path: pages/image_editor/image_editor`)此前一直以为整条"编辑/填标题/发表"UI 都是纯 `TmplWebViewMMUI`(H5 网页),但埋点数据显示图片裁剪/编辑这一步实际上是**一个独立小程序**(appid `wx0b2ec076ec39c786`)渲染出来的,运行在 `appbrand*` 进程里(跟 §3 一开始的假设吻合,只是走了另一条我们没追的分支)。这意味着:贴图号发布流程可能是"原生 Flutter UI(选图) → 小程序(图片编辑) → H5 webview(标题/正文/发表)"三段拼接,每段用不同技术栈、不同网络通道,**"提交"这一步具体发生在哪一段,仍是未知数**——如果发生在小程序段,走的会是小程序专属的另一套 cgi 通道(`k2`/`y7`,回到最早 §一 反编译过的入口,但那条路当时已经确认在 appbrand 进程里挂了钩子却零触发,可能是挂错了具体哪个 appbrand 编号,或者小程序用的是另一套更底层的 native bridge,不经过 k2/y7)。 -### 5.4 现状(本轮结束):三个候选全部证伪,第四个新线索(小程序 image_editor)尚未跟进 +### 5.4 §5.3 结束时的排除法进展 -排除法进展更新: - ❌ `js1.m.c()` / `bizh5webtransfer` —— 只读态 - ❌ `m11.l0.d1()` (CDN 上传服务)—— 零触发 - ❌ `wxartrappsvr/route`(`r45.zt5`/`re7`)—— 确认是小程序埋点上报,无关 -- 🆕 图片编辑步骤是小程序 `wx0b2ec076ec39c786`,可能是下一个该深挖的方向,但还没有具体行动 +- 🆕 图片编辑步骤是小程序 `wx0b2ec076ec39c786`,当时判断是下一个该深挖的方向 -用户配合方式已改进(不再需要用户手动操作,可自主复现),今天已知的三条候选路径均已用真机数据证伪,核心提交调用仍未定位。建议下次接着来的方向:直接搜这个小程序 appid 相关的原生桥接代码(`k2`/`y7` 之外,可能还有别的小程序专属 jsapi 处理提交),或者接受当前"半自动"退路。 +### 5.5 第四个候选:普通消息发图的 CDN 服务 `v70.w.oj()` 也证伪 + +沿着"贴图号提交会不会复用普通消息发图那套服务"的思路(该服务已在 [[wechat-image-send-internals]] 里完整逆向过:`v70.w.oj(r70.g, o70.l)` 是消息图片真正启动 CDN 上传协程的方法),加了 `hookGenericImgCdnSubmit`——**被动观察**任意调用方(不只是我们自己 `sendImageViaAPI` 时构造的调用,用 `sCalledByUs` 标志区分),在 base+Tinker CL、全进程装好,真机确认 hook 安装成功、微信全程运行正常。 + +自主复现发布流程(这次用户手动解锁手机后由我接着跑,中途踩了一个新坑:**设备在会话中途自己息屏落锁了**,`adb shell wm dismiss-keyguard` 对有密码/图案锁的设备无效——不是每次都能纯脚本推进,锁屏后必须让用户物理解锁,不能也不该尝试绕过):**`v70.w.oj()` 全程零触发**,而 `js1.m.c()`/`bizh5webtransfer` 依旧只见只读态调用(277 次),本轮发布依旧真实成功(`chksm=` 再次变化,"作品"列表可见新帖子)。**第四个候选证伪**:贴图号提交图片不复用普通消息发图的 CDN 通道。 + +### 5.6 现状:四个候选全部证伪,核心提交调用仍未定位 + +- ❌ `js1.m.c()` / `bizh5webtransfer` —— 只读态 +- ❌ `m11.l0.d1()` (webview luggage CDN 上传服务)—— 零触发 +- ❌ `wxartrappsvr/route`(`r45.zt5`/`re7`)—— 小程序埋点上报,无关 +- ❌ `v70.w.oj()` (普通消息发图 CDN 服务)—— 零触发 + +四条最合理的候选路径都已经用真机数据(不是猜测)证伪,每次发布本身都确认真实成功。这已经不是"还没挂对钩子"的问题了——更可能是:①真正的提交调用发生在某个我们完全没设想到的类/服务里(贴图号可能有一套自己专属、从没在其它已逆向功能里出现过的原生 API);②或者走了完全绕开 `modelbase.l`/已知 CDN 服务的通道(比如小程序沙箱自己的 `wx.request`/`wx.uploadFile` 实现,跟主进程网络栈是分离的子系统,可能需要 hook 小程序 JS runtime 层而非 native 层)。 + +到此为止,`hookAnyCgiCapture` 是全局通用观察点,覆盖面已经很广(能看到所有内部二进制 cgi),四次真机验证也证明这套 LSPosed 工具链完全稳定可靠(今天全程零故障)。但纯靠"猜一个类挂上去看"的排除法已经遇到收益递减——建议下一步换个思路:要么直接反编译小程序沙箱自身的 JS bridge 层(`wx.uploadFile`/`wx.request` 在 native 侧的实现类,可能在 `com.tencent.mm.plugin.appbrand.*` 包下,是全新的、还没搜过的领域),要么接受"半自动"方案。今天的调查已经很深入,建议先到此为止,下次再看要不要继续深挖小程序沙箱 JS bridge 这条新方向。 --- diff --git a/xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java b/xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java index cff16db..d351276 100644 --- a/xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java +++ b/xhs_lsposed_a16_fix/wechathook_src/WeChatHook.java @@ -200,6 +200,7 @@ public class WeChatHook implements IXposedHookLoadPackage { // Image submission for brandservice: js1.m.c()/bizh5webtransfer never showed a // submit call, traced instead to a separate CDN service (§4 of doc). hookBrandserviceCdnUpload(cl, lpparam.processName); + hookGenericImgCdnSubmit(cl, lpparam.processName); } // ── WCDB hook ────────────────────────────────────────────────────────────── @@ -1759,6 +1760,37 @@ public class WeChatHook implements IXposedHookLoadPackage { } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookAnyCgiCapture failed in " + procName + ": " + t); } } + // 贴图号发布路线 §5.5:js1.m.c()/bizh5webtransfer 的 protobuf-cgi 框架(modelbase.l) + // 三次全流程真机验证都只见 mp.weixin.qq.com 只读态调用(预览/统计/埋点),从没见过 + // 提交语义——很可能提交压根不走这套框架,而是走跟普通消息发图一样的独立二进制 CDN + // 上传服务:Lv70/w;->oj(Lr70/g;Lo70/l;)(见 wechat-image-send-internals.md)。被动 + // 观察任意调用方(不只是我们自己 sendImageViaAPI 时构造的调用),看贴图号发布是否 + // 复用这同一个服务。 + private void hookGenericImgCdnSubmit(ClassLoader cl, String procName) { + try { + Class wCls = XposedHelpers.findClass("v70.w", cl); + java.lang.reflect.Method m = wCls.getDeclaredMethod("oj", + XposedHelpers.findClass("r70.g", cl), XposedHelpers.findClass("o70.l", cl)); + m.setAccessible(true); + XposedBridge.hookMethod(m, new XC_MethodHook() { + @Override protected void beforeHookedMethod(MethodHookParam p) { + try { + StringBuilder sb = new StringBuilder( + "[WeChatHook] IMGCDN-SUBMIT v70.w.oj() called by=" + (sCalledByUs ? "US" : "REAL") + + " in " + procName + "\n r70.g params:\n"); + dumpAllFields(p.args[0], sb, " "); + sb.append(" o70.l preBuildResultItem:\n"); + dumpAllFields(p.args[1], sb, " "); + XposedBridge.log(sb.toString()); + } catch (Throwable t) { XposedBridge.log("[WeChatHook] IMGCDN-SUBMIT dump err: " + t); } + } + }); + XposedBridge.log("[WeChatHook] hookGenericImgCdnSubmit: hooked v70.w.oj in " + procName); + } catch (Throwable t) { + XposedBridge.log("[WeChatHook] hookGenericImgCdnSubmit failed in " + procName + ": " + t); + } + } + // 贴图号发布图片提交(BRANDSERVICE_PUBLISH_RESEARCH.md §4):js1.m.c()/bizh5webtransfer // 那条纯文本 transfer 链路里从没见过提交调用——反编译顺着 // LuggageUploadMediaFileManager$UploadMediaFileTask.z() 往下追,图片走的是完全独立的 @@ -3301,6 +3333,7 @@ public class WeChatHook implements IXposedHookLoadPackage { hookBrandservicePublishTransfer(sTinkerCL, procName + "/tinker"); hookAnyCgiCapture(sTinkerCL, procName + "/tinker"); hookBrandserviceCdnUpload(sTinkerCL, procName + "/tinker"); + hookGenericImgCdnSubmit(sTinkerCL, procName + "/tinker"); // Pad-login: the login UIs (LoginPasswordUI/LoginHistoryUI/ // LoginUI) + bk run in Tinker CL — the base-CL hooks never // fire (0 N-VISIBLE). Register them here so the "以平板登录"