核心结论:
- 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
4.6 KiB
虎牙 32hex hdid 生成逆向研究(真机 M2102J2SC)
目标:搞清楚 WUP 登录帧 t1.t0 / field1.tag0 的 32hex hdid(金样本
ed0db8334cadd236c00cadf7e11ab5a5) 如何生成、是否能在同一台设备上铸造新值(实现"每账号独立设备")。 时间:2026-08-28(真机稳定注入通道打通后进行)。已定位 32hex = NativeEntry.getGUID()。
一、最终结论(决定性)
32hex hdid(WUP 登录帧 field1.tag0)= com.huya.security.hydeviceid.NativeEntry.getGUID() 的返回值。
- 金样本
ed0db8...是金样本设备的 getGUID() 值; - 当前真机 M2102J2SC 的 getGUID() =
0a7dfaa882938a6ab502511452142c57(32hex,交叉验证稳定: 在 5 个设备上报帧中出现 + Java 主动调用一致)。
getGUID() 每台设备唯一且稳定(硬锚硬件,同 getCDID/getHDID 一样),可被 NativeEntry 的
native 方法直接可靠读取。
因此**"生成 32hex hdid" 无需逆向算法**:每台真机天然由 libhydeviceid.so 生成并提供它独有的 32hex getGUID()。多真机设备池 = 每台调 getGUID() 采集各自唯一值。
单机不可铸造(3 个决定性实验)
改 ro.serialno / ANDROID_ID / 删 files/hydevice/ 持久化后重启,设备标识(UUID 文件名 +
getGUID 等)全部不变 —— 因 libhydeviceid.so 基于底层硬件(SoC/IMEI)稳定生成,刷不掉。
二、NativeEntry 全部字段(已可靠读取)
通过 dlopen 拦截 → JNI_OnLoad(0x22c4c8) → RegisterNatives(JNIEnv 函数表索引 215),dump 出
com.huya.security.hydeviceid.NativeEntry 的 19 个 native 方法,Java 主动调用实测值:
| NativeEntry 方法 | 真机实测值 | 对应 WUP 登录帧字段 |
|---|---|---|
| getGUID() | 0a7dfaa882938a6ab502511452142c57 |
field1.tag0 = hdid(32hex) |
| getCDID() | 02df398797432eadefcc12767119ad5e80999389 |
field2.tag2 = device_id(40hex) |
| getHDID() | 7c5387e0539c023c31c4ff0e807e7256117385ee |
field2.tag8 = 另一设备ID(40hex) |
| getMID() | 1e8bdf7d4f7a01d3 |
MID(16hex) |
| getSDID() | PQwemAN9...(base64 180B) |
safedeviceid |
- Java 类
com.huya.security.hydeviceid.NativeEntry注册 method 地址(libhydeviceid.so 偏移): getCDID@0x20eb08 / getSDID@0x20ede8 / getHDID@0x20f0c8 / init@0x20e6e4 / getGUID@0x20f8c8 / getMID@0x20fba8 / linkId@0x20fe88(其余 setPhoneInfo/setMotionEvent/上报类略)。 - 另有
com.huyaudb.HuyaAuthCore注册init/unInit/sendMsg/receiveNet(WUP 消息收发)。
hdid 名称混淆(已彻底澄清)
项目里"hdid"对应多个不同值:
- 32hex(WUP 登录帧 field1.tag0)= getGUID() —— 设备 GUID,硬锚,每台唯一;
- 40hex
7c5387= getHDID() = huyaudb 上报里被命名为 "hdid"(同名不同值); - 40hex
02df39= getCDID() = device_id; - 16hex
1e8bdf7d4f7a01d3= getMID(); - 凭据文件
files/hydevice/<UUID>(64a33427...)持久化加密凭据。
三、libhydeviceid.so 加壳(静态逆向极难,但已绕过)
- OLLVM 混淆(
based on Obfuscator-LLVM 9.0.9svn)+ datadiv 运行时壳(~70 个.datadiv_decode*解密函数 +hy_fopen64/hy_read/hy_syscall/hy_write/hyftell文件 I/O 壳 + 读 TracerPid 反调试)。 - 用 dlopen→JNI_OnLoad→RegisterNatives 动态拿到方法表,绕开混淆,无需反汇编。
- 已 dump 解密后代码:
evidence/diag_phone/libhydeviceid_dump.so(内存态)+ 原始libhydeviceid_original.so。
四、可行方向(服务于"每账号独立设备")——推荐,已具备采集能力
多真机采集设备池:每台真机用真机注入通道(spawn+art_callsite bypass)读
{getGUID(32hex), getCDID(40hex), getHDID(40hex), getMID(16hex), getSDID(base64)},
这些值每台唯一、稳定、被服务端认可。采集多台建设备池轮换,即可实现每账号独立设备。
无需逆向铸造算法,直接读 NativeEntry 即可。
五、实验脚本(真机)
scripts/phone_probe_registernatives2.py(v7:dlopen→JNI_OnLoad→RegisterNatives 注册表 + get* 地址)scripts/phone_java_call_nativeentry.py(Java 主动调 NativeEntry get 全家桶,读取 32hex GUID*)scripts/phone_read_hdid.py(v8:native hook get* 用原生 JNI GetStringUTFChars 解码)scripts/phone_dump_hydev.py(dump 解密后的 libhydeviceid.so)scripts/phone_probe_hdid*.py(早期符号 / 属性 / 文件探针,过程记录)- 证据:
evidence/diag_phone/{hdid_regtable,hdid_read,java_nativeentry,login_frames}.json/libhydeviceid_{original,dump}.so