Files
live-hub-py/docs/模拟器存活闪退诊断报告.md
yml2213 49c5c36c05 docs(huya): 模拟器存活闪退诊断报告与 Frida 探测脚本证据
- 诊断报告: attach 主进程静默退出/EGL 崩溃, 仅约 4s 窗口可抓帧
- scripts: attach/spawn/hook/emu 系列 Frida 脚本与抓帧/验证工具
- evidence: identity/reqchain/frame/inputbuf/magic_buf/propedge 抓取样本,
  emu_* 存活对比, diag_* 策略实验, baseline 裸测基准
2026-08-27 17:58:32 +08:00

15 KiB
Raw Permalink Blame History

模拟器闪退/卡启动/验证码不加载 —— 存活与崩溃诊断报告

日期: 2026-08-27 环境: 模拟器 GC3VE (Android 12 API32, arm64, MuMu系, Kitsune Mask 26.4 rooted) App: 虎牙 com.duowan.kiwi 13.4.22 (115315) frida: server 15.2.2 (fs152, tcp:31878) + RE仓库 .venv (frida-py 15.2.2) 本文档只回答一件事: "App到底什么时候活着、什么时候闪退、为什么", 判断方法全部可复核。

〇、先纠正一个根本性错误: 如何判断"是否存活/是否闪退"

之前(包括文档既有结论)用以下方式判断进程状态, 全部不可靠:

旧方法 为什么错
frida enumerate_processes() 里 pid 存在 spawn 后枚举有延迟/漏报; 进程死后 agent 会话残影还在; 多次实测"显示死"但 /proc 里明明活着
pidof com.duowan.kiwi 取第一个 pid 会混入子进程 com.duowan.kiwi:cloudpatch / :logcat(它们的 cmdline 以包名开头); 把子进程当主进程 attach → 白费
脚本结束后看到黑屏就以为闪退 屏幕回桌面 ≠ 进程死: App 有 KeepAlive, UI Activity 被崩掉后进程会被系统保活重启, pid 可能换新
d.kill(pid) / force-stop 后说"App死了" 是我们自己杀的, 不算闪退

本次统一的事实来源 (evidence/diag_lifecycle/diag_*.json 均按此采集):

  1. 主进程存活 = /proc/<pid>/cmdline 读出来精确等于 com.duowan.kiwi(排除 :cloudpatch/:logcat 子进程)。pidof 每一 pid 都过 cmdline 过滤。
  2. UI 是否闪退 = dumpsys window | grep mCurrentFocus 里是否还包含 com.duowan.kiwi(回到 app.lawnchair 即 UI 已退出)。
  3. 是否发生崩溃 = logcat -d -b crash 是否有 Fatal/signal/Abort message。native 崩溃(如 EGL)会写 tombstone, msaoaidsec 静默 _exit 则 crash buffer 无记录——两种死法必须分开看

一、四组对照实验结果 (2026-08-27 实测)

脚本: scripts/diag_lifecycle.py [a|b|c|d], 证据: evidence/diag_lifecycle/diag_{a,b,c,d}.json

做法 主进程存活 UI 前台 crash buffer 结论
A 无 frida, monkey 正常启动 全程存活 (pid 不变) Homepage 0 无 frida 完全正常, 不闪退
B frida spawn, 挂起 0.11s 立即 resume, 不加载任何脚本 全程存活 Homepage 0 spawn 本身不杀 App!
C frida spawn, 挂起加载 2 个 bypass 脚本 (art_callsite+mask_frida) 再 resume 进程死 回桌面 1 条/轮: EGL 挂起加载脚本 → EGL 崩
D 正常启动 7s 后 attach 主进程(裸, 无脚本) 进程死(静默) 回桌面 0 attach 主进程 → msaoaidsec 静默 _exit

可重复性: B、C 各单独跑 3 轮 (2026-08-27 06:22~06:26)

轮次 B (零挂起 spawn) C (挂起+加载2脚本)
r1 存活 25.8s+ UI=Homepage crashes=0 ⚠️ 存活 26s 但 UI 解析异常 (focus=?), crashes=0
r2 存活 25.8s+ UI=Homepage crashes=0 +8.5s 进程 None, UI 回桌面, crashes=1
r3 存活 25.8s+ UI=Homepage crashes=0 +8.4s 进程 None, UI 回桌面, crashes=1
汇总 3/3 稳定存活 2/3 明确 EGL 崩, 1/3 存活但异常

C 组死亡时间线 (r3 完整轨迹): +2.1s proc 存活 UI=Homepage → +4.2s pid 换新(KeepAlive 重启) UI 回桌面 → +8.4s 无主进程; crash buffer:

