- 诊断报告: attach 主进程静默退出/EGL 崩溃, 仅约 4s 窗口可抓帧 - scripts: attach/spawn/hook/emu 系列 Frida 脚本与抓帧/验证工具 - evidence: identity/reqchain/frame/inputbuf/magic_buf/propedge 抓取样本, emu_* 存活对比, diag_* 策略实验, baseline 裸测基准
15 KiB
模拟器闪退/卡启动/验证码不加载 —— 存活与崩溃诊断报告
日期: 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 均按此采集):
- 主进程存活 =
/proc/<pid>/cmdline读出来精确等于com.duowan.kiwi(排除:cloudpatch/:logcat子进程)。pidof每一 pid 都过 cmdline 过滤。 - UI 是否闪退 =
dumpsys window | grep mCurrentFocus里是否还包含com.duowan.kiwi(回到app.lawnchair即 UI 已退出)。 - 是否发生崩溃 =
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) |
结论:
- attach 必定触发检测/崩溃, 无论时机(挂起中 3ms 还是延迟 5s)、无论加载什么脚本、无论挂起多短。空 attach → msaoaidsec 静默
_exit; attach+bypass 脚本 → msaoaidsec 的清理路径被补丁掉后转为 EGL 崩溃。 mask_frida_maps_only.js单独使用无效(E1 仍静默死)——RE 仓库的 maps 遮蔽思路在此版 msaoaidsec 的模拟器行为下失败, 仅真机组合(G2-0055 多面)才有效。- 唯一无外挂存活路径 = 纯 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 脚本的产物——但当时脚本用frida 枚举里主进程 name 显示为 "虎牙直播", 只有for p in d.enumerate_processes(): if 'kiwi' in p.name or 'duowan' in p.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 登录验证码的真实链路 (本次实测)
- 账号密码页点"立即登录" → 弹协议 → 同意 → 进入
OakVerifyActivity(com.huya.kiwi.crossplatform.common.webview.verification) - 内部是 WebView 加载极验/数美 H5 验证页, 日志可见验证码 SDK 类
cn.fly.verify.*(数美 fly verify), UI 出现 "安全验证 / 请拖动下方滑块完成拼图" - 单次流程可见 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 注入 |
给登录/验证码流程的操作要求
- 登录页面(UIs): 全程不启 frida。 验证码走
OakVerifyActivity原生 WebView, 与 hook 无关。 - 取证抓包: 用 stale 模式单独一次 spawn (
emu_hook_zero_suspend.py --mode stale), 死前窗口(约4s)即可抓到 dfpReport/actionV, 抓完 App 自然崩/被杀, 再正常启动走登录。不要用 race/attach 方式(静默死还抓不到关键帧)。 - 若见"验证码空白/不加载": 先
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.py— v11 三策略抓包:--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 模式同款, 可用)
下一步(按价值排序)
- P0 ✅完成 抓包脚本 v11 三策略落地: stale=抓帧正解(zero存活不可抓, race被静默杀)
- P1 验证纯 Python 登录链再次打通 (不依赖模拟器 UI, 项目已有零设备链路) — 作为不碰 frida 的正路
- 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.mdB-G2-0001 (msaoaidsec EXIT_SELF 阻塞) / G2-0055 (maps/ELF 部分绕过)