#!/usr/bin/env python3 """真机探针 v4: 抓 libhydeviceid.so 生成 hdid 的输入(系统属性/文件/随机源). libhydeviceid.so 加壳, 生成函数不导出。改为抓它的"输入": - hook libc __system_property_get (spawn 挂起时已加载) -> App 启动读的属性 - hook libhydeviceid 的 hy_fopen64/hy_read/hy_syscall (壳文件包装) -> 访问的文件 - hook getHdid(0x26a484) 时同时 dump x0 原始字节, 定位为何返回空 这能判断 hdid 依赖 ro.serialno / ANDROID_ID / /proc 信息 / 随机源等, 决定能否复现. 用法: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \ scripts/phone_probe_hdid4.py [serial] [remote] 输出: evidence/diag_phone/hdid_probe4.json """ from __future__ import annotations import json 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_probe4.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 stdstr(p){ try{ if(p.isNull()) return ''; var first = p.readU8(); if((first&1)===0){ var l=first>>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 ''; } } // 1) spawn 挂起时 hook __system_property_get (libc 已加载) (function(){ try{ var t = Module.findExportByName('libc.so','__system_property_get'); if(!t){ send({type:'no-propget'}); return; } Interceptor.attach(t,{ onEnter:function(a){ this.k = a[0].readCString()||''; this.vp = a[1]; }, onLeave:function(ret){ try{ send({type:'prop', k:this.k, v:this.vp.readCString()||''}); }catch(e){ send({type:'prop', k:this.k, v:''}); } } }); send({type:'propget-hooked'}); }catch(e){ send({type:'propget-err', e:String(e)}); } })(); var hydev=null; function hookHydev(){ if(hydev) return; var md = Process.findModuleByName('libhydeviceid.so'); if(!md) return; hydev = md; send({type:'hydev', base:''+md.base}); // 文件操作壳包装: hy_fopen64(0xf1298), hy_read(0x30bef0), hy_syscall(0x30bf3c) var targets = {0xf1298:'hy_fopen64', 0x30bef0:'hy_read', 0x30bf3c:'hy_syscall', 0x30bf00:'hy_write', 0xf3360:'hyftell'}; Object.keys(targets).forEach(function(off){ try{ Interceptor.attach(md.base.add(parseInt(off,16)), { onEnter:function(a){ var s=''; try{ s = (a[0]&&!a[0].isNull()) ? a[0].readCString(256)||'' : ''; }catch(e){} send({type:'hyio', fn:targets[off], arg0:s, tid:Process.getCurrentThreadId()}); } }); }catch(e){} }); send({type:'hyio-hooked'}); } // getHdid 返回空排查: dump x0 原始字节 function hookUdb(){ var md = Process.findModuleByName('libudbauthunify.so'); if(!md) return; try{ Interceptor.attach(md.base.add(0x26a484), { onLeave:function(ret){ var s=stdstr(ret); var hex=''; try{ hex = Array.from(new Uint8Array(ret.readByteArray(64))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join(''); }catch(e){} send({type:'hdid', str:s, ret:''+ret, hex:hex, tid:Process.getCurrentThreadId()}); } }); send({type:'hdid-hooked'}); }catch(e){ send({type:'udb-err', e:String(e)}); } } setInterval(hookHydev, 40); setInterval(hookUdb, 40); """ 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 = {"props": [], "hyio": [], "hdid": [], "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") if t == "prop": result["props"].append(p); print(f"[prop] {p.get('k')} = {p.get('v')}", flush=True) elif t == "hyio": result["hyio"].append(p); print(f"[hyio] {p.get('fn')} arg0={p.get('arg0')}", flush=True) elif t == "hdid": result["hdid"].append(p); print(f"[hdid] str={p.get('str')!r} ret={p.get('ret')} hex={p.get('hex')}", flush=True) elif t in ("propget-hooked","hydev","hyio-hooked","hdid-hooked","no-propget"): print(f"[*] {t}", 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("[*] 触发登录 Activity...", flush=True) adb("shell", "am", "start", "-n", LOGIN_ACT) time.sleep(15) result["detached"] = det OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8") print(f"[*] saved {OUT}") if __name__ == "__main__": main()