signal 6 (SIGABRT) Abort message: 'Failed to create context, error = EGL_NOT_INITIALIZED'
tid RenderThread, pid <原pid>  → com.duowan.kiwi

注: 脚本 OUT 固定为 diag_c.json 每轮覆盖, 最终文件是最后一轮的数据; rounds 输出见 /tmp/diag_{b,c}.log 系列与 evidence/diag_lifecycle/。

关键崩理解读

C 组 / 今天早上 v8/v9 的 tombstone (tombstone_24~27) 全部同型:

signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x74206665722060 (ASCII 内容!)
#00 __strchr_aarch64+36  / libc.so
#01 strstr+24            / libc.so
#02 android::egl_display_t::initialize+824  / libEGL.so
#03 EglManager::initialize  / libhwui.so
#04 RenderThread::requireGlContext  / libhwui.so

以及 signal 6 (SIGABRT) Abort message: 'Failed to create context, error = EGL_NOT_INITIALIZED' → 崩溃线程是 RenderThread(EGL 初始化), 和 msaoaidsec 无关。

为什么会这样 (经 E6/E7 修正): frida spawn 会把进程挂起在非常早期(zygote fork 后、JVM/GL 尚未初始化)。关键实验 E6/E7: 挂起只 10~18ms、仅加载 1 个补丁脚本, attach 后 RenderThread 仍 100% EGL 崩 (Bad access / Trace/BPT trap, 4/4 轮) → 说明触发 EGL 崩的不是挂起时长, 而是 attach 注入本身(frida agent 初始化与 EGL 显示初始化竞争)。纯 spawn 不 attach (B组) 则完全无此问题。

D 组: attach 主进程 = 真 frida 检测。 裸 attach 无任何 hook, 2~4s 静默死, crash buffer 无记录 → libmsaoaidsec.so 检测到 agent 后 _exit(RE 仓库 STATUS: EXIT_SELF)。这与 EGL 崩是两条完全独立的死因

补充实验 E0E7: attach 与死因的精确归因 (2026-08-27 06:3006:36)

脚本: scripts/diag_egl_strategy.py, 证据: evidence/diag_egl_strategy/egl_strategy.json

case 做法 挂起 结果 死因
E0 spawn→attach(空)→resume 3ms ~2.07s pid换新(KeepAlive) 静默_exit (msaoaidsec)
E1 spawn→attach+mask_frida→resume 15ms ~2.07s pid换新 静默_exit (mask无效)
E3 spawn→resume (不attach) 0ms 存活30s+ ×2 — (B组复现)
E4 spawn→resume→1.6s后attach 1673ms attach后~2.5s死 静默_exit
E5 spawn→resume→5s后attach 5183ms attach后~4.1s死 静默_exit
E6 spawn→attach+art_callsite→resume 11~18ms 4.1s死 ×2 EGL崩 (RendererThread)
E7 spawn→attach+art_callsite+mask→resume 10~12ms 4.1s死 ×2 EGL崩 (RendererThread)

结论:

  1. attach 必定触发检测/崩溃, 无论时机(挂起中 3ms 还是延迟 5s)、无论加载什么脚本、无论挂起多短。空 attach → msaoaidsec 静默 _exit; attach+bypass 脚本 → msaoaidsec 的清理路径被补丁掉后转为 EGL 崩溃。
  2. mask_frida_maps_only.js 单独使用无效(E1 仍静默死)——RE 仓库的 maps 遮蔽思路在此版 msaoaidsec 的模拟器行为下失败, 仅真机组合(G2-0055 多面)才有效。
  3. 唯一无外挂存活路径 = 纯 spawn→resume 不 attach(B组/E3), 代价是无任何 hook 能力。

抓包三策略实测 (v11: scripts/emu_hook_zero_suspend.py, 2026-08-27 06:40)

模式 做法 结果
zero 纯spawn→resume (无hook) 存活30s+ 但0事件 (抓不到协议)
stale 挂起中加载art_callsite+mask→resume→+0.5s挂hook ⚠️ 存活~4.2s: 55事件 + actionV=234548246275beedf8d93a242ef50e8f1178c3b2 (GC3VE真实身份) → 随后EGL崩 KeepAlive换pid
race resume后2s attach+2脚本+hook attach后~2.2s静默死: 17事件(早期RESP) 无actionV

