You need to sign in before continuing.

Commit ead036cf9219137f736727f511c75e9cfb180f44

Authored by yibo
1 parent 3dfe11b2

research(wechat): 验证 Tinker 补丁 dex 与 base.apk 一致,Flutter 线索大概率跟单图发布无关

验证了当前真机 Tinker 补丁(patch-83069b25,2026-07-12,含 libapp.so
确认贴图号 Flutter 功能是热更新下发的)里的 BizPublishApi/lx.m5 跟
base.apk 反编译内容完全一致,排除"反编译资产过期"这个猜测。

用 jadx 读了 BizPublishApi$Companion.setUp() 完整分发逻辑:11 个
Pigeon BasicMessageChannel 直接调用对应方法,无代理/反射,hook 位置
技术上完全正确。两轮真机验证零触发的唯一合理解释:Dart 侧压根没为
"贴图号+单图+短文字"这个简化流程发送这条消息——这套 Pigeon 桥接更
可能服务于另一个更复杂的发布场景(完整公众号图文),跟当前测的路径
无关,这条线大概率是死胡同。

记录了三个待选的下一步方向(精确时间线比对 js1.m.c() 洪流 / hook
WCDB 写库回溯调用栈 / 接受半自动方案),留待下次接手。
wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md
@@ -408,6 +408,28 @@ setNativeEditorDebugMode(mode) @@ -408,6 +408,28 @@ setNativeEditorDebugMode(mode)
408 408
409 鉴于今天已经连续两天、多轮真机验证 + 两次反编译资产过期的教训,这是一个自然的收尾点。 409 鉴于今天已经连续两天、多轮真机验证 + 两次反编译资产过期的教训,这是一个自然的收尾点。
410 410
  411 +### 6.5 验证结果:不是反编译资产过期,是 Flutter/Pigeon 这条线本身大概率跟这次的发布流程无关
  412 +
  413 +用户要求直接验证"当前真机在跑的 APK,类名是否变了"——按此做了严格验证:
  414 +
  415 +1. 查当前安装信息:`versionName=8.0.74 versionCode=3120 lastUpdateTime=2026-07-06`;`~/wechat_apk/base.apk`(旧反编译资产的来源)是 2026-07-27 从设备拉的,版本吻合,**base.apk 本身没有过期**。
  416 +2. 但真正在跑的代码还叠了一层 **Tinker 热更新补丁**(`/data/data/com.tencent.mm/tinker/patch-83069b25/`,2026-07-12 生成)——补丁目录里翻到 **`lib/arm64-v8a/libapp.so`**(Flutter Dart AOT 编译产物的标准文件名),实锤"我的作品"这个 Flutter 功能是**靠 Tinker 热更新下发的**,不在原始安装包里。这印证了本文档最早(§一)就记录过的教训:"base.apk ≠ 正在跑的码",但这次要验证的类恰好两边都有。
  417 +3. 拉了 Tinker 补丁的 `dex/tinker_classN.apk`(174MB,17 个 dex),用 `strings` 扫每个 dex 确认 `BizPublishApi`/`lx.m5` 具体在哪个 dex(`classes11.dex`,跟 base.apk 反编译时的 `smali_classes11` 目录编号一致),用 jadx 直接反编译这个 dex 里的 `BizPublishApi.java` 全文对比。
  418 +
  419 +**结论:两份反编译(base.apk vs Tinker 补丁)内容完全一致**,不是类名/代码变了导致 hook 落空。真正读懂的是 `BizPublishApi$Companion.setUp()` 的完整分发逻辑:11 个方法各自对应一个具名的 Flutter `BasicMessageChannel`(channel 名形如 `"dev.flutter.pigeon.mm_auto_gen.BizPublishApi.notifyBizPublishResult"`),**onMessage 收到 Dart 端发来的消息后直接 `bizPublishApi.notifyBizPublishResult(...)` 调用**,中间没有任何反射/代理/多态转发会让 Xposed 的方法级 hook 失效。也就是说:**hook 挂的位置技术上完全正确**(后来证实两份 dex 里代码一字不差),**两轮真机验证零触发的唯一合理解释是:Dart/Flutter 那一侧压根没有为这次测的"贴图号发单图"这个简单流程发送这条 Pigeon 消息**。
  420 +
  421 +结合已经稳定复现的事实——`js1.m.c()`/`bizh5webtransfer` 每轮都稳定触发 60-280 次,且每轮发布都真实成功(`chksm` 持续变化)——比较合理的推断是:**`BizPublishApi` 这套 Pigeon 桥接大概率服务于另一个更复杂的发布场景(比如完整的公众号图文创作/`appmsg_editor` 富文本文章),不是"贴图号 + 单图 + 短文字"这个我们一直在测的简化路径**。这条 Flutter 线索本身是真实存在的架构,但对当前要抓的这个具体流程,很可能是一条死胡同。
  422 +
  423 +### 6.6 下一步方向(记录以便随时接续,未执行)
  424 +
  425 +真正的提交调用大概率仍然落在已经反复确认触发、但从未逐条精确解码时间线的 `js1.m.c()`/`bizh5webtransfer` 洪流里(每轮 60-280 次调用,此前只做了整体分类统计,没有按"点击发表那一刻精确到毫秒级"做过 reqUrl 逐条比对)。下次可以:
  426 +
  427 +1. 精确记录"点击发表"那一次 `input tap` 的系统时间戳(已经在脚本里做了,`date +%s.%N`),再把 `js1.m.c()` 洪流按时间排序,重点看 tap 后最先触发的几条、以及包含 `action=` 参数值明显不是 `get_*`/只读语义的那几条(此前扫描全部都是 get/batch_report 语义,但没有严格逐条过一遍,可能漏看)。
  428 +2. 或者干脆放弃继续猜测哪个原生/Flutter 类是入口,转向"发布结果一定会写库"这个更确定的信号——去 hook WCDB(`hookWCDB` 已有,之前用于消息表)看贴图号发布成功后有没有对应的本地 DB INSERT,反过来从写库那一刻回溯调用栈,比继续挨个猜 jsapi 类更有把握。
  429 +3. 或接受目前的进展,转向"半自动"方案(人工点发布,工具只管生成图片+文案)——今天两天的调查已经把所有直觉候选都验证过一遍,继续投入的边际收益在下降。
  430 +
  431 +三个方向都还没做,留给下次接手时决定。
  432 +
411 --- 433 ---
412 434
413 ## 四、现场状态(写这份记录时) 435 ## 四、现场状态(写这份记录时)