#!/usr/bin/env python3 """真机 v6: hook SSL_write 抓 hypasswordLogin 登录帧, 提取实际 hdid + 追来源. 目标: 确认登录帧 t1.t0 的 32hex hdid 实际值(应为金样本 ed0db8...), 并 hook 它进入 WUP 帧前被读取的调用链。同时 hook: - libc strstr/strcpy 等 (登录帧组装时 hdid 字符串被拷贝处) - BusinessCfg::getSafeDeviceId(0x26a3c4) / setDeviceInfo(0x26a894) 用法: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \ scripts/phone_probe_hdid6.py [serial] [remote] 输出: evidence/diag_phone/hdid_probe6.json """ from __future__ import annotations import json import re import subprocess import sys import time from pathlib import Path import frida SERIAL = sys.argv[1] if len(sys.argv) > 1 else "5dd8c93f" REMOTE = sys.argv[2] if len(sys.argv) > 2 else "127.0.0.1:31878" PACKAGE = "com.duowan.kiwi" LOGIN_ACT = "com.duowan.kiwi/.loginui.impl.gamesdk.GameSdkLoginActivity" REPO = Path("/Users/yml/codes/douyu_login_py") OUT = REPO / "evidence" / "diag_phone" / "hdid_probe6.json" RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js" JS = r""" 'use strict'; 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 '';}} function stdstr(p){ try{ if(p.isNull()) return ''; var f = p.readU8(); if((f&1)===0){ var l=f>>1; return l? p.add(1).readUtf8String(l):''; } else { var d=p.readPointer(); var l=p.add(8).readU64(); return (l&&l<512)? d.readUtf8String(l):''; } }catch(e){ return ''; } } var sslDone=false, udbHooked=false; function hookSSL(){ if(sslDone) return; 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,600))||''; }catch(e){} if(h.indexOf('hypasswordLogin')>=0){ send({type:'wup_login', len:len, hex:hexb(a[1],len)}); } }}); }); send({type:'sslwrite-hooked'}); }catch(e){} sslDone=true; } function hookUdb(){ if(udbHooked) return; var md = Process.findModuleByName('libudbauthunify.so'); if(!md) return; udbHooked=true; send({type:'udb', base:''+md.base}); // 设备信息读取点 try{ Interceptor.attach(md.base.add(0x26a3c4), { // getSafeDeviceId onLeave:function(ret){ send({type:'getsd', ret:''+ret}); } }); }catch(e){} try{ Interceptor.attach(md.base.add(0x26a894), { // setDeviceInfo(a1=this, a2..a5=std::string*) onEnter:function(a){ send({type:'setdi', args:[stdstr(a[1]), stdstr(a[2]), stdstr(a[3]), stdstr(a[4])]}); } }); }catch(e){} send({type:'udb-hooked'}); } // libhydeviceid 导出函数调用探测 var hyTried=false; function hookHy(){ if(hyTried) return; var md = Process.findModuleByName('libhydeviceid.so'); if(!md) return; hyTried=true; send({type:'hydev', base:''+md.base}); ['hyfopen64','hy_read','hy_syscall','hy_write','hyftell'].forEach(function(n){ try{ var a = Module.findExportByName('libhydeviceid.so', n); if(a) Interceptor.attach(a,{onEnter:function(args){ var s=''; try{ s=args[0].readCString(128)||''; }catch(e){} send({type:'hycall', fn:n, a0:s, tid:Process.getCurrentThreadId()}); }}); }catch(e){} }); send({type:'hydev-hooked'}); } setInterval(hookSSL, 800); setInterval(hookUdb, 50); setInterval(hookHy, 50); """ def adb(*a): return subprocess.run(["adb", "-s", SERIAL, *a], capture_output=True, text=True) def main(): OUT.parent.mkdir(parents=True, exist_ok=True) adb("shell", "am", "force-stop", PACKAGE) time.sleep(1.5) d = frida.get_device_manager().add_remote_device(REMOTE) pid = d.spawn([PACKAGE]) print(f"[*] spawn pid={pid}", flush=True) session = d.attach(pid) det = [] session.on("detached", lambda r, dd: det.append({"reason": r, "detail": str(dd)}) or print(f"[*] detached {r} {dd}", flush=True)) session.create_script(ART_CALLSITE.read_text()).load() result = {"frames": [], "events": [], "detached": det} def on_message(m, data): if m.get("type") == "error": print("[JS-ERR]", str(m)[:200], flush=True); return p = m.get("payload") or {} t = p.get("type") result["events"].append(p) if t == "wup_login": result["frames"].append(p) b = bytes.fromhex(p.get("hex", "")) # 提取帧里的 32hex hdid h32 = re.findall(rb"[0-9a-f]{32}", b) print(f"[WUP_LOGIN] len={p.get('len')} hdid候选: {[h.decode() for h in h32[:3]]}", flush=True) elif t in ("udb","udb-hooked","sslwrite-hooked","hydev","hydev-hooked"): print(f"[*] {t}", p if t=="udb" else "", flush=True) elif t == "setdi": print(f"[setDeviceInfo] args={p.get('args')}", flush=True) elif t == "getsd": print(f"[getSafeDeviceId] ret={p.get('ret')}", flush=True) elif t == "hycall": print(f"[hycall] {p.get('fn')} a0={p.get('a0')}", flush=True) sc = session.create_script(JS) sc.on("message", on_message) sc.load() d.resume(pid) print("[*] resumed", flush=True) time.sleep(4) print("[*] 触发登录...", flush=True) adb("shell", "am", "start", "-n", LOGIN_ACT) time.sleep(18) result["detached"] = det OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8") print(f"[*] 登录帧数: {len(result['frames'])} saved {OUT}") if __name__ == "__main__": main()