Commit c7f0d9568989a4b82b77022d3c11ecbe1c9927d8
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 对有 密码锁的设备无效,需要用户物理解锁,不能也不该尝试绕过。
Showing
2 changed files
with
51 additions
and
4 deletions
wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md
| @@ -319,15 +319,29 @@ Pixel 10 Pro Fold 有两块屏(outer/cover 屏 + inner 屏),`adb exec-out | @@ -319,15 +319,29 @@ Pixel 10 Pro Fold 有两块屏(outer/cover 屏 + inner 屏),`adb exec-out | ||
| 319 | 319 | ||
| 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)。 | 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 | - ❌ `js1.m.c()` / `bizh5webtransfer` —— 只读态 | 324 | - ❌ `js1.m.c()` / `bizh5webtransfer` —— 只读态 |
| 326 | - ❌ `m11.l0.d1()` (CDN 上传服务)—— 零触发 | 325 | - ❌ `m11.l0.d1()` (CDN 上传服务)—— 零触发 |
| 327 | - ❌ `wxartrappsvr/route`(`r45.zt5`/`re7`)—— 确认是小程序埋点上报,无关 | 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,6 +200,7 @@ public class WeChatHook implements IXposedHookLoadPackage { | ||
| 200 | // Image submission for brandservice: js1.m.c()/bizh5webtransfer never showed a | 200 | // Image submission for brandservice: js1.m.c()/bizh5webtransfer never showed a |
| 201 | // submit call, traced instead to a separate CDN service (§4 of doc). | 201 | // submit call, traced instead to a separate CDN service (§4 of doc). |
| 202 | hookBrandserviceCdnUpload(cl, lpparam.processName); | 202 | hookBrandserviceCdnUpload(cl, lpparam.processName); |
| 203 | + hookGenericImgCdnSubmit(cl, lpparam.processName); | ||
| 203 | } | 204 | } |
| 204 | 205 | ||
| 205 | // ── WCDB hook ────────────────────────────────────────────────────────────── | 206 | // ── WCDB hook ────────────────────────────────────────────────────────────── |
| @@ -1759,6 +1760,37 @@ public class WeChatHook implements IXposedHookLoadPackage { | @@ -1759,6 +1760,37 @@ public class WeChatHook implements IXposedHookLoadPackage { | ||
| 1759 | } catch (Throwable t) { XposedBridge.log("[WeChatHook] hookAnyCgiCapture failed in " + procName + ": " + t); } | 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 | // 贴图号发布图片提交(BRANDSERVICE_PUBLISH_RESEARCH.md §4):js1.m.c()/bizh5webtransfer | 1794 | // 贴图号发布图片提交(BRANDSERVICE_PUBLISH_RESEARCH.md §4):js1.m.c()/bizh5webtransfer |
| 1763 | // 那条纯文本 transfer 链路里从没见过提交调用——反编译顺着 | 1795 | // 那条纯文本 transfer 链路里从没见过提交调用——反编译顺着 |
| 1764 | // LuggageUploadMediaFileManager$UploadMediaFileTask.z() 往下追,图片走的是完全独立的 | 1796 | // LuggageUploadMediaFileManager$UploadMediaFileTask.z() 往下追,图片走的是完全独立的 |
| @@ -3301,6 +3333,7 @@ public class WeChatHook implements IXposedHookLoadPackage { | @@ -3301,6 +3333,7 @@ public class WeChatHook implements IXposedHookLoadPackage { | ||
| 3301 | hookBrandservicePublishTransfer(sTinkerCL, procName + "/tinker"); | 3333 | hookBrandservicePublishTransfer(sTinkerCL, procName + "/tinker"); |
| 3302 | hookAnyCgiCapture(sTinkerCL, procName + "/tinker"); | 3334 | hookAnyCgiCapture(sTinkerCL, procName + "/tinker"); |
| 3303 | hookBrandserviceCdnUpload(sTinkerCL, procName + "/tinker"); | 3335 | hookBrandserviceCdnUpload(sTinkerCL, procName + "/tinker"); |
| 3336 | + hookGenericImgCdnSubmit(sTinkerCL, procName + "/tinker"); | ||
| 3304 | // Pad-login: the login UIs (LoginPasswordUI/LoginHistoryUI/ | 3337 | // Pad-login: the login UIs (LoginPasswordUI/LoginHistoryUI/ |
| 3305 | // LoginUI) + bk run in Tinker CL — the base-CL hooks never | 3338 | // LoginUI) + bk run in Tinker CL — the base-CL hooks never |
| 3306 | // fire (0 N-VISIBLE). Register them here so the "以平板登录" | 3339 | // fire (0 N-VISIBLE). Register them here so the "以平板登录" |