→ 抓包能力排序: stale > race > zero。 stale 在死前窗口(约4s)能完整抓到 dfpReport+actionV+dckey 链路(evidence: /tmp/v11_stale.json, 55事件), 与历史成功的 hook_emu_stable.py/emu_spawn_bypass_dfp.json 一致——"抓帧即成功, App 随后崩是固有代价"。race 模式(B组存活后延迟 attach)被 msaoaidsec 静默杀, 抓不到关键帧。


二、历史"成功"记录的真相 (为什么之前以为 attach 能用)

  • evidence/emu_attach_full.json (08-26 20:33, 48 事件) 是当时 attach 脚本的产物——但当时脚本用
    for p in d.enumerate_processes():
        if 'kiwi' in p.name or 'duowan' in p.name:  # ← 致命
    
    frida 枚举里主进程 name 显示为 "虎牙直播", 只有 com.duowan.kiwi:cloudpatch/:logcat 含 "kiwi" 字样。 → 当时 attach 到的是 cloudpatch 子进程, 从未真正 hook 主进程。 所谓"模拟器 attach 不崩"是子进程的假象。
  • hook_emu_stable.py/spawn_g2055_survival.py 的 spawn 成功: 实质是 spawn 后挂起很短(只 load 2 个极快脚本)就 resume, 命中"低挂起存活窗口"; 但 spawn_survival.json 也记录了 4 轮里 6s/6s/6s/3s 全死——大部分轮次仍触发 EGL 崩, 只是脚本在死前刚好抓到 dfp 报文就算"成功"。
  • 结论校准: spawn 路线历史上"成功即抓到 dfp"并非稳定存活, 而是抢在 EGL 崩之前的窗口抓到数; 本次 C 组 3 轮 2 崩也验证了这一点。真正稳定的是 B 组(零挂起)。

三、"验证码无法正常加载"的机制与实测

3.1 登录验证码的真实链路 (本次实测)

  1. 账号密码页点"立即登录" → 弹协议 → 同意 → 进入 OakVerifyActivity (com.huya.kiwi.crossplatform.common.webview.verification)
  2. 内部是 WebView 加载极验/数美 H5 验证页, 日志可见验证码 SDK 类 cn.fly.verify.* (数美 fly verify), UI 出现 "安全验证 / 请拖动下方滑块完成拼图"
  3. 单次流程可见 dfpReport(3800B) → actionV → dckey(744B) 链路照常上报

3.2 实测结论 (模拟器 GC3VE)

  • 无 frida (A 组): 账号密码登录 → 滑块验证码完整加载(UI dump 出现"请拖动下方滑块完成拼图"+ WebView 容器)。→ 验证码不加载不是模拟器环境的锅
  • 之前"验证码不加载"发生的场景 = App 在 frida 挂起/attach 被检测的状态下启动或运行, 或 msaoaidsec 检测到 agent 后 App 处于被风控标记状态(行为上报 Poseidon/dfp collection 带 hooked:... 字段, 服务端不下发验证 H5)。
    • 推论: 登录流程不要带 frida; 需要 hook 取证时, 用"零挂起 spawn + 抓完即杀"。

3.3 模拟器检测面 (本次静态+动态确认)

检测内容 证据
Java 层 /dev/qemu_pipe, /dev/socket/qemud, /sys/qemu_trace, /system/bin/qemu-props, libc_malloc_debug_qemu.so classes.dex 字符串; ryxq.nuj.F()
Java 层 Build.HARDWARE/PRODUCT/FINGERPRINT/BRAND goldfish/sdk/generic, ro.kernel.qemu ryxq.xzj.c()/q(), ryxq.nuj.L()/N()
Java 层 /system/build.prop, /proc/tty/drivers, /proc/cpuinfo goldfish ryxq.nuj.J()
Java 层 su 文件 (/system/bin/su 等 5 路径) ryxq.xzj.e()
Native KH_NSDKThreadDe 线程读 /system/etc/mumu-configs/currentApp 等 MuMu 特有路径 (logcat avc 可证) libnsdt.so
UI 服务端下发 EmulatorCard (HUYA.EmulatorCard) 组件 classes8/9 dex
配置式 "Get emulator files feature from config / model feature from config" (可从远程配置增删检测项) classes6.dex

本机现状: MuMu 的 fakeProductInfoIfNeed 对 com.duowan.kiwi 未匹配任何伪装模板 → App 看到的 build 属性是原始 qemu 泄漏:

ro.build.hv.platform=qemu     ← 明显泄漏
qemu.hw.mainkeys=1

其余 (ro.kernel.qemu 等) 已由 MuMu 内核隐藏。Java 层 goldfish/su 检查当前在 GC3VE 上不命中(brand=Google, fingerprint=release-keys, su 被 Magisk hide 处理), 所以无 frida 时 App 照常给验证码。


