#!/usr/bin/env python3 """R39: 登录流程 md5 输入捕获 — 两阶段配方 (与 hook_final_capture.py 同款): spawn挂起 -> 双bypass装载 -> resume -> 稳定11s -> patch_guard -> 业务钩子 """ import json, time from pathlib import Path import frida REMOTE="127.0.0.1:31878"; PACKAGE="com.duowan.kiwi" RE=Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") HERE=Path(__file__).resolve().parent.parent OUT=HERE/"evidence"/("loginmd5_"+time.strftime("%H%M%S")+".json") def main(): d=frida.get_device_manager().add_remote_device(REMOTE) pid=d.spawn([PACKAGE]) print(f"[*] spawned {pid}",flush=True) s=d.attach(pid) from bypass_loader import load_bypass load_bypass(s, patch_guard_delay_ms=3000) d.resume(pid) print("[*] resumed (bypass_all: A层立即 + B层3s patch_guard)",flush=True) result={}; state={"udb":0} def on(m,_): if m.get("type")=="error": t=str(m) if "ClassNotFoundException" not in t: print(f"[JS] {t[:150]}",flush=True) return p=m.get("payload") or {} ev=p.get("event") if ev=="udb-md5": state["udb"]+=1 print(f"[+] udb-md5 #{state['udb']} in1={str(p.get('in1'))[:100]} out={str(p.get('out'))[:80]}",flush=True) elif ev=="udb-armed": print("[*] udb md5 armed",flush=True) elif ev=="java-digest": out=p.get('out','') if out.startswith('865a') or out.startswith('') and False: pass print(f"[J] digest {p.get('algo')} out={out[:16]} in={repr((p.get('in_parts') or ''))[:70]}",flush=True) result.setdefault(ev or "misc",[]).append(p) sc=s.create_script((HERE/"tools/frida/hook_login_md5.js").read_text()) sc.on("message",on); sc.load() print("[*] 请在 App 中: 退出登录 -> 账号密码重新登录 (窗口 8 分钟)",flush=True) t0=time.time() while time.time()-t0<480: time.sleep(5) if state["udb"]>=12: time.sleep(6); break json.dump(result,open(OUT,"w"),indent=2) print(f"[*] saved {OUT} udb={state['udb']}",flush=True) try: s.detach() except Exception: pass if __name__=="__main__": main()