#!/usr/bin/env python3 """真机 v8: 用 Java 层直接调用 com.huya.security.hydeviceid.NativeEntry 采集 hdid 族. 拿到 libhydeviceid 注册表后, 用 frida Java.use 调 NativeEntry 的 native 方法: init() -> getHDID()/getSDID()/getCDID()/getGUID()/getMID() 直接可靠读取每台真机的 hdid(32hex) 及同族设备 ID —— 这是多机采集 hdid 池的前提. 同时 hook getHDID 的 native 入口(0x20f0c8)记录调用栈确认来源. 用法: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \ scripts/phone_read_hdid.py [serial] [remote] 输出: evidence/diag_phone/hdid_read.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" REPO = Path("/Users/yml/codes/douyu_login_py") OUT = REPO / "evidence" / "diag_phone" / "hdid_read.json" RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js" GETHDID_OFF = 0x20F0C8 # libhydeviceid.so 相对偏移 JS = r""" 'use strict'; var NE_CLASS = 'com.huya.security.hydeviceid.NativeEntry'; var nativeHooked = false; var GETS = [ {n:'getCDID', off:0x20eb08}, {n:'getSDID', off:0x20ede8}, {n:'getHDID', off:0x20f0c8}, {n:'getGUID', off:0x20f8c8}, {n:'getMID', off:0x20fba8} ]; function readJstr(env, ret){ try{ var fns = env.readPointer(); // JNINativeInterface* var g = new NativeFunction(fns.add(169*8).readPointer(), 'pointer', ['pointer','pointer','pointer']); // GetStringUTFChars var rel = new NativeFunction(fns.add(170*8).readPointer(), 'void', ['pointer','pointer','pointer']); // ReleaseStringUTFChars var cv = g(env, ret, ptr(0)); if(cv && !cv.isNull()){ var s=cv.readCString()||''; rel(env, ret, cv); return s; } return ''; }catch(e){ return ''; } } function hookAll(){ if(nativeHooked) return; var m = Process.findModuleByName('libhydeviceid.so'); if(!m) return; nativeHooked = true; GETS.forEach(function(g){ try{ Interceptor.attach(m.base.add(g.off), { onEnter:function(a){ this.env = a[0]; this.name = g.n; }, onLeave:function(ret){ send({type:'getval', name:this.name, str:readJstr(this.env, ret)}); } }); }catch(e){} }); send({type:'all-hooked', mods:GETS.map(function(g){return g.n;})}); } setInterval(hookAll, 300); Java.perform(function(){ function tryRead(){ try{ var NE = Java.use('com.huya.security.hydeviceid.NativeEntry'); send({type:'class-found', cls:NE_CLASS}); // 先 init (可能构造指纹), 再逐个读 var out = {}; ['init','getHDID','getSDID','getCDID','getGUID','getMID'].forEach(function(m){ try{ out[m] = NE[m](); }catch(e){ out[m] = ''; } }); send({type:'read', values:out}); }catch(e){ send({type:'java-err', e:String(e)}); } } // 类可能尚未注册(等 libhydeviceid 加载) var n=0; var timer = setInterval(function(){ n++; try{ Java.use('com.huya.security.hydeviceid.NativeEntry'); clearInterval(timer); tryRead(); }catch(e){ if(n>60){ clearInterval(timer); send({type:'cls-not-found'}); } } }, 500); }); """ 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 = {"read": None, "nativetrap": [], "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 == "read": result["read"] = p["values"] print("[*] NativeEntry 读取:", flush=True) for k, v in p["values"].items(): print(f" {k} = {v}", flush=True) elif t == "getval": print(f"[getval] {p.get('name')} = {p.get('str')}", flush=True) # 存起来 result.setdefault("getvals", []).append(p) elif t == "all-hooked": print(f"[*] all get methods hooked: {p.get('mods')}", flush=True) elif t in ("class-found","native-hooked"): print(f"[*] {t}", flush=True) elif t == "cls-not-found": print("[!] 类未找到(可能未加载)", flush=True) elif t == "java-err": print(f"[java-err] {p.get('e')}", flush=True) sc = session.create_script(JS) sc.on("message", on_message) sc.load() d.resume(pid) print("[*] resumed, waiting for class + read...", flush=True) time.sleep(35) 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()