Commit c7f0d9568989a4b82b77022d3c11ecbe1c9927d8

Authored by yibo
1 parent 67417267

research(wechat): 第四个候选(消息发图 CDN 服务)也证伪,四条路径均排除

加了 hookGenericImgCdnSubmit 被动观察 v70.w.oj()(普通消息发图真正
CDN 上传入口,已在 wechat-image-send-internals.md 逆向过),测试贴图号
提交是否复用这条通道。真机确认:全程零触发,发布依旧真实成功
(chksm 变化),第四个假说证伪。

至此 js1.m.c()/bizh5webtransfer、m11.l0.d1()、wxartrappsvr/route、
v70.w.oj() 四条最合理候选全部用真机数据排除。hookAnyCgiCapture 已是
全局通用观察点,纯靠猜类名排除法收益递减,建议下次改查小程序沙箱
自身 JS bridge 层(wx.uploadFile/wx.request 的 native 实现,appbrand
包下,全新方向)。

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