Files
live-hub-py/docs/HUYA_HDID_ALGORITHM_GEN.md
T
yml2213 d2adfb25ad feat(huya): doLaunch live对拍定案 - 信封/键/路径被服务端接受, tReq值恒定拒绝
- App侧全细节确认: a09.getRequestKey=tReq / UniPacket(true)=v3 / 值=write(struct,0)
  / URL=/launch/doLaunch / getOtherParams追加 platform/version/channel/uid 键
  / 传输类型服务端动态下发(HTTP是否真渠道未定)
- live诊断: E(空值)->require field / F(缺键)->not found key / G(0x0c)->完全复现
  'read struct type mismatch tag0 type12' -> 服务端确实解析tReq值, 语义与本地djce不同
- 工具升级: --live 用5键 App同等 map; 悬而未决点=需App真实doLaunch帧(约束内路径全探)
  - logcat无klog / xlog+mmap2 mars压缩不解 / 冷启动走缓存不发网络launch / 禁pm clear
  - 可选项: 代理+CA被动抓包 / 模拟器新装 / 继续抠mars xlog
2026-08-28 19:13:36 +08:00

353 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 虎牙 32hex hdid 算法生成路径分析(不用采集,纯算法铸币)
> 前置结论(见 `HUYA_HDID_RESEARCH.md`):32hex hdidWUP 登录帧 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` | 有 base64SDID 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 getGUID0x20f8c8)反汇编现场 = 标准 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 生成器(最终形态,周级)
1. E2 输入图 + E4 硬锚值 → 复原"种子串拼接格式"(大概率就是若干固定前缀 + S 逐项);
2. 用金样本 pair(本节 §2.2 常量 + 真机 S)在 Python 里**穷举组合对拍**
`MD5(盐‖S)``SHA1(盐‖S)``AES-ECB/CBC``HMAC-MD5/SHA1` × 各候选密钥/盐;
3. 命中即纯 Python 版本直接成立(**无需解一个字节的 OLLVM**);未命中再走 Ghidra+
锚点反汇编看拼接/变换细节;
4. 产出 `tools/hdid_mint.py` + `core/huya/device_mint.py`(零 native 依赖)。
要点:dump 已解密、原语已定位、密钥候选已提取 —— 静态路上最贵的 "OLLVM
平坦化全解" **不是必需项**,先用黑盒对拍把算法筛出来。
### 路径 B:unidbg 仿真执行(天级,**推荐先行**)
1. 装 JDK(本机 JVM 失效:`/usr/bin/java` 空壳、sdkman 空目录 → brew/sdkman 装
OpenJDK 17/21);拉 unidbgzhkl0228/unidbg);
2. 把 decrypt dump 载入 ARM64 仿真,hookJNI env、`__system_property_get`
`open/read``ioctl`、TelephonyManager 等 JNI 桩;
3. **金测试**:喂真机 S 实测值 → 期望输出 `0a7dfaa8...`;对上了 = 整个算法已在
PC 上可执行;
4. 之后每套虚拟 S 跑一次 init+get* 即铸一套身份 —— **这就是"算法生成"的落地形式**
5. 风险: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...`),观察:注册是否成功、登录是否放行、滑块/风控是否升级。
- 若服务端只校验"格式/自洽/已注册",铸币路径畅通;若校验设备库真实性,则需评估
深层对抗(那是另一个量级的问题,先实测出结论再投入)。
## 七、推荐执行序列(蓝线)
| 阶段 | 动作 | 依赖 | 产出 |
|---|---|---|---|
| D01~2 天) | E1/E2/E3/E4 真机实验 | 真机 + 稳定通道(现成) | 输入图、种子实测值、差分语料库 |
| D1(并行) | Python 黑盒对拍(§五-A2 | D0 语料 + §2.2 常量 | 是/否命中标准构造 |
| D1(并行) | 路径 BJDK + unidbg + 金测试 | D0 输入图 | 仿真铸币机(保底形态) |
| D2 | 铸币 → 注册 → 登录闭环实测 | D1 任一成果 | 服务端接受度结论 |
| D3 | `device_mint.py` 接入 `app_login.py`(每账号独立设备) | D2 通过 | 生产落地 |
## 八、风险清单
1. 种子含 TEE/Keystore 级硬锚且无再生路径 → 仿真下仍可铸(S 自拟),但服务端可能
拒绝"无对应真实硬件"的身份 → 以第六节实测为准;
2. §2.2 常量为运行时下发替代 → 对拍用真机实测值逐个对账,避免拿死值硬套;
3. qimei 参与 GUID 时,qimei 本身依赖 AndroidID/IMEI → 铸币需把 qimei 一并纳入
虚拟 S(unidbg 下可控);
4. OLLVM 全量反平坦化(数周)**不作为必经路线**——只有黑盒对拍全失败才需要;
5. 服务端风控升级(针对新设备登录的滑块/验证)→ 已有 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_androidId` `cdid_version` `hydeviceid_guid`** → `NativeBridge.b(102)`(=MID/androidId)
`getDataDir`。getGUID 读 `hydeviceid_guid` → 空则 `WupHelper.getGuid()``NativeBridge.b(100)`
### 9.2 unidbg 金测试通过(决定性)
- 解密 FULL dumpr--+rw- 全段,`phone_dump_hydev_full.py`+ 解密数据合入原文件
`merge_decrypted.py`,unidbg 重定位自会覆盖指针槽)。
- 喂真机种子(ANDROID_ID / hydeviceid_config / NativeBridge.b 实测值):
- **`androidId` pref = `PDSuHxt854IZRR5Y7AGyaQ==` —— 与真机逐字节一致**(采集器 AES 加密可复现)
- **getGUID = `0a7dfaa882938a6ab502511452142c57` —— 与真机一致**
- 工具链:JDK21brew+ unidbgModule 冲突补丁/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)`,输入全部可控可计算。
- 下一步两条线(任选其一即可闭环铸币):
1. **unidbg 双库/三库加载**:找到并加载 b 原生所在库(可疑:运行期 dex 动态下载的另一个 .so,
需在真机 dlopen 全拦截清单里补齐),让 b(100) 真实计算;
2. **静态还原 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/udb0 命中;③健康进程 attach+
重触发 init/getGUIDRN 全程 0 命中。
- **unidbg verbose 实锤**hydeviceid JNI_OnLoad 只 `FindClass(NativeBridge)+NewGlobalRef`(不注册),
udb JNI_OnLoad 只注册 HuyaAuthCoreNativeBridge.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 仍未真注册
- libudbauthunifyJNI_OnLoad@0x262620RN 调用点 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 侧)
1. **歧义清除**:确认 NativeBridge.b/klog 到底是否 nativedex 反射 isNative,用 SPAWN+bypass
轻探,一次即可);若 native → 在 hydeviceid 代码中用"运行期构建表"模式(构造
{name,sig,fnPtr} 时 ldr 常量页)找注册函数;若 Java → 直接读运行期 dex 里 getGuid/b 逻辑。
2. **oracle 差分**unidbg 侧可控 ANDROID_ID/Settings.Secure 已证明 androidId 密文会变;
b 一旦真注册,`(ANDROID_ID → GUID)` 差分对即可在 unidbg 内批量生成 → MD5/SHA1/AES 对拍。
3. b 的 fnPtr 兜底获取:运行期 dump hydeviceid .data 时同时读 JNINativeMethod 运行期构造表
spawn+bypass 一次抓全,附 base 校验)。
## 十一、静态还原定案(2026-08-29 凌晨,PC 侧完成,无需 SPAWN/真机)
### 11.1 isNative 判定 = 纯 Javadex 直读,比 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 全部 nativecode_off=0RN 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` → 空则 Java `WupHelper.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。
- 与既有认知吻合:旧实验"改 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 铸币路径重定义(下一步蓝线)
1. **服务端签发复现**:用现有纯 Python WUP 链(tools/huya_wup_encoder.py 等)构造虚拟设备指纹的
`doLaunch`/`device_register` 请求(deviceId/IMEI/mid 取自虚拟 profile)→ 收 sGuid →
**先验确定性**:同一指纹多次请求 sGuid 是否一致 / sGuid 是否 = f(请求设备字段)。
2. 若确定性签发:`device_mint.py` = 虚拟指纹 → doLaunch → sGuid →(可选 dfpReport 注册)
→ 现有 WUP 登录链验证服务端接受度(用户既有"每账号一套全新虚拟身份"验收口径不变)。
3. 若 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=sMId
- `UserId`(classes11)t0=lUid(int64) t1=sGuid t2=sToken t3=sHuYaUA t4=sCookie
t5=iTokenType t6=sDeviceInfo t7=sQIMEI
- `LiveLaunchRsp`**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_PROFILEmid 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+endmap 值头
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。
- 前置条件不变:**live 复测前先落 docs §11.5 步骤 1 的确定性实验设计**(同一虚拟指纹
多次请求 → sGuid 是否一致 / 是否 = f(字段))。若 sGuid 随机签发,则铸币=整套身份模板
随机化后由登录链验证服务端接受度。