Commit 645c4d30dde49c9564539bda8ca950657cdebf7d

Authored by yibo
1 parent 09e6a868

research(wechat): 确认微信主进程 frida 注入不安全,两次真实故障,此路线到此为止

第二次尝试(spawn 后立即 resume,隔 0.8s 再 attach)连 hook 都没装成功
(frida 自身 JVMTI attach 报 access violation),但微信依然被搞挂(这次
表现为界面卡死而非"无法连接服务器")。两次事故一次 hook 装成功一次没装成功,
排除了"hook 代码本身有 bug",说明是"往微信主进程冷启动过程里做 frida
attach"这个动作本身在这台设备这个微信版本上就不安全,不是时序调参能解决的。

建议:不再对主进程做任何 frida spawn/attach 变体;appbrand 子进程的
spawn-gating hook 依然安全可复用;退回半自动方案(人工点发布)性价比
明显更高。
wechat_send_internal/BRANDSERVICE_PUBLISH_RESEARCH.md
@@ -192,21 +192,50 @@ API 用)。 @@ -192,21 +192,50 @@ API 用)。
192 (具体是哪一步冲突的没有细查,代价太高不值得再试来定位)。**不要在没有更谨慎方案 192 (具体是哪一步冲突的没有细查,代价太高不值得再试来定位)。**不要在没有更谨慎方案
193 之前重复"spawn 主进程 + 挂钩子"这个操作。** 193 之前重复"spawn 主进程 + 挂钩子"这个操作。**
194 194
195 -### 3.7 下一步(还没做,需要用户决策后再动)  
196 -  
197 -在主进程里稳定 hook `Ljs1/m;->c()` 的思路都不便宜:  
198 -- 主进程存量 attach(不 force-stop,不 spawn)大概率会撞上文档最早记录的"存量进程  
199 - 类枚举超时"问题(主进程加载的类比 appbrand 子进程多得多,超时风险更高)。  
200 -- 换个更早/更安全的注入时机(比如 spawn 后立即 resume、隔几百 ms 再单独 attach,  
201 - 不在 spawn-paused 状态下装钩子)也许能避开这次的冲突,但没验证过,仍有再搞坏一次  
202 - 微信启动的风险。  
203 -- 退回"半自动"方案(人工点发布,只自动生成图片+文案,不追求端到端全自动)此时  
204 - 性价比在明显上升——今天已经:让用户配合走了 6 轮真机发布流程 + 1 次微信主进程  
205 - 连接故障(虽然自愈了)。  
206 -  
207 -三个原始脚本(`hook_lite_transfer.js`/`_bundled.js`, `multi_spawn_hook.py`,  
208 -`hook_transfer_sink.js`/`_bundled.js`, `spawn_mm_and_hook.py`)都已验证/写好可复用,  
209 -如果决定继续这条路线,工具链是现成的,缺的是一个更安全的主进程注入时机。 195 +### 3.7 第二次事故:换个注入时机(spawn 后立即 resume,隔 0.8s 再 attach)依然搞坏微信
  196 +
  197 +`spawn_mm_and_hook.py` v2:不在 spawn-paused 状态下注入,而是 spawn 后立刻
  198 +`resume()`,隔 0.8 秒再对已经在跑的 pid 做正常 `device.attach()`。这次连 hook 本身
  199 +都没装成功——`Java.perform` 内部 attach JVMTI 环境时直接报
  200 +`Error: access violation accessing 0x0`(frida 自己的 JVMTI attach 逻辑挂了,
  201 +时机太早,ART 运行时还没就绪),我们的 hook 代码根本没跑到。
  202 +
  203 +**但即便 hook 没装成功,微信依旧被搞坏了**——用户反馈"微信点不动"(界面卡死/无响应),
  204 +跟第一次的"无法连接服务器"症状不同,但同样需要 `am force-stop` 全杀 + 手动重新打开
  205 +才恢复正常。
  206 +
  207 +**这排除了"是我们 hook 代码本身有 bug"这个解释**——两次事故一次 hook 装成功了
  208 +(§3.6,之后微信显示网络错误),一次 hook 压根没装上就已经 native 报错(这次,
  209 +微信直接卡死)。共同点只有"往微信主进程冷启动过程里做 frida attach 这个动作本身"。
  210 +不管注入时机多晚、hook 代码写不写东西,只要在主进程刚起来那段时间用 frida 碰它,
  211 +就有实质性概率把微信弄挂,且两次表现还不一样(无法连接服务器 / 界面卡死),
  212 +说明不是某个特定时序窗口的问题,而是这个操作本身对这台设备上这个微信版本
  213 +就是不安全的。
  214 +
  215 +### 3.8 结论:主进程注入这条路线到此为止
  216 +
  217 +**不建议再尝试对微信主进程做任何形式的 frida spawn/attach**(不管是 spawn-paused
  218 +时机、resume 后延迟 attach,还是理论上的其它变体)。今天累计:6 轮真机手动发布 +
  219 +2 次主进程注入导致微信实际不可用(每次都要 force-stop 全杀 + 手动重开才恢复,
  220 +过程中用户体验是真实受影响的,不是纯理论风险)。风险已经从"可能浪费时间"上升到
  221 +"实际两次打断用户在用的微信",性价比不再支持继续在真机上试验变体。
  222 +
  223 +`appbrand` 子进程的 spawn-gating hook(`multi_spawn_hook.py`,用
  224 +`hook_lite_transfer_bundled.js`)本身依然是安全的、已验证的技术(多次验证只影响
  225 +子进程,不影响主进程/登录态),可以留着给其它子进程场景复用;`Ljs1/m;->c()`
  226 +是不是真正 sink 也依然成立(反编译分析站得住),只是"怎么安全地够到跑在主进程里
  227 +的它"这个问题目前没有答案。
  228 +
  229 +如果以后要再碰这条线,思路上可以考虑的方向(都没验证,下次再评估要不要试):
  230 +- 不整个重启微信主进程,而是想办法在**已经在跑、且刚从后台切到前台**的主进程上
  231 + 做非 spawn 式的常规 attach(拿掉"重启"这个变量,只保留"attach"这个变量,
  232 + 分开验证到底是哪一步不安全)——但存量进程类枚举超时的老问题还在,且同样有
  233 + "只是 attach 就把微信弄挂"的风险,不能假设更安全。
  234 +- 完全放弃 hook 微信主进程,改成分析 MMTLS 外层协议本身能不能在不进程内注入的
  235 + 情况下拿到明文(例如找有没有已知的 MMTLS 密钥导出/降级漏洞——目前没有调研过,
  236 + 真实存在的可能性未知,可能是死路)。
  237 +- **退回"半自动"方案**:人工点发布,工具只负责自动生成图片+文案,不追求端到端
  238 + 全自动。今天的两次真实故障之后,这个选项的性价比明显更高了,建议优先考虑。
