- docs/HUYA_ID_NOMENCLATURE.md: 规范总表 + 三条强制规则 + 代码变量对应 - 4 篇主文档头部去混声明 + §11.x 被证伪假设行标注更正 - 代码: GOLDEN_HDID→GOLDEN_HDID32, device_profile.HDID→HDID32, wup_encoder/app_login 注释前缀化 - 后续所有表述必须以 GUID32(doLaunch) / HDID32(登录t1.t0) / DEVID40(t5) 前缀书写
34 KiB
虎牙 32hex hdid 算法生成路径分析(不用采集,纯算法铸币)
前置结论(见
HUYA_HDID_RESEARCH.md):32hex hdid(WUP 登录帧 field1.tag0)= hdid(WUP 登录帧 field1.tag0)=com.huya.security.hydeviceid.NativeEntry.getGUID();真机 M2102J2SC 金值0a7dfaa882938a6ab502511452142c57;getGUID 是 getter,值在init()(偏移 0x20e6e4) 时由libhydeviceid.so(OLLVM 混淆 + datadiv 壳)算出并缓存。用户约束:不能用真机采集设备池;必须用算法生成(在代码里根据可控输入铸出 一套又一套服务端认可的设备身份)。本文是"如何继续"的完整分析。
时间:2026-08-28(
0c6aff6/022357a之后)。
一、任务再定义:什么是"算法生成"
设备身份是纯函数:
getGUID() = F_guid(种子集 S) → 32hex
getCDID() = F_cdid(种子集 S) → 40hex
getHDID() = F_hdid(种子集 S) → 40hex
getMID() = F_mid(种子集 S) → 16hex
getSDID() = F_sdid(种子集 S) → base64 180B
- S = 设备的指纹输入集合(属性/文件/系统值/持久化密钥,详见 §三)。
- "算法生成" = 我们在任意环境(纯 Python / unidbg 仿真)里把 F 跑出来, 输出不依赖任何物理设备;换一套 S 就铸一个新身份。
- 关键澄清:"单机不可铸造"只证明
{ro.serialno, ANDROID_ID, files/hydevice/}不在 S 里(改它们 GUID 不变);不证明 S 不可控。在仿真里 S 由我们提供; 而 S 只要有一丁点可变化的空间(IMEI/qimei/SoC 序列号/密钥文件),铸币空间就是无限的。
二、本轮新证据(对 decrypt dump 的静态分析)
对 evidence/diag_phone/libhydeviceid_dump.so(datadiv 已就地解密的内存镜像)做常量/结构扫描:
2.1 lib 内置标准密码学原语(这是静态移植可行性的决定性地雷)
| 原语 | dump 内偏移 | 说明 |
|---|---|---|
| AES S-box(完整 256B,精确匹配) | 0x36be82 |
有 AES(256 位密钥可能性大) |
MD5 IV 常量(01 23 45 67 89 ab cd ef fe dc ba 98 76 54 32 10) |
0x36c190 |
有 MD5 |
| SHA1 轮常量 K(小端,4×u32) | 0x36c1a0 |
有 SHA1 |
| base64 字母表 | 0x3e8880 |
有 base64(SDID 180B 的编码) |
→ 32hex 输出=MD5 类、40hex 输出=SHA1 类、SDID=AES+base64 的组合推测是标准构造 + 内置密钥, 不是自研魔改算法 → 不需要解全部 OLLVM 平坦化,只需定位"哪些函数把哪些输入喂给这些原语"。
2.2 .data 里已提取的固定密钥 / 盐候选(datadiv 解密后的明文)
| 常量 | 形态 | 角色猜测 |
|---|---|---|
7jbF5IiqqIDdgEtiNQLzJSuTZmRk8sbc(32B,出现 8+ 次) |
base64 风格 32 字符 | AES-256 固定密钥(最可能) |
HuyaUdb1928374650qwertyuiop(27B,出现 2 次) |
明文 | UDB 体系固定密钥/加盐串 |
865a4924a40897ac1fcfe6b4c2cbb045 / ...abc798 |
32hex ×2(只差 4B) | appid 对密钥哈希 / dfpReport 通道常量 |
961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3 |
40hex + 版本 | SHA1 固定值(某固定材料摘要) |
400e18d7b4c8070374bd216279da774b / 473c8798375341d8849804154d181acc |
32hex ×2 | 运行时/渠道相关常量 |
0123456789abcdef0123456789abcdef |
32hex | 默认 GUID 模板 / 输入填充 |
注意:
getDfpConfig是服务端下发的接口名(.data 里有),这些 32hex 常量里 可能有运行时应答替换的值——金样本对拍时要用真机实测值逐一对账(§五)。
2.3 getGUID(0x20f8c8)反汇编现场 = 标准 OLLVM 平坦化
0x20f8c8 处可见典型 dispatcher 模式(mov w16,#case; cmp w16,w13; b.gt/b.le/b.eq 状态机跳转),
函数体完整存在于 dump 中(壳已解密)。Stalker 记录其内部调用锚点(模块偏移):
0x46140、0x1d6e68 等(0x46140 紧邻 0x45f20/0x45890/0x45aa0/0x46fd0 小偏移簇 =
hex/base64/CRC 类工具函数区)。这些锚点是静态还原的切入点:先在这些地址反汇编,
认出"读 S → 拼接 → MD5/SHA1/AES 调用"的骨架即可,不需要全量反平坦化。
三、种子集 S 的候选清单(当前输入图缺口)
从 .data 字符串、init 窗口 openat 记录、probe 事件三方交叉(都只是候选,待 E2 定案):
| 类别 | 具体项 | 备注 |
|---|---|---|
| 属性 | ro.product.{model,manufacturer,board,device,name,brand,marketname,vendor.model} |
.data 出现完整名单 |
| 系统文件 | /proc/cpuinfo(含 Serial:)、/proc/version、/proc/meminfo |
cat /proc/cpuinfo 字符串直接出现 |
| 持久化 APP 密钥 | fileshydckey(init() 窗口 open 过 1 次) |
疑似"设备密钥",是否每次安装重新生成 = 决定性 |
| 腾讯 QIMEI | qimei16 / qimei36(.data 出现 3 组) |
腾讯灯塔设备 ID,持久化于 shared_prefs,清数据会再生成 |
| 蓝牙/WiFi | 蓝牙名称/地址、WiFi 信息 | .data 有读取代码路径 |
| 缓存/上报 | files/hydevice/resinfo、上报链路字段 |
已确认与 GUID 值无关(实验删过不变) |
| 硬件锚(待验证) | IMEI、/sys/devices/soc0/serial_number、TEE/Keystore 密钥 |
上一轮推断"硬锚 SoC"的落点,尚无直接实验 |
为什么上一轮"不可铸造"实验不完整:它们没有覆盖 fileshydckey 删除、全量清数据/
卸载重装(会影响 qimei 与可能重生成的文件)、IMEI 变更。只要 E1/E3 里任一项变了
GUID,就说明 S 里有可再生的成分 → 铸币空间立刻打开。
四、决定性实验清单(真机在线:5dd8c93f,通道稳定,1~2 天)
- E1:全量清数据 / 卸载重装 → getGUID 变不变?(若有持久化种子:变了 = 单机 "重装即新设备",先于一切;不变 = 硬件锚确证)
- E2(最高价值工件):init()(0x20e6e4)库内定向 Stalker:只跟踪
libhydeviceid 内部调用 + 库内触发的
__system_property_get/open/read/ioctl, 带参数值快照(属性名+值、文件路径+读入的前 N 字节、ioctl 解码)→ 得到 完整输入图 + 每个种子的实测值。上一轮 getguid_init.json 是全 App 启动噪音 窗口,需要收紧到库内。 - E3:IMEI 变更(root + 模块)→ getGUID 变不变?(确定 IMEI 是否在 S 中)
- E4:补记 E2 之外的硬锚路径实测值(
/sys/devices/soc0/serial_number、/proc/cmdline的 androidboot 段、getprop ro.boot.*值)→ 这些直接把"不可改 硬件"变成"可仿真输入"。
副产品(对拍语料):上述每次变更都记录 (S 的逐项值 → 5 个输出值),形成
差分语料库。这是 E2 之外静态移植的主要"答案纸":能直接看出哪个输出依赖哪些输入、
以及单输入变化时的输出变化模式(MD5 雪崩 vs 局部字节变化 → 立刻排除/确认构造)。
五、执行路径对比
路径 A:纯静态还原 → 纯 Python 生成器(最终形态,周级)
- E2 输入图 + E4 硬锚值 → 复原"种子串拼接格式"(大概率就是若干固定前缀 + S 逐项);
- 用金样本 pair(本节 §2.2 常量 + 真机 S)在 Python 里穷举组合对拍:
MD5(盐‖S)、SHA1(盐‖S)、AES-ECB/CBC、HMAC-MD5/SHA1× 各候选密钥/盐; - 命中即纯 Python 版本直接成立(无需解一个字节的 OLLVM);未命中再走 Ghidra+ 锚点反汇编看拼接/变换细节;
- 产出
tools/hdid_mint.py+core/huya/device_mint.py(零 native 依赖)。
要点:dump 已解密、原语已定位、密钥候选已提取 —— 静态路上最贵的 "OLLVM 平坦化全解" 不是必需项,先用黑盒对拍把算法筛出来。
路径 B:unidbg 仿真执行(天级,推荐先行)
- 装 JDK(本机 JVM 失效:
/usr/bin/java空壳、sdkman 空目录 → brew/sdkman 装 OpenJDK 17/21);拉 unidbg(zhkl0228/unidbg); - 把 decrypt dump 载入 ARM64 仿真,hook:JNI env、
__system_property_get、open/read、ioctl、TelephonyManager 等 JNI 桩; - 金测试:喂真机 S 实测值 → 期望输出
0a7dfaa8...;对上了 = 整个算法已在 PC 上可执行; - 之后每套虚拟 S 跑一次 init+get* 即铸一套身份 —— 这就是"算法生成"的落地形式;
- 风险:OLLVM 仿真慢(毫秒级可接受)、反调试(TracerPid 读取,可 hook)、
若算法依赖 Java 服务调用需补 JNI 桩(init 是
()V无参,依赖面小)。
路径 C:有根模拟器 / 重装循环(备用,仅对拍用)
lib 内置大量模拟器检测串(qemu_pipe / mumuvmm / genymotion / windroyed…), 此前模拟器直接闪退;且服务端对模拟器身份可能单独风控。不作为主路径。
六、服务端验收闭环(从第一天起就验,避免白做)
- 验收口径:铸出的身份 → 现有纯代码 dfpReport 设备注册链(
tools/dfp_gen.py/core/huya已有零设备注册)+ 现有 WUP 密码登录链(core/huya/app_login.py)→ 拿到有效 Cookie/登录态 = 服务端接受该身份。 - 关键测试:用每个账号一套全新虚拟身份跑完整登录(替换现在全账号共用的金样本
ed0db8...),观察:注册是否成功、登录是否放行、滑块/风控是否升级。 - 若服务端只校验"格式/自洽/已注册",铸币路径畅通;若校验设备库真实性,则需评估 深层对抗(那是另一个量级的问题,先实测出结论再投入)。
七、推荐执行序列(蓝线)
| 阶段 | 动作 | 依赖 | 产出 |
|---|---|---|---|
| D0(1~2 天) | E1/E2/E3/E4 真机实验 | 真机 + 稳定通道(现成) | 输入图、种子实测值、差分语料库 |
| D1(并行) | Python 黑盒对拍(§五-A2) | D0 语料 + §2.2 常量 | 是/否命中标准构造 |
| D1(并行) | 路径 B:JDK + unidbg + 金测试 | D0 输入图 | 仿真铸币机(保底形态) |
| D2 | 铸币 → 注册 → 登录闭环实测 | D1 任一成果 | 服务端接受度结论 |
| D3 | device_mint.py 接入 app_login.py(每账号独立设备) |
D2 通过 | 生产落地 |
八、风险清单
- 种子含 TEE/Keystore 级硬锚且无再生路径 → 仿真下仍可铸(S 自拟),但服务端可能 拒绝"无对应真实硬件"的身份 → 以第六节实测为准;
- §2.2 常量为运行时下发替代 → 对拍用真机实测值逐个对账,避免拿死值硬套;
- qimei 参与 GUID 时,qimei 本身依赖 AndroidID/IMEI → 铸币需把 qimei 一并纳入 虚拟 S(unidbg 下可控);
- OLLVM 全量反平坦化(数周)不作为必经路线——只有黑盒对拍全失败才需要;
- 服务端风控升级(针对新设备登录的滑块/验证)→ 已有 safe_auth 自动过验能力可兜底。
九、执行进展(2026-08-28 晚,本次会话落地)
9.1 输入图彻底打开(真机 E2 + unidbg 双证)
- 库窗口内系统调用只有三类:反模拟器 faccessat 探测(qemu/mumu/nox/bluestacks 路径)、
反调试
/proc/self/task/*/status(TracerPid)、自身持久化文件检查。零硬件文件读取。 - 种子不在属性层:X1 改
ro.product.*全家族、X2 改 IMEI 属性/ro.boot.cpuid/serialno, GUID 均不变(与全清数据 E1 一致)。属性只参与上报,不影响 GUID。 - JNI 面由 unidbg 暴露:
NativeEntry.init()走AppGlobals.currentApplication→getSharedPreferences("table")→ 读/写 key:hydeviceid_config(服务端下发)androidId(加密态)old_androidIdcdid_versionhydeviceid_guid→NativeBridge.b(102)(=MID/androidId) →getDataDir。getGUID 读hydeviceid_guid→ 空则WupHelper.getGuid()→NativeBridge.b(100)。
9.2 unidbg 金测试通过(决定性)
- 解密 FULL dump(r--+rw- 全段,
phone_dump_hydev_full.py)+ 解密数据合入原文件 (merge_decrypted.py,unidbg 重定位自会覆盖指针槽)。 - 喂真机种子(ANDROID_ID / hydeviceid_config / NativeBridge.b 实测值):
androidIdpref =PDSuHxt854IZRR5Y7AGyaQ==—— 与真机逐字节一致(采集器 AES 加密可复现)- getGUID =
0a7dfaa882938a6ab502511452142c57—— 与真机一致
- 工具链:JDK21(brew)+ unidbg(Module 冲突补丁/JDK21、getrlimit/setrlimit 兜底)+ harness
tools/unidbg/hydev(JNI 桩:NativeBridge.b/klog、Settings.Secure、SharedPreferences 日志化)。
9.3 遗留的最后一块(下一轮主攻)
NativeBridge.b(100)的原生实现归属库未定:不在 libhydeviceid(静态无 xref、注册表 23 条 无它)、不在 libudbauthunify(解密 dump 无该串)、不在 libnative-device-util/libjsiwup。 RegisterNatives 长窗口探测全程 0 次命中(时机/环境因素存疑)。当前 harness 用真机实测值桩顶替。- 结论修正:此前"单机不可铸造/硬锚 SoC"判断错误 —— 实际链路是
GUID = b(100) = f(androidId加密态, hydeviceid_config(服务端), MID),输入全部可控可计算。 - 下一步两条线(任选其一即可闭环铸币):
- unidbg 双库/三库加载:找到并加载 b 原生所在库(可疑:运行期 dex 动态下载的另一个 .so, 需在真机 dlopen 全拦截清单里补齐),让 b(100) 真实计算;
- 静态还原 b(100) 公式:基于 (ANDROID_ID→GUID) 差分对拍(unidbg 内改 ANDROID_ID 即可批量
产对),用 .data 密钥(
7jbF5I.../HuyaUdb.../config 解密链)猜 MD5/SHA1/AES 结构。
十、Hook 阶段结果(2026-08-28 深夜续)
10.1 NativeBridge 归属排查(穷尽运行时手段)
- 动态 JNI 排除:全模块
Java_*导出扫描,自研库仅 libhypopup/libjsiwup/AI 库有——无Java_com_huya_security_hydeviceid_NativeBridge_*。 - RegisterNatives 排除:①v7 注册表(23 条)= NativeEntry(19@hydeviceid)+HuyaAuthCore(4@udb), 无 NativeBridge;②dlopen→JNI_OnLoad 拦截各库(hydeviceid/udb)0 命中;③健康进程 attach+ 重触发 init/getGUID,RN 全程 0 命中。
- unidbg verbose 实锤:hydeviceid JNI_OnLoad 只
FindClass(NativeBridge)+NewGlobalRef(不注册), udb JNI_OnLoad 只注册 HuyaAuthCore;NativeBridge.b/klog 在 unidbg 内经 JNI 以"未注册 native" 路由到 AbstractJni(桩)。 - 定位到的关键静态物证:hydeviceid .data@0x3e21e4 起为内联方法描述符块
(
klog()(L...Z)V、b (I)L..、c (I)J、h ()L..、g (L)L..、getDataDir/getPath…), 类名字符串com/huya/security/hydeviceid/NativeBridge@0x3e2f20 紧随其后;但代码无任何 ADRP/GOT 引用该页(间接/运行期构建)。完整 {name,sig,fnPtr} 指针表未以静态形式存在。 - 新输入候选:采集器还读写
udb_deviceID/udb_deviceID_encode/oaid(prefs)+NativeBridge.b(104)(oaid 相关);Build.*静态字段读。
10.2 双库 unidbg 已打通但 b 仍未真注册
- libudbauthunify(JNI_OnLoad@0x262620,RN 调用点 0x2629bc)解密合并后 unidbg 加载成功;
- merge_decrypted.py 升级为段驱动(vaddr→文件偏移,支持 .rodata/delta 各段)+ .dynamic 保留
- .init_array 置零 + 仅重定位槽指针清理(避免误伤字符串);
- 卡点:NativeBridge natives 从未在任何可见路径注册 → b(100) 仍走桩。
10.3 设备稳定性注意(重要)
- 反复
pm clear+ frida attach 探测会清空应用数据(弹协议)并导致 App 闪退; 已确认无 frida 冷启动 App 健康(首页焦点正常)。后续禁 pm clear、attach 探测收敛。
10.4 静态还原重启点(step-2 方案,全部 PC 侧)
- 歧义清除:确认 NativeBridge.b/klog 到底是否 native(dex 反射 isNative,用 SPAWN+bypass 轻探,一次即可);若 native → 在 hydeviceid 代码中用"运行期构建表"模式(构造 {name,sig,fnPtr} 时 ldr 常量页)找注册函数;若 Java → 直接读运行期 dex 里 getGuid/b 逻辑。
- oracle 差分:unidbg 侧可控 ANDROID_ID/Settings.Secure 已证明 androidId 密文会变;
b 一旦真注册,
(ANDROID_ID → GUID)差分对即可在 unidbg 内批量生成 → MD5/SHA1/AES 对拍。 - b 的 fnPtr 兜底获取:运行期 dump hydeviceid .data 时同时读 JNINativeMethod 运行期构造表 (spawn+bypass 一次抓全,附 base 校验)。
十一、静态还原定案(2026-08-29 凌晨,PC 侧完成,无需 SPAWN/真机)
11.1 isNative 判定 = 纯 Java(dex 直读,比 SPAWN 更硬)
- 工具
tools/dex_probe_native.py(dex 解析器)扫全部 18 个 classes*.dex:NativeBridge(classes5.dex):b(I)L..;、a/c/g/h/klog/i1/i2/j全部为 Java(有 code_off), 无任何 native 方法 —— 10.1 的怪象全部解释:JNI_OnLoad 只 FindClass+NewGlobalRef 是因为 没有任何 native 需要注册;.data@0x3e31e4(vaddr 0x3e41e4) 的描述符块(klog/b/c/h/g/getDataDir/getPath +类名 com/huya/security/hydeviceid/NativeBridge + com/duowan/biz/wup/WupHelper) = native 代码运行期 GetStaticMethodID 反向调用 Java 的 name/sig 常量表。NativeEntry:getGUID/getCDID/getHDID/getMID/getSDID/init 全部 native(code_off=0,RN 19 条已实锤)。
NativeBridge.b(I)=fj3.c(I)分发器(DeviceInfo.java),b(100)→pnc.c()→ 静态pnc.a。
11.2 GUID 值流完整还原(GUID ≠ 本地指纹公式!)
- native getGUID = 纯缓存 getter(unidbg 旧配置金测试实证):
pref hydeviceid_guid→ 空则 JavaWupHelper.getGuid()→NativeBridge.b(100)→ 回写 pref 并返回。 即 unidbg 金测试的"GUID 一致"= Java 桩直喂 golden 的闭环,从未验证过本地公式。 - Java 链(每环唯一调用者/写入者,交叉验证):
YYProtoSdkModule.initHuyaUdbSdk (classes17.dex, code@0x3834d0) → HyDeviceProxy.j(HY_APPID, WupHelper.getGuid(), mid) (setAppInfoId) → pnc.k(guid) → pnc.a → NativeBridge.b(100) → fj3.c(100) → pnc.c() WupHelper.getGuid() (classes12.dex) = ((IHal)hlf.i(IHal.class)).getGuid() → HalImpl.getGuid() = sGuidProperty.get() → sGuidProperty 仅两处写入: initCache() 与 initialInner 网络回调 → LiveLaunchRsp.sGuid (live-launch/doLaunch 服务端响应, 32hex 硬校验: 非32位触发 mIllegalGuidCallBack) - 结论修正(推翻 §9.3"GUID=f(androidId密, config, MID)"):铸币本体不是本地算法,而是
服务端 doLaunch/device_register 按设备指纹签发 sGuid。doLaunch 请求携带
LiveUserbase{tUAEx.sDeviceId(Config 持久化), sIMEI, sMId} + tId(userId.sGuid=当前guid, lUid)}; 响应sGuid即后续所有登录帧 tag0 的 32hex hdid。⚠️ 旧假设 —— 已于 §11.10 证伪: GUID32(sGuid) ≠ HDID32(登录帧t1.t0)。 - 与既有认知吻合:旧实验"改 ANDROID_ID/全清数据 GUID 不变"(服务端签发+本地缓存)、
tools/注记"hdid 服务端硬锚/不可铸造"。
11.3 新工具(PC 侧,无设备依赖)
tools/dex_probe_native.py:dex 类/方法 access_flags 判定(含 class_data fields 跳过修复)。tools/dex_scan_calls.py:跨 dex 指令级扫描指定方法的调用点(找 setAppInfoId→j() 即用此)。- 用法:
python3 tools/dex_probe_native.py work/apk_extract/python3 tools/dex_scan_calls.py work/apk_extract Lcom/hy/HyDeviceProxy; j "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"
11.4 unidbg 金测试回归修复(重要教训)
- 症状:新 harness(加入 libudbauthunify 第二库 + 扩展桩)后 getGUID/getCDID/… 全 null, init 阶段 Dynarmic "assertion failed" 崩溃(PC 跳 libc++ 零区)。
- 根因:C++ locale/facet 路径(
std::locale::use_facet、ios_base::getloc等)在 unidbg 内置 libc++ 上 ABI 不兼容;第二库加载改变了执行路径触达该处。 - 修复/验证:14:16 金测试同款 harness(无 udb 第二库,git 70621cf)+ 当前 merged so → getGUID = 0a7dfaa882938a6ab502511452142c57 MATCH,并首次实证 native 缓存写回流程 (WupHelper.getGuid→null → b(100)→golden → prefs-put hydeviceid_guid → return)。
- dump 真实运行基址 = 0x7814200000(非 probe 记录的 0x7813211000);merge v3 重定位结果 正确(merged = dump - 0x7814200000 = 模块内偏移)。
11.5 铸币路径重定义(下一步蓝线)
- 服务端签发复现:用现有纯 Python WUP 链(tools/huya_wup_encoder.py 等)构造虚拟设备指纹的
doLaunch/device_register请求(deviceId/IMEI/mid 取自虚拟 profile)→ 收 sGuid → 先验确定性:同一指纹多次请求 sGuid 是否一致 / sGuid 是否 = f(请求设备字段)。 - 若确定性签发:
device_mint.py= 虚拟指纹 → doLaunch → sGuid →(可选 dfpReport 注册) → 现有 WUP 登录链验证服务端接受度(用户既有"每账号一套全新虚拟身份"验收口径不变)。 - 若 sGuid 随机签发(仅库内下发),则评估"锁定身份"的现实等价物:设备指纹→注册→登录 全链路用同一份虚拟 profile 保持一致性,铸币=整套身份模板随机化。
- 真机/未决项:sGuid 签发是否依赖 qimei16/36、hdid/cdid/sdid 上报(采集器还是把设备信息上报 给 dfpReport 侧),这些在 §六 的注册链实测中一并验证。
11.6 doLaunch wire 已离线还原(2026-08-29 续)
- JCE struct 规格(dex writeTo 精确还原,全部位于 classes9/com/duowan/HUYA):
LiveLaunchReq:t0=tId(UserId) t1=tLiveUB(LiveUserbase) t2=bSupportDomain(int16)LiveUserbase:t0=eSource(=2) t1=eType(=1) t2=tUAEx(LiveAppUAEx)LiveAppUAEx:t1=sIMEI t2=sAPN t3=sNetType t4=sDeviceId t5=sMIdUserId(classes11):t0=lUid(int64) t1=sGuid t2=sToken t3=sHuYaUA t4=sCookie t5=iTokenType t6=sDeviceInfo t7=sQIMEILiveLaunchRsp:t0=sGuid(铸币目标) t1=iTime t2=vProxyList t3=eAccess t4=sClientIp
- 信封:UniPacket(同密码登录);servant=
launch(@WupServant("launch")),func=doLaunch; sBuffer=map<string,bytes> 键_wup_data;4B 大端长度前缀。 - 工具
tools/huya_launch_mint.py:build_live_launch_wup(profile)→请求字节、 parse_launch_rsp(bytes)→候选 sGuid(递归解 SIMPLE_LIST 载荷)、--self-test(回环)/--dump/--live [url]。默认虚拟 profile 见 DEFAULT_PROFILE(mid 16hex、imei 15 位、 device_id 32hex、guid=""→服务端签发)。 - 传输待 live 实测确认:hyns/KiwiServant(a09)栈的最终 URL/头(先按 wup.huya.com 平铺, 与服务端对拍后再校准);响应 sBuffer 可能是 map<string,JceStruct> 假象(解析器已兼容 bytes/struct 两种形态,sGuid_candidates 兜底)。
11.7 doLaunch live 对拍结论(2026-08-29 实测,wire 已 App 对齐)
- 已确认 App 侧全部细节(bjj/cjj 构建器反编译):
a09.getRequestKey() = "tReq",getResponseKey() = "tRsp";bjj.a(servant,func,key,req,otherParams)+new UniPacket(true)(= v3 + _newData map<string,byte[]>);值 =write(struct,0)= struct_begin(0)+fields+end,map 值头 SIMPLE_LIST(0x1d) 与登录黄金帧逐字节同构;- URL 路径 = /launch/doLaunch(hyns 约定
"/"+servant+"/"+func); - sBuffer 额外键:a09.getOtherParams() 追加
platform/version/channel/(yyuid/uid/imei)(u9a.put 裸字符串值,map 头含全部键,实测服务端接受此形态); - 传输类型服务端动态下发:
FunctionTransportModule.getTransportType("launch#doLaunch")查动态配置 mFunctionTransportMap(缺省 mDefaultTransportType)——HTTP 是否 doLaunch 真渠道 未定;zij.a().b() 还支持 cdn.wup.huya.com IP 列表直连 :80(明文 HTTP/WS 形态)。
- live 实测序列(全部 HTTP 200,服务端回声 servant/func + requestId):
变体 STATUS_RESULT_DESC 键 _wup_data/ 根路径UniAttribute not found key:tReq键 tReq(值=wrapped/SIMPLE_LIST)read 'struct' type mismatch, tag: 0, get type: 12.键 tReq值=裸字段/STRING4/guid=空/黄金guid/5键 map同上(恒定) 值=0x0c 单字节 完全复现同错(=tag0 ZERO,服务端确实在解析值) 值=空 require field not exist, tag: 0(解析走到流尾)无 tReq 键 UniAttribute not found key:tReq - 结论:信封/键/路径已被服务端接受;tReq 值永远被拒且错误与内容无关 → 最可能 =
服务端对 tReq 值的解析语义与本地 djce 版本不同(或 HTTP 非 doLaunch 真渠道)。
悬而未决点,定案需 App 真实 doLaunch 帧:
- 约束内路径全试过:logcat 无 klog(自定义 xlog 落盘);xlog/mmap2 拉取为 mars 压缩
格式,无公开魔数解不动;17:17 冷启动走
initCache from disc(缓存未过期 → 不发 网络 doLaunch);禁 pm clear ⇒ 无法制造首次启动场景;无模拟器;spawn/attach 收敛。 - 可选项:① 授权一次"代理+系统CA"被动抓包(可逆,无 pm clear/attach);② 装模拟器 全新安装(必然触发 doLaunch);③ 继续纯 PC 侧拆 mars xlog。
- 约束内路径全试过:logcat 无 klog(自定义 xlog 落盘);xlog/mmap2 拉取为 mars 压缩
格式,无公开魔数解不动;17:17 冷启动走
- 前置条件不变:live 复测前先落 docs §11.5 步骤 1 的确定性实验设计(同一虚拟指纹 多次请求 → sGuid 是否一致 / 是否 = f(字段))。若 sGuid 随机签发,则铸币=整套身份模板 随机化后由登录链验证服务端接受度。
§11.8 铸币机打通:doLaunch 结构 bug 定案 + 服务端确定性签发 (2026-08-28)
破案链
- 真机全通道对拍:多轮被动抓包/WG 全解密/Frida SSL 明文 — wup.huya.com 之外的
launch servant 真帧 (queryHttpDns) = map 只有 tReq 一个键,值 = JCE struct:
0a(struct_begin tag0) + 字段…… (非此前追加多键 + 0x0c 包装)。 - tReq 恒拒根因:
encode_live_launch_req少写外层 struct_begin(0) — UserId 结构体 被直接顶到顶层,服务器解析 LiveLaunchReq 时 tag0(tId) 读到 UserId 内部 lUid 的 ZERO_TAG(0x0c=type12) → 恒定错误read 'struct' type mismatch, tag: 0, get type: 12。 - 修复 = 双层 struct_begin(0) + 收尾 struct_end() + map 只留 tReq 键。
live 实证 (POST https://wup.huya.com 根路径)
| 指纹 | sGuid (tRsp.tag0, 32hex) | 确定性 |
|---|---|---|
| 默认 (mid=1e8bdf7d4f7a01d3, device_id=f2f6…) | 0a7dfaa882938a6ab502511452142c57 |
多次同值 ✓ |
| mid=9c41d0a7b3e5f281 (+device_id 换) | 0a7dd9dc1190916aba02dcf4ddf45b78 |
两次同值 ✓ |
| mid=31415f26a7c8b9d0, imei=861234… | 0a7d90756f90916a9e023aa03ba0e901 |
两次同值 ✓ |
| 仅 device_id 变 (mid 不变) | = 默认值 | device_id 不驱动 |
结论:sGuid = f(mid) —— 服务端对同一 mid 确定性签发同一 sGuid;mid 一换 sGuid 即变。 mid = LiveUserbase→tUAEx→t5=sMId (16hex),可任意铸造 → doLaunch 收 sGuid → §11.10 更正: GUID32(sGuid) 与 HDID32(登录帧t1.t0) 是两套独立 32hex 体系, sGuid 不能作登录 hdid。 §11 旧结论"GUID 唯一不可铸造"正式推翻; 纯算法铸币闭环 = mid(铸) → doLaunch → sGuid → 登录链。
工具落地
tools/huya_launch_mint.py:
encode_live_launch_req外层 struct_begin/end 修复;- build 只发 tReq 单键 (对齐真机帧);
parse_launch_rsp支持 gzip +\x06\x20(32hex)sGuid 可靠提取;--live一条命令出 sGuid; 确定性矩阵可复现。
§11.9 铸币机接入登录链验收 (2026-08-28 夜, 3 个测试账号)
device_mint.py 落地
tools/device_mint.py (编排器):
--mint-only [mid]: mid → doLaunch → sGuid ×3 确定性--login <acct> <pwd> [--mid X]: 铸币 → 零设备注册链(gen_fresh_identity) → WUP 登录(铸币 hdid)--control <acct> <pwd>: 金样本 hdid 登录 (对照基线)tools/huya_launch_mint.mint_sguid(): 单函数铸币上线 (gzip + sGuid 提取)
验收矩阵 (账号 hy_300030430/aa778899)
| 路径 | 结果 | 解读 |
|---|---|---|
| 零设备注册链 (getDfpConfig→selectOperator→dfpReport) | ✅ HTTP200, 新鲜 action(180B) + device_id(40hex) | 注册链仍在线 |
| 铸币 doLaunch (mid=aabbccdd00112233 ×2) | ✅ 0a7d910eb99a916a3801d7b0d8b02bce 两次同值 |
sGuid=f(mid) 确定性 |
| 铸币 hdid 登录 (hdid=上述 sGuid) | ❌ 652B APP_SIGN_NOT_MATCH "APP签名不匹配" |
签名校验拦截 |
| 对照: 金样本 hdid 登录 (ed0db8…) | ⚠️ NEED_RISK (safe_auth 滑块 URL) | 签名通过, 进风控 (非签名错误) |
结论: 铸币机的边界精确化
- sGuid(32hex hdid) = 可铸 —— doLaunch 按 mid 确定性签发, 服务端接受度 ✅ (§11.8)。
- 登录链的签名 = 独立硬锚: hypasswordLogin 的 APP_SIGN = native (libudbauthunify/libhydeviceid) 私钥签名覆盖请求字段 (hdid/app_version 换值即错), 与 doLaunch 的 sGuid 是两套体系 —— 铸币 hdid ≠ 签名证书, 登录被签名层拦截。
- 旧结论"32hex hdid 不可铸造"的准确表述修正为:hdid 可铸造 (服务端签发), 但 WUP 登录需要 native 私钥证书重签 (注册链/OTA/safe_auth 证书) —— 登录签名的纯代码复刻 = 下一道关 (记录: AESkeyMgr 钥表 + bigaes 事件已定位, 未复刻)。
- device_mint 的登录接入 = 完成到"签名边界"可复现: 铸币→注册链→登录→APP_SIGN 恒定复现。
§11.10 登录 hdid 隔离实验: sGuid ≠ 登录 hdid (两个 32hex 体系) (2026-08-28 夜)
隔离实验 (金样本账号 hy_300023887, 同一构建器, 仅换 hdid)
| 请求 | 响应 | 解读 |
|---|---|---|
| 原样重放证据金样本 bin (旧 session/旧 requestId) | 1571B 完整登录数据 | R3 奇迹今夜复现 — 登录链 cred 可用 |
| Python 构建 金样本hdid (ed0db8…) | 1571B OK-data | 构建 = 100% 金样本 (1B 差无关) |
| 同构建 铸币hdid (0a7d91…, doLaunch sGuid) | 652B APP_SIGN_NOT_MATCH | 唯一变量 = hdid |
决定性澄清: 手机同时存在两个 32hex, 之前文档混为一谈
- GUID/sGuid =
0a7dfaa882938a6ab502511452142c57(HySignalGUIDCache) —— doLaunch 按 mid 签发, 铸币机已打通 (§11.8)。 - 登录 hdid =
ed0db8334cadd236c00cadf7e11ab5a5(golden) —— 登录帧 t1.t0, 格式同为 32hex 但= 另一套服务端校验体系: 换值 → APP_SIGN_NOT_MATCH 硬错。 - 目标原始假设 "sGuid → 登录帧 tag0" = 被证伪: doLaunch 的 sGuid 不能直接作登录 hdid; 登录 hdid 是 native(libhydeviceid/libudbauthunify 私钥)签发的设备证书, 服务端按 证书(而非注册链)校验 —— 纯 Python 铸造 sGuid 后仍需: (a) native 私钥证书重签 (unidbg 侧差分 / AESkeyMgr 钥表) 或 (b) 若 hdid=服务端可重签 token, 找到签发 hdid-cert 的 HTTP 通道 (device_register/OTA 变体)。
已固证据 (evidence/mint/)
- login_golden_acct_golden_hdid.bin (1571B cred 响应)
- login_golden_acct_mint_hdid.bin (652B APP_SIGN)
- login_golden_hdid.bin / login_minted_hdid.bin (测试账号版)
- doLaunch_rsp_gzip_313B_partial.bin
§11.10 补充: hdid 全链路不可见 (2026-08-28 夜实测)
- 登录 hdid (ed0db8…) 不出现在: 全部 WG/mitm 解密流量(WUP/HTTP 各帧)、 Frida SSL 明文、shared_prefs、files —— 唯一出现点 = 登录帧 t1.t0 本身 + 旧 frida 的 FOUND_HDID_32 (lib setSafeDeviceId 内存事件)。
- 推论: hdid-cert = 设备首次初始化时 native(私钥)签名 + 服务端侧存档; 服务端按"该 hdid 是否存在于注册设备库"校验 (APP_SIGN)。无上行明文通道可抓, 纯代码复刻 = 需 unidbg 重放 native 签名 (AESkeyMgr/bigaes 钥表线索已记录)。
§11.11 标识符全清单与去混 (2026-08-28 夜, 用户要求彻底厘清)
设备上/证据中的全部 hex 标识 (逐一核实)
| # | 值 | 长度 | 角色 | 来源/渠道 | 可铸造? |
|---|---|---|---|---|---|
| G1 | 0a7dfaa882938a6ab502511452142c57 |
32 | GUID/sGuid: HySignalGUIDCache.xml + hysignalconfigcache 双处一致 | doLaunch 按 mid 签发 (mid=1e8bdf7d=手机当前mid) | ✅ 可铸 (§11.8) |
| L1 | ed0db8334cadd236c00cadf7e11ab5a5 |
32 | 登录 hdid (WUP hypasswordLogin t1.t0) = GOLDEN | native 生成 + setDeviceInfo 上报, 不上行明文 (§11.10) | ❌ 纯Python不可铸 |
| D1 | 5b434c778490889697170e225029f56aff19ca47 |
40 | HYSIGNAL_DEVICE_ID_KEY (当前手机) | hysignal 缓存 | ? (未研究) |
| D2 | 7c5387e0539c023c31c4ff0e807e7256117385ee |
40 | 登录帧 t2.t8 device_id (GOLDEN) = 库 getHDID 期望 | libhydeviceid getHDID@0x20f0c8 | unidbg 可调 |
| D3 | 02df398797432eadefcc12767119ad5e80999389 |
40 | CDID (库 getCDID 期望) | libhydeviceid | unidbg 可调 |
| M1 | 1e8bdf7d4f7a01d3 |
16 | mid (库 getMID 期望 = 手机当前 mid = 铸币默认 mid) | libhydeviceid getMID@0x20fba8 | ✅ 可铸 (任意16hex) |
当前在搞哪一个 (定论)
登录帧 t1.t0 的 32hex hdid (L1) —— 它就是目标。决定性实验 (金样本账号, 仅换 hdid): 手机当前 GUID (G1) 作 hdid ❌ 被拒; 金样本 hdid (L1) ✅ 出 cred。
已完成 / 未完成
- ✅ doLaunch 铸币机: sGuid (G1 系) = f(mid), 确定性, live 多次复现
- ✅ 零设备注册链: 每账号新鲜 action/device_id (t2/t5)
- ✅ 登录管线: 金样本 hdid + 新鲜 action → 1571B cred (纯 Python 出 cred 全链路通)
- ❌ 登录 hdid (L1) 的纯代码铸造: 与 GUID 无关 (手机自己的 G1 也被拒); 它只存在于登录帧 + native 内存 (setSafeDeviceId), 无 HTTP 签发通道 (§11.10 全链路不可见)
- ❌ unidbg 重放 32hex-hdid 生成: 库已定位 getGUID/getCDID/getHDID(40hex)/getMID, 32hex 登录-hdid 的生成函数尚未定位 (getHDID 是 40hex, 非登录 hdid)
下步(未完成工作)
- libhydeviceid 内定位 32hex-hdid 生成函数 (unidbg 枚举 32hex 返回的导出/偏移)
- 若存在: 输入=?(androidId/config/MID 模型已有) → 铸币设备独立 hdid → 登录
- 若不存在: 登录 hdid = 设备级私钥证书, 纯代码不可达 → 每账号独立设备需真机/模拟器池