docs(huya): 分析 getGUID 生成机制 - getter读缓存/init生成/硬锚底层硬件
- phone_stalk_getguid: Stalker 跟踪 getGUID(0x20f8c8) 执行, 揭示内部调用 (art::JNI::NewStringUTF 外层, libhydeviceid 内部函数+jsemalloc, 不读属性) - phone_getguid_ioctl: getGUID 窗口 ioctl/openat/property_get 追踪 -> ioctl=0, 确认是 getter 非生成器 - phone_init_ioctl: init()(0x20e6e4) 窗口追踪 -> 巨大 ioctl 噪音(App启动) + open fileshydckey(设备密钥) - getguid_stalk/ioctl/init 证据 结论: 32hex GUID = init 时用底层硬件指纹(SoC序列号等不可改)+hydckey经OLLVM保护算法生成并缓存; getGUID 只是读取。单机不可铸造(种子为不可改硬件ID)。
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
#!/usr/bin/env python3
|
||||
"""真机: 确认 getGUID 生成时是否 ioctl/openat 读底层硬件(而非属性/文件).
|
||||
|
||||
hook libc ioctl + openat + __system_property_get, getGUID(0x20f8c8) onEnter/onLeave 设窗口,
|
||||
窗口内记录调用的 ioctl(fd/request) / openat(path) / 属性读取。决定 32hex 硬件输入来源。
|
||||
|
||||
用法:
|
||||
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
||||
scripts/phone_getguid_ioctl.py [serial] [remote]
|
||||
输出:
|
||||
evidence/diag_phone/getguid_ioctl.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" / "getguid_init.json"
|
||||
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
|
||||
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
|
||||
GETGUID_OFF = 0x20E6E4
|
||||
|
||||
JS = r"""
|
||||
'use strict';
|
||||
var inGuid=false, hooked=false, iosHooked=false;
|
||||
|
||||
function hookIo(){
|
||||
if(iosHooked) return;
|
||||
try{
|
||||
var ioctl = Module.findExportByName('libc.so','ioctl');
|
||||
var openat = Module.findExportByName('libc.so','openat');
|
||||
var open = Module.findExportByName('libc.so','open');
|
||||
if(!ioctl && !openat && !open) return;
|
||||
iosHooked=true;
|
||||
if(ioctl) Interceptor.attach(ioctl,{onEnter:function(a){ if(inGuid){ try{ send({type:'ioctl', fd:a[0].toInt32(), req:a[1].toInt32()}); }catch(e){} } }});
|
||||
if(openat) Interceptor.attach(openat,{onEnter:function(a){ if(inGuid){ try{ var p=a[1].readCString()||'?'; send({type:'openat', p:p}); }catch(e){} } }});
|
||||
if(open) Interceptor.attach(open,{onEnter:function(a){ if(inGuid){ try{ send({type:'open', p:a[0].readCString()||'?'}); }catch(e){} } }});
|
||||
send({type:'io-hooked'});
|
||||
}catch(e){ send({type:'io-err', e:String(e)}); }
|
||||
}
|
||||
|
||||
function hookGetGUID(){
|
||||
if(hooked) return;
|
||||
var md=Process.findModuleByName('libhydeviceid.so');
|
||||
if(!md) return;
|
||||
hooked=true;
|
||||
Interceptor.attach(md.base.add(0x20e6e4),{ // init()
|
||||
onEnter:function(){ inGuid=true; send({type:'g-enter'}); },
|
||||
onLeave:function(){ inGuid=false; send({type:'g-leave'}); }
|
||||
});
|
||||
send({type:'guid-hooked'});
|
||||
}
|
||||
setInterval(hookGetGUID,200);
|
||||
setInterval(hookIo,200);
|
||||
|
||||
setTimeout(function(){
|
||||
Java.perform(function(){
|
||||
try{
|
||||
var NE=Java.use('com.huya.security.hydeviceid.NativeEntry');
|
||||
send({type:'cls-ok'});
|
||||
send({type:'call', v:NE['init']()}); // 主动 init
|
||||
}catch(e){ send({type:'java-err', e:String(e).slice(0,120)}); }
|
||||
});
|
||||
}, 8000);
|
||||
"""
|
||||
|
||||
|
||||
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 = {"ioctl": [], "openat": [], "props": [], "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 == "ioctl":
|
||||
result["ioctl"].append(p); print(f"[ioctl] fd={p.get('fd')} req=0x{p.get('req'):x}", flush=True)
|
||||
elif t == "openat":
|
||||
result["openat"].append(p); print(f"[openat] {p.get('p')}", flush=True)
|
||||
elif t == "open":
|
||||
result["openat"].append(p); print(f"[open] {p.get('p')}", flush=True)
|
||||
elif t in ("g-enter","g-leave","guid-hooked","io-hooked","cls-ok","call"):
|
||||
print(f"[*] {t}", p if t=="call" else "", flush=True)
|
||||
elif t in ("io-err","java-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", flush=True)
|
||||
time.sleep(9)
|
||||
result["detached"] = det
|
||||
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
||||
print(f"[*] ioctl={len(result['ioctl'])} openat={len(result['openat'])} saved {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user