#!/usr/bin/env python3 """真机 E2 v2: init()/get* 库内定向输入图 — 带参数值快照的完整输入清单. v2 关键修正: - lib 文件/ioctl 读走自身 hy_syscall(内联 svc), libc 钩子看不见 (E2 v1 实测: 窗口内只有 futex/gettid 类系统调用). 直挂: hy_syscall @0x30bf3c (nr=a1..a3 -> 解码 openat/read/ioctl/close...) hyfopen64 @0xf1298 (path) hy_read @0x30bef0 (fp/buf/len, 试探性记录) - 窗口从"单线程"改为"全局计数"(init 可能跨线程); 归属过滤用 returnAddress 是否落在 lib 区间(精确, 不用 FUZZY backtrace) - 参数全部从 this.context.xN 寄存器快照取(跨 frida 版本稳定) 用法: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \ scripts/phone_init_inputmap.py [serial] [remote] 输出: evidence/diag_phone/init_inputmap.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" / "init_inputmap.json" RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0") ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js" WINDOW_OFFS = { "init": 0x20E6E4, "getGUID": 0x20F8C8, "getCDID": 0x20EB08, "getSDID": 0x20EDE8, "getHDID": 0x20F0C8, "getMID": 0x20FBA8, } HY_SYSCALL_OFF = 0x30BF3C HY_FOPEN64_OFF = 0xF1298 HY_READ_OFF = 0x30BEF0 # aarch64 关键系统调用号 (linux asm-generic) SYSCALLS = { 29: "ioctl", 56: "openat", 57: "close", 63: "read", 64: "write", 65: "readv", 67: "pread64", 78: "readlinkat", 79: "fstat", 61: "lseek", 97: "futex", 98: "futex2", 99: "set_robust_list", 113: "clock_gettime", 169: "gettimeofday", 171: "adjtimex", 174: "rt_sigaction", 178: "gettid", 222: "mmap", 226: "mprotect", 278: "getrandom", 291: "statx", 263: "faccessat2", 258: "newfstatat", } JS_TEMPLATE = r""" 'use strict'; var LIB = 'libhydeviceid.so'; var WINDOW = __WINDOW_JSON__; var libBase = null, libSize = 0; var inLibWindow = 0; var hooked = false; var SYSCALL = __SYSCALL_JSON__; function inWindow(){ return inLibWindow > 0; } function inLibCall(inv){ try{ var ra = inv.returnAddress; if (!ra || ra.isNull()) return false; return (ra.compare(libBase) >= 0 && ra.compare(libBase.add(libSize)) < 0); }catch(e){ return false; } } function snap(inv){ var c = inv.context; return { x0: ptr(c.x0), x1: ptr(c.x1), x2: ptr(c.x2), x3: ptr(c.x3) }; } function hookAll(){ if(hooked) return; var md = Process.findModuleByName(LIB); if(!md){ return; } libBase = md.base; libSize = md.size; hooked = true; Object.keys(WINDOW).forEach(function(fn){ try{ Interceptor.attach(libBase.add(parseInt(WINDOW[fn], 16)), { onEnter: function(){ inLibWindow++; send({type:'w-open', fn:fn, tid:Process.getCurrentThreadId()}); }, onLeave: function(){ inLibWindow--; send({type:'w-close', fn:fn}); } }); }catch(e){ send({type:'w-err', fn:fn, e:String(e)}); } }); send({type:'windows-hooked'}); function addHook(name, t, onEnter, onLeave){ try{ if(!t) return; Interceptor.attach(t, { onEnter: function(){ if(!inWindow()) return; if(!inLibCall(this)) return; var s = snap(this); this._valid = true; try{ onEnter(this, s); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); } }, onLeave: function(){ if(!this._valid) return; try{ onLeave(this); }catch(e){ send({type:'hook-err', name:name, e:String(e).slice(0,120)}); } } }); }catch(e){ send({type:'attach-err', name:name, e:String(e).slice(0,120)}); } } // ---- libc 层 (兜底: 仍可能走 libc 的路径) addHook('libc:__system_property_get', Module.findExportByName('libc.so', '__system_property_get'), function(th, s){ th._key = s.x0.readCString() || ''; th._vp = s.x1; }, function(th){ var v = ''; try{ v = th._vp.readCString() || ''; }catch(e){} send({type:'prop', tid:Process.getCurrentThreadId(), k:th._key, v:v}); }); addHook('libc:openat', Module.findExportByName('libc.so', 'openat'), function(th, s){ th._p = s.x1.readCString() || '?'; }, function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); }); addHook('libc:open', Module.findExportByName('libc.so', 'open'), function(th, s){ th._p = s.x0.readCString() || '?'; }, function(th){ send({type:'open', tid:Process.getCurrentThreadId(), p:th._p, fd:th.returnValue.toInt32()}); }); ['read','pread','pread64'].forEach(function(n){ addHook('libc:'+n, Module.findExportByName('libc.so', n), function(th, s){ th._fd = s.x0.toInt32(); th._buf = s.x1; th._len = s.x2.toInt32(); }, function(th){ var n = th.returnValue.toInt32(); if(n <= 0) return; var cnt = Math.min(n, 512); var hex = ''; try{ hex = th._buf.readByteArray(cnt) ? Array.from(new Uint8Array(th._buf.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} send({type:'read', tid:Process.getCurrentThreadId(), fd:th._fd, n:n, hex:hex}); }); }); addHook('libc:ioctl', Module.findExportByName('libc.so', 'ioctl'), function(th, s){ th._fd = s.x0.toInt32(); th._req = s.x1.toInt32(); }, function(th){ send({type:'ioctl', tid:Process.getCurrentThreadId(), fd:th._fd, req:th._req}); }); addHook('libc:syscall', Module.findExportByName('libc.so', 'syscall'), function(th, s){ th._nr = s.x0.toInt32(); th._a1 = s.x1.toInt32(); th._a2 = s.x2.toInt32(); }, function(th){ send({type:'sys', tid:Process.getCurrentThreadId(), nr:th._nr, a1:th._a1, a2:th._a2}); }); // ---- lib 自身壳 (主路径) addHook('hy_syscall', libBase.add(HY_SYSCALL_OFF), function(th, s){ th._nr = s.x0.toInt32(); th._a1 = s.x1; th._a2 = s.x2; th._a3 = s.x3; var name = SYSCALL[th._nr] || ('nr'+th._nr); send({type:'hysys', tid:Process.getCurrentThreadId(), nr:th._nr, name:name, a1:th._a1.toString(), a2:th._a2.toString(), a3:th._a3.toString()}); }, function(th){ var name = SYSCALL[th._nr] || ('nr'+th._nr); if (name === 'read' || name === 'pread64') { var n = th.returnValue.toInt32(); if (n > 0) { var cnt = Math.min(n, 512); var hex = ''; try{ hex = th._a2.readByteArray(cnt) ? Array.from(new Uint8Array(th._a2.readByteArray(cnt))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} send({type:'hysys-read', tid:Process.getCurrentThreadId(), nr:th._nr, name:name, n:n, hex:hex}); } } else if (name === 'openat') { try{ var p = th._a1.readCString() || '?'; send({type:'hysys-open', tid:Process.getCurrentThreadId(), p:p, fd:th.returnValue.toInt32()}); }catch(e){} } }); addHook('hyfopen64', libBase.add(HY_FOPEN64_OFF), function(th, s){ try{ th._p = s.x0.readCString() || '?'; }catch(e){ th._p = '?'; } }, function(th){ send({type:'hyfopen', tid:Process.getCurrentThreadId(), p:th._p, ret:th.returnValue.toString()}); }); addHook('hy_read', libBase.add(HY_READ_OFF), function(th, s){ th._x0 = s.x0.toString(); th._buf = s.x1; th._len = s.x2.toInt32(); }, function(th){ var n = th.returnValue.toInt32(); var hex = ''; if (n > 0) { try{ hex = th._buf.readByteArray(Math.min(n,512)) ? Array.from(new Uint8Array(th._buf.readByteArray(Math.min(n,512)))).map(function(b){return ('0'+b.toString(16)).slice(-2);}).join('') : ''; }catch(e){} } send({type:'hyread', tid:Process.getCurrentThreadId(), x0:th._x0, len:th._len, n:n, hex:hex}); }); send({type:'libc-hooked'}); } setInterval(function(){ try{ hookAll(); }catch(e){} }, 150); setTimeout(function(){ Java.perform(function(){ try{ var NE = Java.use('com.huya.security.hydeviceid.NativeEntry'); send({type:'cls-ok'}); var out = {}; ['init','getGUID','getCDID','getHDID','getMID','getSDID'].forEach(function(m){ try{ if(m==='init'){ NE[m](); out[m]='ok'; } else{ var v = NE[m](); out[m] = v; send({type:'val', m:m, v:v}); } }catch(e){ out[m]='ERR:'+String(e).slice(0,60); } }); send({type:'all-vals', out:out}); }catch(e){ send({type:'java-err', e:String(e).slice(0,150)}); } }); }, 6000); """ 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 = {"events": [], "values": {}, "lib": None, "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 in ("prop", "open", "ioctl", "read", "sys", "hysys", "hysys-read", "hysys-open", "hyfopen", "hyread"): result["events"].append(p) # 打印去重: 同一(key)只打印前几次 key = (t, p.get("k") or p.get("p") or p.get("name") or str(p.get("nr")) or str(p.get("fd")), p.get("v") or str(p.get("n") or "")) if result["values"].get("_print") is None: result["values"]["_print"] = 0 if result["values"]["_print"] < 400: result["values"]["_print"] += 1 if t == "hysys": print(f"[hysys] {p.get('name')} nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')} a3={p.get('a3')}", flush=True) elif t == "hysys-read": print(f"[hysys-read] {p.get('name')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True) elif t == "hysys-open": print(f"[hysys-open] {p.get('p')} fd={p.get('fd')}", flush=True) elif t == "hyfopen": print(f"[hyfopen] {p.get('p')} ret={p.get('ret')}", flush=True) elif t == "hyread": print(f"[hyread] x0={p.get('x0')} len={p.get('len')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True) elif t == "prop": print(f"[prop] {p.get('k')}={p.get('v')}", flush=True) elif t == "open": print(f"[open] {p.get('p')} fd={p.get('fd')}", flush=True) elif t == "read": print(f"[read] fd={p.get('fd')} n={p.get('n')} hex={p.get('hex','')[:60]}...", flush=True) elif t == "ioctl": print(f"[ioctl] fd={p.get('fd')} req={p.get('req')}", flush=True) elif t == "sys": print(f"[sys] nr={p.get('nr')} a1={p.get('a1')} a2={p.get('a2')}", flush=True) elif t == "val": result["values"][p["m"]] = p["v"] print(f"[*] {p['m']} = {p['v']}", flush=True) elif t == "all-vals": result["values"].update(p["out"]) elif t in ("w-open", "w-close", "windows-hooked", "libc-hooked", "cls-ok"): print(f"[*] {t}", flush=True) elif "err" in t: print(f"[!] {t}: {p.get('e')}", flush=True) JS = JS_TEMPLATE.replace("__WINDOW_JSON__", json.dumps({k: hex(v) for k, v in WINDOW_OFFS.items()}) ).replace("__SYSCALL_JSON__", json.dumps(SYSCALLS)) sc = session.create_script(JS) sc.on("message", on_message) sc.load() d.resume(pid) print("[*] resumed, 等待 init 窗口(6s 后主动触发 Java 调用)...", flush=True) time.sleep(11) print("[*] 采集结束", flush=True) time.sleep(2) result["detached"] = det OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8") print(f"[*] saved {OUT} (events={len(result['events'])})", flush=True) if __name__ == "__main__": main()