210 239
211 --- 240 ---
212 241
wechat_send_internal/spawn_mm_and_hook.py
@@ -2,8 +2,12 @@ @@ -2,8 +2,12 @@
2 # 贴图号发布 hook 的真正入口点是 Ljs1/m;->c(TransferRequestInfo, callback)(见 2 # 贴图号发布 hook 的真正入口点是 Ljs1/m;->c(TransferRequestInfo, callback)(见
3 # BRANDSERVICE_PUBLISH_RESEARCH.md §3.5),大概率跑在微信主进程(长驻,不是 3 # BRANDSERVICE_PUBLISH_RESEARCH.md §3.5),大概率跑在微信主进程(长驻,不是
4 # spawn-gating 能抓的子进程)。长驻进程 Java.perform 枚举存量类会超时,唯一办法是 4 # spawn-gating 能抓的子进程)。长驻进程 Java.perform 枚举存量类会超时,唯一办法是
5 -# force-stop 整个微信、用 frida 重新 spawn 它,在类加载膨胀之前就把钩子挂上  
6 -# (跟之前抓 appbrand 子进程用的是同一个技巧,只是这次 spawn 的是整个 App)。 5 +# force-stop 整个微信、用 frida 重新 spawn 它,在类加载膨胀之前就把钩子挂上。
  6 +#
  7 +# v1(spawn-paused 时就 attach+hook+resume)实测会把微信主进程冷启动搞坏
  8 +# ("无法连接服务器",见 §3.6)。v2 改成:spawn 后立刻 resume(不在暂停态注入),
  9 +# 隔 ATTACH_DELAY_SEC 再正常 attach——避开暂停态注入这个可疑触发点,但没验证过
  10 +# 是否真的安全,也没验证过延迟这么久还能不能赶在类加载膨胀前挂上钩子。
7 # 11 #
8 # 用法: source .venv/bin/activate && python3 spawn_mm_and_hook.py 12 # 用法: source .venv/bin/activate && python3 spawn_mm_and_hook.py
9 import subprocess 13 import subprocess
@@ -13,6 +17,7 @@ import frida @@ -13,6 +17,7 @@ import frida
13 17
14 PACKAGE = "com.tencent.mm" 18 PACKAGE = "com.tencent.mm"
15 SCRIPT_PATH = "../wechat_send_internal/hook_transfer_sink_bundled.js" 19 SCRIPT_PATH = "../wechat_send_internal/hook_transfer_sink_bundled.js"
  20 +ATTACH_DELAY_SEC = 0.8
16 21
17 22
18 def on_message(message, data): 23 def on_message(message, data):
@@ -35,14 +40,16 @@ def main(): @@ -35,14 +40,16 @@ def main():
35 40
36 print(f"[*] spawning {PACKAGE} (paused) ...") 41 print(f"[*] spawning {PACKAGE} (paused) ...")
37 pid = device.spawn([PACKAGE]) 42 pid = device.spawn([PACKAGE])
38 - print(f"[+] spawned pid={pid}, attaching...") 43 + print(f"[+] spawned pid={pid}, resuming immediately (no injection while paused)...")
  44 + device.resume(pid)
  45 + print(f"[*] waiting {ATTACH_DELAY_SEC}s before attaching...")
  46 + time.sleep(ATTACH_DELAY_SEC)
  47 + print(f"[*] attaching to pid={pid} ...")
39 session = device.attach(pid) 48 session = device.attach(pid)
40 script = session.create_script(source) 49 script = session.create_script(source)
41 script.on("message", on_message) 50 script.on("message", on_message)
42 script.load() 51 script.load()
43 - print("[+] script loaded, resuming...")  
44 - device.resume(pid)  
45 - print(f"[*] {PACKAGE} resumed (pid={pid}). Reopen WeChat on the phone and redo the flow.") 52 + print(f"[+] script loaded on running pid={pid}. Reopen WeChat on the phone and redo the flow.")
46 53
47 try: 54 try:
48 while True: 55 while True: