#!/usr/bin/env python3 """模拟器 attach 版 dfpReport 捕获 (轮询重启 attach, 覆盖冷启动窗口)。 App 在模拟器上 spawn 会 EGL 崩, 所以用 attach。但 attach 只能抓 attach 之后的请求, 而 dfpReport 在冷启动早期就发。解法: 脚本后台反复 attach, 若 App 重启则重新 attach, 每次保住最长的 hook 窗口。配合外部 force-stop 重启 App 来完整覆盖。 """ from __future__ import annotations import frida, time, json from pathlib import Path REMOTE = "127.0.0.1:31878" PACKAGE = "com.duowan.kiwi" OUT = Path("/Users/yml/codes/douyu_login_py/evidence/emu_attach_dfp.json") JS = r""" 'use strict'; send({type:'armed'}); function hexb(p,n){try{return Array.from(new Uint8Array(p.readByteArray(n))).map(b=>('0'+b.toString(16)).slice(-2)).join('');}catch(e){return '';}} var n=0; try{ var r=new ApiResolver('module'); r.enumerateMatchesSync('exports:*!SSL_write').forEach(function(m){ Interceptor.attach(m.address,{onEnter:function(a){ var len=a[2].toInt32(); if(len<50||len>50000)return; var h='';try{h=a[1].readCString(Math.min(len,1500));}catch(e){return;} if(h.indexOf('dfpReport')>=0||h.indexOf('dckey')>=0){ n++; send({cls:h.indexOf('dfpReport')>=0?'dfpReport':'dckey',n:n,len:len,t:Date.now(),hex:hexb(a[1],len)}); } }}); }); send({type:'hooked'}); }catch(e){send({type:'err',e:String(e)});} """ def main(): d = frida.get_device_manager().add_remote_device(REMOTE) events = [] seen_sessions = set() print(f"[*] attach 循环开始 (OUT={OUT})", flush=True) try: while True: # 找 kiwi 进程 pid = None try: for p in d.enumerate_processes(): if 'kiwi' in p.name or 'duowan' in p.name: pid = p.pid break except Exception: pass if pid and pid not in seen_sessions: try: s = d.attach(pid) seen_sessions.add(pid) print(f"[*] attached pid={pid}", flush=True) sc = s.create_script(JS) sc.on('message', lambda m, data: _on(m, events)) sc.load() print("[*] hooked", flush=True) # 保留这个会话 _keep[pid] = (s, sc) except Exception as e: print(f"[*] attach {pid} err {e}", flush=True) time.sleep(3) except KeyboardInterrupt: pass OUT.write_text(json.dumps(events)) print(f"[*] saved {len(events)} -> {OUT}", flush=True) _keep = {} def _on(message, events): if message.get('type') != 'send': if message.get('type') == 'error': print("[JS-ERR]", str(message)[:150], flush=True) return p = message.get('payload') or {} t = p.get('type') if t == 'armed': return elif t == 'hooked': print("[*] ssl hooked on this attach", flush=True) elif t == 'err': print("[*] err", p.get('e'), flush=True) elif 'cls' in p: import re print(f"[{p.get('t')}] {p.get('cls')} len={p.get('len')}", flush=True) events.append(p) OUT.write_text(json.dumps(events)) if __name__ == "__main__": main()