四、最终结论与可用操作路径

事实矩阵 (v11 实测修正: 2026-08-27 06:40)

想要做的事情 正确做法 实测
App 在模拟器上正常启动/正常登录/验证码加载 完全不要 attach frida A 组: 100% 正常, 验证码完整加载
仅在模拟器取证抓 dfpReport / hdid / 协议帧 stale 模式: spawn 挂起中加载 art_callsite+mask → resume → 尽快挂 SSL hook v11 stale: 存活~4.2s, 55事件+actionV 完整链路, 随后 EGL 崩属固有代价
用 frida attach 主进程长时 hook 当前不可行 (任何 attach 均死: 空attach静默_exit / attach+bypass=EGL崩) E0~E7 全灭, 与挂起时长/时机无关
纯 spawn 不 attach 可稳定存活(无 hook 能力), 适合验证 App 本身状态 B组/E3: 3/3+2/2 存活
长时间 hook 主进程 未达成 需解决 masaoaidsec (RE 仓库 B-G2-0001 阻塞仍开放) 或换非 frida 注入

给登录/验证码流程的操作要求

  1. 登录页面(UIs): 全程不启 frida。 验证码走 OakVerifyActivity 原生 WebView, 与 hook 无关。
  2. 取证抓包: 用 stale 模式单独一次 spawn (emu_hook_zero_suspend.py --mode stale), 死前窗口(约4s)即可抓到 dfpReport/actionV, 抓完 App 自然崩/被杀, 再正常启动走登录。不要用 race/attach 方式(静默死还抓不到关键帧)。
  3. 若见"验证码空白/不加载": 先 am force-stop + 正常重启 App 再试(清除 frida 痕迹); 若仍空白, 查 logcat -b crash 是否 EGL tombstone(挂起残留), 不是的话多半是服务端风控(行为上报带 hook 标记), 需要换干净设备身份。

复用脚本 (本次新增/更新)

  • scripts/diag_lifecycle.py — 四组存活/闪退/崩溃对照诊断器 (A/B/C/D), 输出 JSON 证据
  • scripts/diag_egl_strategy.py — E0~E7 attach 归因实验 (空attach/mask/art_callsite/延迟attach)
  • scripts/watch_proc_v2.py — 主进程存活监控 (cmdline 精确匹配, 不用 frida enumerate)
  • scripts/emu_hook_zero_suspend.pyv11 三策略抓包: --mode zero|race|stale (推荐 stale 抓帧)
  • scripts/emu_spawn_captcha_test.py — spawn+bypass 下验证码加载对照 (结论: spawn 挂起偶发 EGL 崩, 登录勿用)
  • scripts/emu_attach_captcha_test.py — attach 三件套对照 (结论: attach 主进程必死, 勿用于登录)
  • scripts/hook_emu_stable.py — 既有: spawn+极短挂起抓 dfpReport (stale 模式同款, 可用)

下一步(按价值排序)

  1. P0 完成 抓包脚本 v11 三策略落地: stale=抓帧正解(zero存活不可抓, race被静默杀)
  2. P1 验证纯 Python 登录链再次打通 (不依赖模拟器 UI, 项目已有零设备链路) — 作为不碰 frida 的正路
  3. P2 若确需长时 hook: 深入 masaoaidsec (RE 仓库 G2 系列, 模拟器与真机版本差异), 或换用非 frida 注入 (ptrace-free/gdb), 或尝试 masaoaidsec 检测线程启动前的时间窗 (数据: msaoaid 检测在 App 启动 ~2s 内完成, attach 窗口<2s 理论存在但 E4 已证 1.6s attach 仍死, 需 <1s 超早 attach 验证)

附录 A: 本次使用的判定代码要点

def main_pid():
    # 唯一可信: /proc/<pid>/cmdline 精确等于包名
    for p in adb pidof 的所有 pid:
        if cat /proc/<p>/cmdline == "com.duowan.kiwi": return p

def focus_ok():
    m = re.search(r"mCurrentFocus=Window\{([^}]+)\}", dumpsys window)
    return PACKAGE in m.group(1)   # 注意 u0/窗口hash在花括号内

# 崩溃: logcat -d -b crash | grep -E 'Fatal|signal|Abort message'

附录 B: 相关既有文档

  • docs/dfpReport破解进度.md 第十四~十六节 (模拟器探索/EGL 崩溃原始记录)
  • RE 仓库 STATUS.md B-G2-0001 (msaoaidsec EXIT_SELF 阻塞) / G2-0055 (maps/ELF 部分绕过)