核心结论:
- 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
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""真机探针 v2: 抓 libhydeviceid.so 经 RegisterNatives 注册的 native 方法 + getHdid 调用栈.
|
|
|
|
libhydeviceid.so 没有 JNI/生成函数导出(核心方法经 JNI_OnLoad -> RegisterNatives 动态注册)。
|
|
本脚本在 spawn 挂起中 hook libart.so 的 RegisterNatives, App 启动早期注册会被捕获,
|
|
拿到 libhydeviceid.so 注册的 native 方法指针 -> 基址偏移, 定位 hdid 生成入口。
|
|
同时 hook libudbauthunify.so 的 BusinessCfg::getHdid(0x26a484) 抓 hdid 值+调用栈。
|
|
|
|
用法:
|
|
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
|
|
scripts/phone_probe_registernatives.py [serial] [remote]
|
|
输出:
|
|
evidence/diag_phone/hdid_registernatives.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_registernatives.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 ''; }
|
|
}
|
|
function modOf(a){ try{ var m=Process.findModuleByAddress(a); return m? {n:m.name, off:''+a.sub(m.base)}: {n:'?', off:'?'}; }catch(e){ return {n:'?',off:'?'}; } }
|
|
|
|
// 1) hook libart RegisterNatives
|
|
(function(){
|
|
var RN = Module.findExportByName('libart.so','_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi');
|
|
if(!RN){ send({type:'rn-notfound'}); return; }
|
|
send({type:'rn-found', a:''+RN});
|
|
Interceptor.attach(RN,{
|
|
onEnter:function(a){
|
|
var count = a[3].toInt32();
|
|
if(count<=0||count>500) return;
|
|
var cls='';
|
|
try{ cls = Java.vm.tryGetEnv().getClassName(a[1])||''; }catch(e){ cls='?'; }
|
|
for(var i=0;i<count;i++){
|
|
try{
|
|
var base=a[2].add(i*24);
|
|
var name=base.readPointer().readCString()||'';
|
|
var sig =base.add(8).readPointer().readCString()||'';
|
|
var fn =base.add(16).readPointer();
|
|
var mo=modOf(fn);
|
|
send({type:'reg', cls:cls, name:name, sig:sig, mod:mo.n, off:mo.off});
|
|
}catch(e){}
|
|
}
|
|
}
|
|
});
|
|
send({type:'rn-hooked'});
|
|
})();
|
|
|
|
// 2) hook BusinessCfg::getHdid (libudbauthunify.so 0x26a484)
|
|
(function(){
|
|
var md = Process.findModuleByName('libudbauthunify.so');
|
|
if(!md){ send({type:'udb-not-loaded'}); return; }
|
|
try{
|
|
Interceptor.attach(md.base.add(0x26a484),{
|
|
onLeave:function(ret){
|
|
var s=stdstr(ret);
|
|
send({type:'hdid', str:s, tid:Process.getCurrentThreadId()});
|
|
}
|
|
});
|
|
Interceptor.attach(md.base.add(0x26a2e0),{ // setSafeDeviceId(a2=sd, a3=hdid)
|
|
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,24).map(function(x){return x.toString();});
|
|
send({type:'bt', bt:bt});
|
|
}catch(e){}
|
|
}
|
|
});
|
|
send({type:'udb-hooked'});
|
|
}catch(e){ send({type:'udb-err', e:String(e)}); }
|
|
})();
|
|
"""
|
|
|
|
|
|
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()
|
|
print("[*] art_callsite loaded", flush=True)
|
|
result = {"regs": [], "hdid": [], "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 == "reg":
|
|
result["regs"].append(p)
|
|
elif t in ("hdid", "setsd"):
|
|
result["hdid"].append(p)
|
|
print(f"[{t}] {p}", flush=True)
|
|
elif t == "bt":
|
|
print(" bt:", " <- ".join(p["bt"][:8]), flush=True)
|
|
elif t in ("rn-found","rn-hooked","udb-hooked"):
|
|
print(f"[*] {t}", flush=True)
|
|
sc = session.create_script(JS)
|
|
sc.on("message", on_message)
|
|
sc.load()
|
|
d.resume(pid)
|
|
print("[*] resumed, collecting 18s", flush=True)
|
|
time.sleep(18)
|
|
result["detached"] = det
|
|
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
|
|
|
|
# 汇总 libhydeviceid.so 注册的方法
|
|
print("\n=== libhydeviceid.so 注册的 native 方法 ===")
|
|
seen = set()
|
|
for r in result["regs"]:
|
|
if r.get("mod") == "libhydeviceid.so" and (r["name"], r["off"]) not in seen:
|
|
seen.add((r["name"], r["off"]))
|
|
print(f" 0x{r['off']} {r['name']}{r['sig']} (class={r.get('cls')})")
|
|
print(f"\n=== 所有 so 注册统计 ===")
|
|
from collections import Counter
|
|
c = Counter(r.get("mod") for r in result["regs"])
|
|
for mod, n in c.most_common():
|
|
print(f" {mod}: {n}")
|
|
print(f"\n[*] saved {OUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |