docs(huya): 模拟器存活闪退诊断报告与 Frida 探测脚本证据
- 诊断报告: attach 主进程静默退出/EGL 崩溃, 仅约 4s 窗口可抓帧 - scripts: attach/spawn/hook/emu 系列 Frida 脚本与抓帧/验证工具 - evidence: identity/reqchain/frame/inputbuf/magic_buf/propedge 抓取样本, emu_* 存活对比, diag_* 策略实验, baseline 裸测基准
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""T2: 拦截 msaoaidsec 的退出动作 (找到检测点 + 让App活下来).
|
||||
|
||||
spawn -> attach(挂起) -> 加载【exit拦截脚本】:
|
||||
- Interceptor.replace libc _exit/exit/exit_group/_Exit/abort
|
||||
- 若调用者 backtrace 含 libmsaoaidsec.so → 吞掉该退出(不真正退出), 记录 backtrace
|
||||
- 其他来源(Java System.exit等) → 放行原函数
|
||||
resume 后观察 App 是否存活 (若存活并能看到 msaoaidsec backtrace = 检测点找到+绕过成功)
|
||||
"""
|
||||
from pathlib import Path
|
||||
import frida, time, json, subprocess, sys
|
||||
|
||||
REMOTE = "127.0.0.1:31878"
|
||||
ADB = ["adb", "-s", "127.0.0.1:5555"]
|
||||
PACKAGE = "com.duowan.kiwi"
|
||||
OUT = Path("/tmp/t2_exit_block.json")
|
||||
|
||||
INTERCEPT_JS = r"""
|
||||
'use strict';
|
||||
const PACK = 'libmsaoaidsec.so';
|
||||
let swallowed = 0, passthrough = 0;
|
||||
const log = [];
|
||||
|
||||
function bt() {
|
||||
const src = [];
|
||||
try {
|
||||
for (const a of Thread.backtrace(this.context, Backtracer.ACCURATE)) {
|
||||
const m = Process.findModuleByAddress(a);
|
||||
src.push(m ? (m.name === PACK ? a.sub(m.base).toString() : m.name) : a.toString());
|
||||
}
|
||||
} catch (_) {}
|
||||
return src;
|
||||
}
|
||||
|
||||
function make(name, retType, argTypes) {
|
||||
try {
|
||||
const addr = Module.findExportByName('libc.so', name);
|
||||
if (addr === null) return;
|
||||
const orig = new NativeFunction(addr, retType, argTypes);
|
||||
Interceptor.replace(addr, new NativeCallback(function () {
|
||||
const src = bt();
|
||||
const fromMsao = src.some(s => typeof s === 'string' && s.startsWith('0x'));
|
||||
if (fromMsao) {
|
||||
swallowed++;
|
||||
log.push({ fn: name, msao: true, src: src.slice(0, 8) });
|
||||
send({ sw: name, n: swallowed });
|
||||
return; // 吞掉 msaoaidsec 的退出
|
||||
}
|
||||
passthrough++;
|
||||
send({ pass: name, src: src.slice(0, 6) });
|
||||
return orig.apply(null, arguments);
|
||||
}, retType, argTypes));
|
||||
send({ installed: name });
|
||||
} catch (e) { send({ err: name, e: String(e) }); }
|
||||
}
|
||||
|
||||
make('_exit', 'void', ['int']);
|
||||
make('_Exit', 'void', ['int']);
|
||||
make('exit', 'void', ['int']);
|
||||
make('exit_group', 'void', ['int']);
|
||||
make('abort', 'void', []);
|
||||
// 线程退出也要看 (pthread_exit 常用于静默死)
|
||||
try {
|
||||
const pe = Module.findExportByName('libc.so', 'pthread_exit');
|
||||
const origPe = new NativeFunction(pe, 'void', ['pointer']);
|
||||
Interceptor.replace(pe, new NativeCallback(function (retval) {
|
||||
const src = bt();
|
||||
if (src.some(s => typeof s === 'string' && s.startsWith('0x'))) {
|
||||
swallowed++;
|
||||
log.push({ fn: 'pthread_exit', msao: true, src: src.slice(0, 8) });
|
||||
send({ sw: 'pthread_exit', n: swallowed });
|
||||
return; // 吞掉
|
||||
}
|
||||
return origPe(retval);
|
||||
}, 'void', ['pointer']));
|
||||
send({ installed: 'pthread_exit' });
|
||||
} catch (e) { send({ err: 'pthread_exit', e: String(e) }); }
|
||||
|
||||
send({ ready: true });
|
||||
"""
|
||||
|
||||
def sh(*a): return subprocess.run(a, capture_output=True, text=True)
|
||||
def adb(*a): return sh(*ADB, *a)
|
||||
|
||||
def main_pid():
|
||||
r = adb("shell", "pidof", PACKAGE)
|
||||
for p in r.stdout.strip().split():
|
||||
rr = adb("shell", "cat", f"/proc/{p}/cmdline")
|
||||
if rr.stdout.strip("\x00").strip() == PACKAGE:
|
||||
return int(p)
|
||||
return None
|
||||
|
||||
def main():
|
||||
d = frida.get_device_manager().add_remote_device(REMOTE)
|
||||
for rnd in (1, 2, 3):
|
||||
adb("shell", "am", "force-stop", PACKAGE)
|
||||
time.sleep(1.5)
|
||||
adb("shell", "logcat", "-c")
|
||||
pid = d.spawn([PACKAGE])
|
||||
session = d.attach(pid)
|
||||
t0 = time.time()
|
||||
events = []
|
||||
def on(m, dta):
|
||||
if m.get('type') != 'send': return
|
||||
p = m.get('payload') or {}
|
||||
p['elapsed'] = round(time.time() - t0, 2)
|
||||
events.append(p)
|
||||
if 'sw' in p:
|
||||
print(f" [+{p['elapsed']}s] 吞掉 msaoaidsec {p['sw']} (#{p['n']})", flush=True)
|
||||
elif 'pass' in p:
|
||||
print(f" [+{p['elapsed']}s] 放行 {p['pass']} src={p['src'][:4]}", flush=True)
|
||||
elif 'installed' in p:
|
||||
print(f" [+{p['elapsed']}s] hook {p['installed']}", flush=True)
|
||||
sc = session.create_script(INTERCEPT_JS)
|
||||
sc.on('message', on)
|
||||
sc.load()
|
||||
d.resume(pid)
|
||||
print(f"[T2.r{rnd}] spawn pid={pid} + exit拦截, resume +{(time.time()-t0)*1000:.0f}ms", flush=True)
|
||||
died = None
|
||||
while time.time() - t0 < 20:
|
||||
time.sleep(1.5)
|
||||
mp = main_pid()
|
||||
if mp is None or mp != pid:
|
||||
died = time.time() - t0
|
||||
break
|
||||
# 抓 backtrace 详情
|
||||
swallows = [e for e in events if 'sw' in e]
|
||||
print(f"[T2.r{rnd}] 结果: {'死@+'+str(round(died,1))+'s' if died else '存活20s'} 吞掉={len(swallows)} 放行={sum(1 for e in events if 'pass' in e)}", flush=True)
|
||||
# 打印最后几次吞掉的 backtrace (从 py 侧拿不到 log, 用 events 里的 sw 计数即可)
|
||||
try: session.detach()
|
||||
except Exception: pass
|
||||
try: d.kill(pid)
|
||||
except Exception: pass
|
||||
time.sleep(1)
|
||||
OUT.write_text(json.dumps(events, ensure_ascii=False, indent=1))
|
||||
print(f"[*] -> {OUT}", flush=True)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user