Files
live-hub-py/scripts/phone_probe_hdid3.py
T
yml2213 0c6aff6a6d docs(huya): 逆向定位 32hex hdid = NativeEntry.getGUID() + 真机注入通道证据
核心结论:
- 32hex hdid (WUP 登录帧 field1.tag0) = com.huya.security.hydeviceid.NativeEntry.getGUID()
- 金样本 ed0db8 为金样本设备 GUID; 当前真机 M2102J2SC getGUID()=0a7dfaa882938a6ab502511452142c57(稳定)
- NativeEntry 全字段: getGUID(32hex)/getCDID(40hex)/getHDID(40hex)/getMID(16hex)/getSDID(base64 safedeviceid)
- 单机不可铸造: 改 serialno/ANDROID_ID/删files/hydevice持久化 均不变(硬锚底层硬件)
- libhydeviceid.so 为 OLLVM+datadiv 壳; 用 dlopen->JNI_OnLoad->RegisterNatives(env表索引215) 绕开混淆

脚本: phone_probe_registernatives2(注册表)/phone_java_call_nativeentry(Java读getGUID)/phone_read_hdid(native hook)
      phone_dump_hydev(dump解密so)/phone_probe_login_frame(登录帧)/arm64_disasm(反汇编)
证据: hdid_regtable/java_nativeentry/hdid_read/login_frames/hdid_probe* + libhydeviceid_{original,dump}.so
2026-08-28 12:30:34 +08:00

143 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""真机探针 v3: 等 so 加载后 hook, 抓 32hex hdid 生成/使用链路 + 登录触发.
修正 v1/v2: 挂起时 libhydeviceid.so 尚未加载 -> 用 setInterval 轮询,
so 一加载即 hook:
- libhydeviceid.so: 枚举非 runtime 导出 + hook JNI_OnLoad(0x22c4c8)
- libudbauthunify.so: hook BusinessCfg::getHdid(0x26a484) / setSafeDeviceId(0x26a2e0) 带调用栈
resume 后 am start GameSdkLoginActivity 触发设备上报/登录, 让 hdid 被读取/生成.
用法:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
scripts/phone_probe_hdid3.py [serial] [remote]
输出:
evidence/diag_phone/hdid_probe3.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_probe3.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<256)? d.readUtf8String(l):''; }
}catch(e){ return ''; }
}
var hydev=null, udb=null;
function hookHydev(){
if(hydev) return;
var md = Process.findModuleByName('libhydeviceid.so');
if(!md) return;
hydev = md;
send({type:'hydev', base:''+md.base, size:md.size});
var ex = md.enumerateExports();
var useful = ex.filter(function(e){ return !/^_Z/.test(e.name) && !/^__/.test(e.name) && e.name!==''; });
send({type:'hydev-exports', count:useful.length,
exports: useful.map(function(e){ return {off:''+e.address.sub(md.base), name:e.name}; })});
try{
Interceptor.attach(md.base.add(0x22c4c8), {
onEnter:function(){ send({type:'jni-onload-enter', tid:Process.getCurrentThreadId()}); },
onLeave:function(ret){ send({type:'jni-onload-leave', ret:''+ret}); }
});
send({type:'jni-onload-hooked'});
}catch(e){ send({type:'hydev-err', e:String(e)}); }
}
function hookUdb(){
if(udb) return;
var md = Process.findModuleByName('libudbauthunify.so');
if(!md) return;
udb = md;
send({type:'udb', base:''+md.base});
try{
Interceptor.attach(md.base.add(0x26a484), {
onLeave:function(ret){ send({type:'hdid', str:stdstr(ret), tid:Process.getCurrentThreadId()}); }
});
send({type:'hdid-hooked'});
}catch(e){ send({type:'udb-err', e:String(e)}); }
try{
Interceptor.attach(md.base.add(0x26a2e0), {
onEnter:function(a){
send({type:'setsd', sd:stdstr(a[1]), hd:stdstr(a[2]), tid:Process.getCurrentThreadId()});
try{
var bt=Thread.backtrace(this.context, Backtracer.ACCURATE).slice(0,20).map(function(x){return x.toString();});
send({type:'bt', bt:bt});
}catch(e){}
}
});
send({type:'setsd-hooked'});
}catch(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 = {"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 {}
result["events"].append(p)
t = p.get("type")
if t in ("hdid", "setsd"):
print(f"[{t}] {p.get('hd') or p.get('str')} tid={p.get('tid')}", flush=True)
elif t == "bt":
print(" bt:", " <- ".join(p["bt"][:8]), flush=True)
elif t == "hydev":
print(f"[*] libhydeviceid loaded base={p.get('base')}", flush=True)
elif t == "hydev-exports":
print(f"[*] hydev 非runtime导出 {p.get('count')}:", flush=True)
for e in (p.get("exports") or [])[:40]:
print(f" 0x{e['off']} {e['name']}")
elif t in ("udb","hdid-hooked","setsd-hooked","jni-onload-hooked","jni-onload-enter","jni-onload-leave"):
print(f"[*] {t}", p if t in ("jni-onload-leave",) else "", flush=True)
sc = session.create_script(JS)
sc.on("message", on_message)
sc.load()
d.resume(pid)
print("[*] resumed", flush=True)
time.sleep(5)
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()