#!/usr/bin/env python3 """T3: 抓 msaoaidsec 的真实退出途径 (raw syscall / tgkill / kill). T2 proved libc _exit/exit/abort/pthread_exit NOT used (0 calls, app still dies). This experiment hooks: - libc 'syscall' (可变参数) → 若 msaoaidsec 用 syscall(exit_group) - libc 'tgkill' / 'tkill' / 'kill' → 若自杀信号 - libc 'raise' - libc '_exit' _Exit exit exit_group (再拦一次确认) 每次命中打印 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/t3_exit_trace.json") JS = r""" 'use strict'; const PACK = 'libmsaoaidsec.so'; function bt() { const out = []; try { for (const a of Thread.backtrace(this.context, Backtracer.ACCURATE)) { const m = Process.findModuleByAddress(a); out.push(m ? (m.name === PACK ? 'MS:' + a.sub(m.base).toString() : m.name) : a.toString()); } } catch (_) {} return out.slice(0, 10); } function watch(name, nargs) { try { const addr = Module.findExportByName('libc.so', name); if (addr === null) { send({ skip: name }); return; } Interceptor.attach(addr, { onEnter(args) { const src = bt(); const ms = src.some(s => typeof s === 'string' && s.startsWith('MS:')); const argv = []; for (let i = 0; i < nargs; i++) { try { argv.push(args[i].toString()); } catch (_) { argv.push('?'); } } send({ fn: name, ms, src, argv }); } }); send({ hooked: name }); } catch (e) { send({ err: name, e: String(e) }); } } watch('syscall', 8); watch('tgkill', 3); watch('tkill', 2); watch('kill', 2); watch('raise', 1); watch('_exit', 1); watch('_Exit', 1); watch('exit', 1); watch('exit_group', 1); watch('abort', 0); 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() hits = [] def on(m, dta): if m.get('type') != 'send': return p = m.get('payload') or {} p['elapsed'] = round(time.time() - t0, 3) if 'fn' in p: hits.append(p) mark = 'MSAO!!' if p.get('ms') else ' ' print(f" [{mark} +{p['elapsed']:6.2f}s] {p['fn']} argv={p.get('argv')}", flush=True) print(f" bt={p.get('src')}", flush=True) elif 'hooked' in p: pass elif 'skip' in p: print(f" [skip] {p['skip']}", flush=True) sc = session.create_script(JS) sc.on('message', on) sc.load() d.resume(pid) print(f"[T3.r{rnd}] pid={pid} resume +{(time.time()-t0)*1000:.0f}ms", flush=True) died = None while time.time() - t0 < 15: time.sleep(1) mp = main_pid() if mp is None or mp != pid: died = time.time() - t0 break print(f"[T3.r{rnd}] {'死@+'+str(round(died,1))+'s' if died else '存活'} 命中={len(hits)}", flush=True) try: session.detach() except Exception: pass try: d.kill(pid) except Exception: pass time.sleep(1) OUT.write_text(json.dumps(hits, ensure_ascii=False, indent=1)) print(f"[*] -> {OUT}", flush=True) if __name__ == "__main__": main()