#!/usr/bin/env python3 """真机 v7: dlopen 拦截 libhydeviceid -> attach JNI_OnLoad -> hook JNINativeInterface::RegisterNatives 拿到 libhydeviceid.so 动态注册的全部 native 方法(名字+签名+函数地址). 绕过 OLLVM 混淆: 不去静态反汇编 JNI_OnLoad, 而是运行时在 JNI_OnLoad 里用 env 的 JNINativeInterface 函数表定位 RegisterNatives 槽位并 hook, 抓 JNINativeMethod 数组. 用法: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \ scripts/phone_probe_registernatives2.py [serial] [remote] 输出: evidence/diag_phone/hdid_regtable.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_regtable.json" RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js" # JNINativeInterface 中 RegisterNatives 的索引 (JNI 1.6 规范, 含 4 个 reserved) RN_INDEX = 215 def mod_of(a): try: m = Process.findModuleByAddress(a) return (m.name, str(a.sub(m.base))) if m else ("?", "?") except Exception: return ("?", "?") JS = r""" 'use strict'; function jmod(a){ try{ var m = Process.findModuleByAddress(a); return m ? {n:m.name, off:''+a.sub(m.base)} : {n:'?',off:'?'}; }catch(e){ return {n:'?',off:'?'}; } } var hooked = false; function attachJniOnload(md){ try{ Interceptor.attach(md.base.add(0x22c4c8), { onEnter:function(a){ send({type:'jni-onload-enter', tid:Process.getCurrentThreadId()}); try{ var env = Java.vm.tryGetEnv(); var fns = env.handle.readPointer(); // JNINativeInterface* // 打印 200..230 槽位的所属模块, 验证表结构 var dump=[]; for(var i=200;i<=230;i++){ var p = fns.add(i*8).readPointer(); var m = jmod(p); dump.push({i:i, a:''+p, mod:m.n, off:m.off}); } send({type:'fns-scan', dump:dump}); var rn = fns.add(%d*8).readPointer(); send({type:'rn-addr', rn:''+rn, mod:jmod(rn)}); Interceptor.attach(rn, { onEnter:function(args){ // args[1]=jclass clazz, args[2]=JNINativeMethod* methods, args[3]=jint n var count = args[3].toInt32(); var methods = args[2]; var cls=''; try{ cls = Java.vm.tryGetEnv().getClassName(args[1]||Java.vm.tryGetEnv().getClass(args[1])); }catch(e){ cls='?'; } send({type:'reg-call', cls:cls, count:count, tid:Process.getCurrentThreadId()}); for(var i=0;i=0){ send({type:'dlopen-hydev', name:this.name}); var md = Process.findModuleByName('libhydeviceid.so'); if(md){ attachJniOnload(md); } } } }); } setInterval(hookDlopen, 40); """ % RN_INDEX 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 = {"regs": [], "fns_scan": [], "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 == "reg": result["regs"].append(p) print(f"[reg] {p.get('name')}{p.get('sig')} @ {p.get('mod')}+0x{p.get('off')} (class={p.get('cls')})", flush=True) elif t == "reg-call": print(f"[*] RegisterNatives called count={p.get('count')} cls={p.get('cls')}", flush=True) elif t == "rn-addr": print(f"[*] RegisterNatives addr 0x{p['rn']} mod={p.get('mod')}", flush=True) elif t == "fns-scan": result["fns_scan"].append(p) print("[*] JNINativeInterface 槽位 200-230:", flush=True) for e in p["dump"]: print(f" [{e['i']}] {e.get('mod')}+0x{e.get('off')}", flush=True) elif t in ("dlopen-hooked","jni-onload-hooked","rn-hooked","dlopen-hydev","jni-onload-enter"): print(f"[*] {t}", p if t=="dlopen-hydev" else "", flush=True) elif t in ("jni-err","rn-err"): print(f"[!] {t}: {p.get('e')}", flush=True) sc = session.create_script(JS) sc.on("message", on_message) sc.load() d.resume(pid) print("[*] resumed, waiting RegisterNatives...", flush=True) time.sleep(22) result["detached"] = det OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8") print(f"[*] 注册方法数: {len(result['regs'])} saved {OUT}") if __name__ == "__main__": main()