Files
live-hub-py/scripts/phone_probe_hdid.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

122 lines
4.5 KiB
Python

#!/usr/bin/env python3
"""真机探针: 枚举 libhydeviceid.so / libudbauthunify.so 导出符号, 定位 32hex hdid 生成入口.
用真机稳定注入通道 (spawn + art_callsite bypass + resume) 长时存活。
App 启动即加载这两个 so, 枚举 exports 拿函数名+基址偏移, 为下一步
针对性 hook 生成函数(抓输入)做准备。
用法:
/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python \
scripts/phone_probe_hdid.py [serial] [remote]
输出:
evidence/diag_phone/hdid_probe_exports.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_probe_exports.json"
RE = Path("/Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0")
ART_CALLSITE = RE / "evidence" / "scripts" / "bypass_msaoaid_maps_art_callsite.js"
PROBE_JS = r"""
'use strict';
function listExports(modName, tag){
try{
var md = Process.findModuleByName(modName);
if(!md){ send({type:'mod', name:modName, state:'not-loaded'}); return false; }
var ex = md.enumerateExports();
var sym = md.enumerateSymbols();
send({type:'mod', name:modName, base:''+md.base, size:md.size,
exports: ex.map(function(e){return {name:e.name, type:e.type, off:''+e.address.sub(md.base)};}),
symCount: sym.length,
syms: sym.map(function(s){return {name:s.name, off:''+s.address.sub(md.base)};})});
return true;
}catch(e){ send({type:'err', name:modName, e:String(e)}); return false; }
}
function hookPropGet(){
var t = Module.findExportByName('libc.so','__system_property_get');
if(!t) return;
Interceptor.attach(t,{
onEnter:function(a){ this.k = a[0].readCString()||''; },
onLeave:function(ret){
try{ send({type:'prop', k:this.k, v:(this.ctx.x1.readCString()||'')}); }catch(e){}
}
});
send({type:'propget-hooked'});
}
// App 启动早期就抓 libhydeviceid.so 加载后的符号 + 派生属性读取
setTimeout(function(){ listExports('libhydeviceid.so','hydev'); }, 2000);
setTimeout(function(){ listExports('libudbauthunify.so','udb'); }, 2000);
setTimeout(hookPropGet, 2000);
"""
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()
d.resume(pid)
print("[*] bypass loaded + resumed", flush=True)
result = {"modules": {}, "props": []}
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 == "mod":
result["modules"][p["name"]] = p
print(f"[mod] {p['name']} base={p.get('base')} exports={len(p.get('exports') or [])} syms={p.get('symCount')}", flush=True)
elif t == "prop":
result["props"].append({"k": p.get("k"), "v": p.get("v")})
elif t == "propget-hooked":
print("[*] __system_property_get hooked", flush=True)
elif t == "err":
print(f"[err] {p.get('name')}: {p.get('e')}", flush=True)
sc = session.create_script(PROBE_JS)
sc.on("message", on_message)
sc.load()
print("[*] probe loaded, collecting 12s", flush=True)
time.sleep(12)
result["detached"] = det
OUT.write_text(json.dumps(result, indent=1, ensure_ascii=False), encoding="utf-8")
# 打印关键: 各 so 的导出符号名(去重)
for name, m in result["modules"].items():
print(f"\n=== {name} exports ({len(m.get('exports') or [])}) ===")
for e in (m.get("exports") or [])[:60]:
print(f" 0x{e['off']} {e['name']}")
print("\n=== 派生属性 (去重) ===")
seen = {}
for pr in result["props"]:
seen[pr["k"]] = pr["v"]
for k, v in seen.items():
print(f" {k} = {v}")
print(f"\n[*] saved {OUT}")
if __name__ == "__main__":
main()