Files
live-hub-py/docs/HUYA_HDID_ALGORITHM_GEN.md
T

870 lines
58 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.
<!-- ⚠️ 命名规范强制约束:本档中所有设备标识必须带前缀。规范见 docs/HUYA_ID_NOMENCLATURE.md。
GUID32=doLaunch sGuid(32hex, launch体系) | HDID32=登录帧t1.t0设备证书(32hex) |
DEVID40=注册链t5/t2.t8 device_id(40hex) | MID16=mid(16hex) | ACTION=注册链t2授权令牌。
旧段落中裸词按上下文语义理解:登录相关=HDID32launch/铸币=GUID3240hex=DEVID40。 -->
# 虎牙 32hex hdid 算法生成路径分析(不用采集,纯算法铸币)
> 前置结论(见 `HUYA_HDID_RESEARCH.md`):32hex hdidWUP 登录帧 field1.tag0= 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。⚠️ 旧假设 —— 已于 §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 铸币路径重定义(下一步蓝线)
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 随机签发,则铸币=整套身份模板
随机化后由登录链验证服务端接受度。
## §11.8 铸币机打通:doLaunch 结构 bug 定案 + 服务端确定性签发 (2026-08-28)
### 破案链
1. **真机全通道对拍**:多轮被动抓包/WG 全解密/Frida SSL 明文 — wup.huya.com 之外的
launch servant 真帧 (queryHttpDns) = **map 只有 tReq 一个键**,值 = JCE struct:
`0a`(struct_begin tag0) + 字段…… (非此前追加多键 + 0x0c 包装)。
2. **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`。
3. 修复 = 双层 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 确定性签发同一 sGuidmid 一换 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) | **签名通过**, 进风控 (非签名错误) |
### 结论: 铸币机的边界精确化
1. **sGuid(32hex hdid) = 可铸** —— doLaunch 按 mid 确定性签发, 服务端接受度 ✅ (§11.8)。
2. **登录链的签名 = 独立硬锚**: hypasswordLogin 的 APP_SIGN = native
(libudbauthunify/libhydeviceid) 私钥签名覆盖请求字段 (hdid/app_version 换值即错),
与 doLaunch 的 sGuid 是两套体系 —— 铸币 hdid ≠ 签名证书, 登录被签名层拦截。
3. 旧结论"32hex hdid 不可铸造"的准确表述修正为:**hdid 可铸造** (服务端签发),
但 WUP 登录需要 native 私钥证书重签 (注册链/OTA/safe_auth 证书) ——
**登录签名的纯代码复刻 = 下一道关** (记录: AESkeyMgr 钥表 + bigaes 事件已定位, 未复刻)。
4. 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)
### 下步(未完成工作)
1. libhydeviceid 内定位 32hex-hdid 生成函数 (unidbg 枚举 32hex 返回的导出/偏移)
2. 若存在: 输入=?(androidId/config/MID 模型已有) → 铸币设备独立 hdid → 登录
3. 若不存在: 登录 hdid = 设备级私钥证书, 纯代码不可达 → 每账号独立设备需真机/模拟器池
## §11.12 Java 侧 HDID32 链全映射 + 签名锚定实验 (2026-08-28 夜)
### Java 调用链 (jadx classes5.dex 实证)
```
HuyaAuth.initSDID() (com/huyaudbunify/HuyaAuth.java:1261)
├─ sdid = dj3.getSDID() → HyDeviceProxy.g() → NativeEntry.getSDID() ← ACTION(注册链t2)
└─ hdid = dj3.getHDID() → HyDeviceProxy.e() → NativeEntry.getHDID() ← native getHDID@0x20f0c8
(setSafeDeviceId(sdid, hdid) → WUP 帧绑定)
NativeBridge.b(i) 语义全表 (b→fj3.c):
1=APPID(5008) 2=versionName(13.4.22) 3=SDK版本(1.14.68) 5=安装时间json
100=GUID 101/102=MID(ANDROID_ID) 104/105=空 106=64hex
107=qimei16 108=qimei36 300/303/304=...
```
- 登录帧 t1.t0(HDID32) 的 JAVA 源头 = NativeEntry.getHDID() (registerNative@0x20f0c8)。
- unidbg 收集器 b(3)=SDK版本 已补桩 (此前 null 导致收集不完整), 但 getter 仍读缓存
(GUID/HDID 均为缓存读, 缓存由 JAVA 侧 doLaunch/注册链回填) —— 下步: 仿真 JAVA 回填序。
### 签名锚定实验 (金样本账号, 逐变量)
| 变量 | hdid(t1.t0) | device_id(t2.t8) | 结果 |
|---|---|---|---|
| 全金样本 | ed0db8334cadd236c00cadf7e11ab5a5 | 7c5387e0…(40hex) | ✅ 1571B cred |
| 换 device_id | ed0db8… | **deadbeef…(随机40hex)** | ✅ **1571B cred** |
| 换 hdid | **0a7dfa…(手机当前GUID32)** | 7c5387e0… | ❌ APP_SIGN_NOT_MATCH |
| 换 hdid | **0a7d91…(铸币sGuid)** | 7c5387e0… | ❌ APP_SIGN_NOT_MATCH |
**定论: 登录 APP_SIGN 唯一锚 = HDID32(t1.t0); DEVID40 自由; GUID32(铸币)≠登录证书体系。**
## §11.13 32hex 派生被证伪 + AESkeyMgr 钥表清单 (2026-08-28 夜)
### MD5/sha1 派生实验 (6 组合全拒)
hdid = md5(AID)/md5(MID)/md5(AID+MID)/md5(AID+CFG)/md5(AID+MID+CFG)/sha1(AID)[:32]
→ 全部 APP_SIGN_NOT_MATCH (对照金样本 ed0db8 = 1571B cred)。**32hex ≠ 无钥哈希**。
### libudbauthunify_merged.so 明文钥表 (24 字符 base62, rodata)
已定位 16 把 + AESkeyMgr::C2@0x26fcc8 装载钥:
| 钥 | VA | 备注 |
|---|---|---|
| `owNMiaCgcHmqoTr3iRamFuHj` | 0x1b898b | **C2(26fcc8) 实际装载** (std::string assign) |
| `KFWAH2cbkkNgFAgQPzriFPcU` | 0x1b9f27 | R2 旧记录 "请求签名专用钥" 候选 |
| `ybEWdvkjGjPQa2ugireHJCLL` | 0x1b9f40 | R2 "邻串" |
| `4VYcPdvKKqjBHZtCmbroRXHk` | 0x1bc810 | 证书钥族 (R2 记录) |
| `xXEDWqiKLGwEZ6HubEiswCqK` | 0x1bc829 | 证书钥族 |
| `3FMHubdKosFrhmXNLHTNHZwe` | 0x1bc842 | 证书钥族 |
| alkdfEINVBE4fjhidfioYgMN / asdfhSHFSDFI2sSDjksSDFks / dzTkHQDPmAsPVgEhrwWs7RzP / hgtuioiulbsdjwEHHMYU5gjt / fNoMrJhbEMXm8nHcHXTNovaL / jBxBvaruXYzrvNsacsAD4BfT / masldDSIFGJjdfio5hkhsSDF / novwSHidrhDg1ADUKLkejnHR / nskdI7MDGKSDJsnadjdoonvs / nvirAGiohDsfdiHDSUIGH5A9 / qZsBbgYpBBcdkUFp3qfaFRCW / ubPzXJwj6hrYxPYYjnpePrzJ / whdFNGFdfJHMJbdfg6jhdsFG / xnkdDFIERRIPT5dfhfgiJ0FD | 分散 | 疑似 AESkeyMgr 全钥表 (C1/C2/… 各 id) |
完整清单: evidence/mint/udb_keytable_24char.json
### 下步 (unidbg 执行路线)
C2/钥表已定位 → 下一步 = unidbg 调 AESkeyMgr/C2-相关 sign/AES 函数 (hyudb_otp_encrypt@0x32fa24,
UdbAESUtil::encrypt, xxtea...) 以可控输入差分 → 复刻 32hex-HDID32 生成。
## §11.14 AESkeyMgr 钥表按 id 注册 + encode_aes API 定案 (2026-08-28 夜)
### AESkeyMgr::C2@0x26fcc8 = 钥表构造 (按 id 注册 24 字符钥)
| id | 钥 | VA |
|---|---|---|
| 1 | `ZMHAVPRaxJ3MtXDjduUnXAKQ` | 0x1c155e |
| 2 | `4VYcPdvKKqjBHZtCmbroRXHk` | 0x1bc810 |
| 3 | `xXEDWqiKLGwEZ6HubEiswCqK` | 0x1bc829 |
| 4 | `3FMHubdKosFrhmXNLHTNHZwe` | 0x1bc842 |
| (C2 前置装载) | `owNMiaCgcHmqoTr3iRamFuHj` | 0x1b898b |
| KFWAH/ybEWdv 族 | 0x1b9f27/0x1b9f40 | R2 旧线索 |
### encode_aes API (0x330218) 完全解码
```
encode_aes(IN:std::string&, KEY:std::string&, OUT:std::string&)
1. OUT.assign(常量串@0x1ba000+0xa90) ← 初始化输出
2. UdbAESUtilC1(KEY) ← 构造器吞 KEY
3. UdbAESUtil::encrypt(IN, OUT) ← 加密
```
- lib 自带完整 AES 原语: KeyExpansion@0x24ebd4, Cipher@0x24f314, MixColumns/AddRoundKey/
InvSubBytes/InvShiftRows/InvMixColumns/FFmul — **纯自研 AES (非 openssl!)**
- `UdbAESUtil::_encrypt@0x24f9f0` = 底层; `hyudb_otp_encrypt@0x32fa24` = OTP 封装
### 朴素 AES 爆破实验 (证伪)
24B 钥 × {ECB, CBC-zeroIV} × {md5(aid/mid/…), sha1, aid-bytes, cfg-b64…, 10 候选明文}
→ 与 ed0db8 零命中。**key 必经 UdbAESUtilC1 加工 (派生/置换) 或自研变异 AES**。
### 下步 (定位派生)
A. unidbg 调 encode_aes(in, key) → 输出对拍 (可控输入差分)
B. 静态读 UdbAESUtilC1/KeyExpansion(0x24ebd4) 取派生规则 → Python 复刻
## §11.15 标准 AES 确认 + 爆破全证明伪 (2026-08-28 夜)
### SBOX 对照实验
UdbAESUtil 的盒表 @0x1c5d8e (SBOX) + @0x1c5e8e (INV-SBOX) = **标准 AES 盒** (逐字节一致)。
→ 自研代码 = 标准 AES 的重实现 (非变异)。KeyExpansion@0x24ebd4 列主序装载 (Nk=6 → AES-192)。
### 爆破总矩阵 (全部零命中, 对照 ed0db8)
| 维度 | 变体 |
|---|---|
| 钥 | 24B 原文 / 列转置 / base64 解码 18B→补丁 / md5(钥) / … × 9 键 |
| 模式 | ECB / CBC-zeroIV |
| 明文 | md5(aid/mid/aid+cfg/…), sha1, aid-bytes, cfg-b64[:16], mid-bytes … × 11 |
**结论: 32hex-HDID32 = 标准 AES 密文, 但 钥材料/明文序列化/模式 三者未定。**
### 下步 (决定性二选一)
A. **unidbg 调 encode_aes(IN, KEY) → OUT**: 可控输入差分, 直接对拍 ed0db8 + 反推钥/明文
B. **静态读完整 encrypt 路径** (0x24f9f0 _encrypt + Cipher@0x24f314): 明文源 + 模式 + 派生
## §11.16 unidbg encode_aes 探针上线 + 64B 钥材料发现 (2026-08-28 夜)
### tools/unidbg/hydev/src/hydev/AesProbe.java (可执行差分源!)
- 直接调 libudbauthunify_merged.so 的 encode_aes@0x330218 (IN, KEY, OUT)
- NDK-libc++ std::string ABI (short ≤22B inline / long heap) 正确读写字
- 权威输出 (已知输入):
| IN | KEY | 输出 |
|---|---|---|
| 0123456789abcdef | ZMHAVPRaxJ3MtXDjduUnXAKQ(24B) | ba3fb8f156b03a9d7db185d1254e0730 |
| 0123456789abcdef | owNMiaCgcHmqoTr3iRamFuHj | 74bd517c7e5d2bbce63c8e98a192c760 |
| 0123456789abcdef | ZMHAV…+64B-pad | (同24B) ba3fb8f1… |
| 0123456789abcdef | 64B-"0123456789…" | 72727e881edcfd0100a718687909b565 |
### KeyExpansion@0x24ebd4 关键发现
- 密钥装载 = **以 16B 步长读入** (x1[0]/x1[16]/x1[32]/x1[48] → 字) → **钥材料 64B** (非 24B!)
- 输出 = 64B (状态/扩展轮钥?) — Nk/Nr 分支在 0x24ec78-cmp#0x29
### 对比结论
标准 AES-192-ECB(同一输入+24B钥) = b1e3abcc… ≠ 原生 ba3fb8f1… — **原生 AES 变体 (64B 钥材料 + 自定义扩展)**。
华生线索: 原生输出对「输入块内含 NUL 的 64B 钥」敏感 — 下一轮 = Nk 分支 + Cipher 轮结构静态定案 → Python 复刻。
## §11.17 hyudb_otp_encrypt 组合链解码 (2026-08-28 夜)
### 0x32fa24 (hyudb_otp_encrypt) 组件链
```
cred_packls(…多个字段打包) ← 请求材料构建
xxtea_encrypt @0x457e60 ← XXTEA 层
AESkeyMgrC2 → AESkeyMgr::getkey(int,int) ← 双 id 取钥
string-insert(钥) + md5_char16 ← 32hex 材料 (MD5-hex 32 字符!)
UdbAESUtilC1 + encrypt ← AES 层
```
### 32hex 生成假设
- md5_char16 = 32 字符 MD5-hex — **与 HDID32 的 32hex 形态吻合** (候选公式候选)
- md5(钥)/md5(钥+域) 组合爆破 → 与 ed0db8 仍零命中 (材料=getkey 双 id 产物 + cred_pack 序列化, 未定)
### 工作台状态 (本轮新增)
- tools/unidbg/hydev/src/hydev/AesProbe.java: encode_aes 差分源 (已验证)
- 子代理: 静态复刻 KeyExpansion/Cipher → Python (验证目标 = 探针 3 向量)
## §11.18 md5_char16 变体识别 + 探针全家桶 (2026-08-28 夜)
### md5_char16@0x32e71c = "MD5-hex 中段 16 字符" (非 32hex 本体)
- md5_char16("") → 8f00b204e9800998 = md5("").hex[8:24] (d41d8cd9⁈**8f00b204e9800998**⁈ecf8427e) ✓
- md5_char16("0123456789") → 5d69b566979b86e2 = md5.hex[8:24] ✓
→ OTP 链的 32hex 形态 = 其它环节 (md5 全串 / AES 输出)。
### AesProbe 可调函数 (unidbg 权威差分)
- encode_aes@0x330218 (IN,KEY,OUT) ✓
- md5_char16@0x32e71c (OUT,IN) ✓
- xxtea_encrypt@0x32e83c (OUT,IN,KEY): xxtea("0123456789abcdef","ZMHAV…")→124105f9d7a549dfb6bc756cade6679ed412a6bd
- hyudb_otp_encrypt@0x32fa24 (string,h,h,string,string,string,h,m,string&): 参数语义待定 (现按序猜, 出空)
### 在途
- 子代理静态复刻 KeyExpansion/Cipher → huya_aes_replica.py (3 验证向量)
- 下步: OTP 参数语义 (读 32fa24 前半 arg 处理) + getkey 双 id 材料
## §11.19 hyudb_otp_encrypt 击穿: 参数语义 (arg1 必须=2) + 32hex 结构现形 (2026-08-28 夜)
### 参数语义 (静态)
- arg1 (uint8) 必须 == 2 (cmp #2 b.ne early-return) — 探测值修正后链路全通
- 32fa24 签名: (string-in, h=2, h, string, string, string, h, m, string&out)
### OTP 权威输出 (unidbg)
| 输入 | 输出 (hex) |
|---|---|
| in=abc key=ZMHAV… | `03 0088fa6ee519d52f61348e01d6e74c81cd8b38f113056fe498b0c7de2ffd52c7b0` |
| in=1e8bdf7d… key=KFWAH… | `03 0043c5bf3f792216e249116c968c84ff3a8b38f113056fe498b0c7de2ffd52c7b0` |
**结构: 0x03 + 可变(输入依赖) + 固定 32hex 尾 `8b38f113056fe498b0c7de2ffd52c7b0`** — 32hex 形态实锤,
可变段 = 输入序列化 + AES/xxtea 层, 固定尾 = 静态钥派生 MAC (跨输入稳定!)。
### 下步 (round7)
1. OTP 输入差分 (变 in/变 key → 可变为/尾变?)
2. 固定尾溯源 (8b38f1… = md5/aes(静态钥材料)?)
3. 32hex-HDID32 (ed0db8) vs OTP 体系对拍 (登录帧 t1.t0 可能 = OTP 输出的某段!)
### §11.19b OTP 差分定性 (2026-08-28 夜)
| in | key | 中段(输入依赖) | 32hex 尾 |
|---|---|---|---|
| abc | ZMHAV… | 0088fa6ee519d52f61348e01d6e74c81cd | 8b38f113056fe498b0c7de2ffd52c7b0 |
| abcX | ZMHAV… | 00bc4882b312f73d5c6f6dcbc15681db76 | **同** |
| abc | owNMia… | 0088fa6e…(同 abc!) | **同** (key 参数被忽略/固定位) |
- 32hex 尾 = **静态钥材料计算值** (非嵌入: so 全文件无 8b38f1/ed0db8 字节)
- 中段 = f(输入) (变输入即变) — 待定性为 md5/xxtea/AES 哪层
- 32hex-HDID32 (ed0db8) 与 OTP 尾 = 同族 MAC 理论的验证对象 (下轮)
## §11.20 OTP 中段公式实证: md5_char16(input ∥ getkey(1)-派生) (2026-08-28 夜)
### 静态流 (32fa24: 32fb60-32fbdc)
```
AESkeyMgrC2() ← 钥表构造
getkey(this, w1=1, x2=第二参数) ← id1=1 (ZMHAV…族)
string::insert(input, pos, getkey产物) ← 钥材料插入输入串
md5_char16(合成串) ← 中段 16B
UdbAESUtilC1(…) + encrypt ← 后段
```
### 差分数据 (中段, 输入依赖)
| in | 中段 (16B) |
|---|---|
| a | 78ea165d2d375a55a13989d176e96f3b |
| ab | 8c84ce0f1b734b1dc8227930936a4fd1 |
| abc | 88fa6ee519d52f61348e01d6e74c81cd |
| abcd | 0cd8ddaeb48a97b03459d4a1dc24a148 |
朴素的 md5_char16(in+key)/md5_char16(key+in) 全组合 = 零命中 → getkey 产物 = **派生钥** (非裸 24B)。
### 下轮
1. 探针直接调 AESkeyMgr::getkey(1,…) 取派生钥 → md5_char16 合成验证
2. 子代理 AES 复刻 (运行中)
## §11.21 里程碑: UdbAESUtil 纯 Python 复刻成功 + t1=ProtoInfo(appSign=HDID32) 对应 (2026-08-28 夜)
### 1) tools/huya_aes_replica.py (617 行, 逐指令复刻, 子代理产出)
- 5 段内嵌 objdump 原文逐条比对一致; **A/B/D 三向量逐字节命中**:
enc("0123456789abcdef", ZMHAV-24B) = ba3fb8f156b03a9d7db185d1254e0730 ✓
enc("0123456789abcdef", 64B-0123…) = 72727e881edcfd0100a718687909b565 ✓
enc("0123456789abcdef", owNMia-24B) = 74bd517c7e5d2bbce63c8e98a192c760 ✓
- **算法 = AES-128 变异**: 64B 钥材只用 key[0..15] (转置装载 rk[i]=key[4*(i%4)+i//4]);
扩展 10 轮×16B=176B (AES-128 形); SubWord 输入=相邻列末字节 + 链式 XOR (非标准词独立);
无独立 ShiftRows (位移吸收进寄存器置换共轭); SBOX/RCON/INV = 标准表。
### 2) 登录帧 t1 结构 = mobilequicklogin.ProtoInfo (结构级定案!)
| t1.tag | 字段 | 金样本值 |
|---|---|---|
| 0 | **appSign** | **ed0db8334cadd236c00cadf7e11ab5a5 (HDID32!!)** |
| 1 | appVer | 13.4.22 |
| 2 | sdkVer | 1.0.80138 |
| 3 | lcid | "" |
| 4 | clientIp | 127.0.0.1 |
| 5 | channel | xiaomi |
| 6 | countryCode | "" |
- **"APP_SIGN" 错误名 = 字段名 appSign 的校验失败** — 32hex-HDID32 = appSign 值 = 签名函数产物!
### 3) 下一关: appSign (ed0db8) 的生成式
- ed0db8 = encode_aes(16B-plaintext, key) 形态候选; 朴素明文组合 (aid/mid/DEVID40…异或/拼/零垫) 零命中
- 下步: (a) 补 InvCipher 解密路径 → 直解 ed0db8 见明文; (b) 探针 encode_aes(设备材料块) 差分对拍
## §11.22 复刻 15/15 全量实证 + C 向量更正 (2026-08-28 夜, 子代理终报)
### tools/huya_aes_replica.py (逐指令转录) + DiffProbe.java (差分权威源)
- **A/B/D/E/R0-R7 共 15 组全部位精确命中真实二进制** (含 8 组随机 16B 明文×随机 64B 钥)
- C 期望值更正: in="1e8bdf7d4f7a01d3"(16 ASCII), key=64B-ZMHAV+NUL → **实跑 = ae26d00a4d5a837baba2903d35135102**
(旧 2cdcf6… = brief 过时值; 算法零残差由随机明文组证明)
- 结论: KeyExpansion 64B 钥材只用 [0..15] (4×4 转置 rk[i]=key[4*(i%4)+i//4]);
扩展 10 轮×176B (列旋转字节链, 标准 SBOX/RCON); 10 轮末轮无 MixColumns;
ShiftRows 吸收进寄存器置换 → 逐指令模拟必需。
### appSign (ed0db8) 唯一剩余未知
- 16B 密文 (encode_aes 形态) — 朴素明文组合 (aid/mid/DEVID40/版本/配置) × 6 钥族 × 转置/b64/md5 = 零命中
- decode_aes 直解 (6 钥) = 32B 无明文 → **钥 = getkey(双 id) 派生材料** (非裸 24B)
- 下步 (round10): 逆向 replaaica 得 decrypt → 或 DiffProbe 调 getkey 取派生钥 → 对拍
## §11.23 getkey 等于裸钥! 堆节点取证 + 明文族爆破零命中 (新目标 R1)
### AESkeyMgr 堆节点取证 (unidbg 全内存扫)
- @0x127ddda0: `ZMHAV…24B` + len-0x18 = **getkey 返回的 = 裸 24B 钥串 (无派生!)**
- @0x127ea000: ZMHAV + 4VYcPdv… + xXEDWq… 相邻节点 = 钥表顺序装载
- @0x127ddf00: ZMHAV + "abc" (输入副本同址 = OTP 的 insert 目标 = 内部副本!)
- 此前「派生钥」假设 → **推翻: 24B 钥直读直用**
### appSign (ed0db8) 明文族爆破 (复刻引擎 × 8 钥 × 19 材料 × 8 形态 = 零命中)
- md5/sha1/sha256 摘要 16B、首尾 16B、双 md5、hex 编码等全灭
- 结论: 明文 ≠ 简单材料哈希 — 可能是 config 解密产物 / 复合序列化 (下轮: OTP insert-pos 差分 + config 88B 解码路径)
## §11.24 OTP 内部字符串缓存池取证 (R2 前夜, 0x127ddbe0 1056B dump)
- 相邻 string 对象: key1(24B ZMHAV!) + "abc"(SSO) + "k"(SSO) + "ivx"(SSO) + key2(owNMia!) + ...
- **`MsgRegSendSms`(13) + `MsgResponceRegSendSms`(21) 协议标签现形** → OTP = 注册/登录
wup 请求加密器 (消息名入串池, 参与 md5/xxtea/AES 组合!) — 纯协议路线的关键拼图
- 各对象 = std::string (data/cap/len/ptr) 排布, 与§11.23 堆节点同区
- 下步: 消息名 + 请求字段序列化 进入 OTP 组合的完整还原 (md5_char16 输入=消息级结构)
## §11.25 decode_aes 全 11 钥直解 ed0db8 = 全二进制垃圾 (R2)
- ybEWdvk…→576ead68… / HuyaUdb27B→c336e348… / xXED/4VYc/3FMH/owNMia/KFWAH/ZMHAV = 全无明文
- **appSign 加密钥 ∉ AESkeyMgr 24B 目录(11把) 文件钥(27B list)**
- decode_aes 输出 = 32B (16B 入双块) → ed0db8 = 16B 密文块 (32hex ✓) 但钥 = 他处
- 方向修正: 找 32hex-hdid 的真正生产方 (encode_aes 之调用者三处已列: OTP/文件/encode — 若 hdid 不走 encode_aes → 查 GetHDID 注册原语周边 + initSDID 的 Java 侧回填)
## §11.26 hydeviceid_config(112B) 直解 = 全钥垃圾 (R3)
- 服务端 config b64→112B; decode_aes(HuyaUdb27B/ZMHAV/owNMia) → ef1a5bb2…/全无明文
- config 加密 = 文件层多密钥组合 (writeFileEx@2523fc 周边) — 非单钥 AES
- appSign 未知收敛: {encode_aes 钥目录 11 把 × 明文族} {config 直解 3 钥} = 零命中
- 下步候选: ①hdid 走非 encode_aes 路径 (GetHDID native 40hex 体系周边/initSDID-Java 回填)
②OTP 消息结构串池 (MsgRegSendSms) 完整排序后 md5 组合验证 ③config 文件层双钥 (xxtea 前垫)
### §11.26b OTP mid 组合排列爆破 (5040 序 × 4 输入) = 零命中
- 串池 7 元素全排列拼接 md5-char16 无一命中 → 组合含长度前缀/TAF 序列化 (池对象带 0x1b/0x17/0x31 头值)
- mid 材料 = cred_packls 编码产物 (len-prefix 字段流), 非裸拼接
## §11.27 harness 复活 + GUID 缓存读取器输入透明性 (R4)
- **重合并 so (merge_decrypted 重跑) → 70621 级: getGUID 回 0a7dfa MATCH!** (缓存恢复)
- 修 b 桩 (101/102 接 System.getProperty) → 指纹可控
- **透明性证明: getGUID/getMID = 直接返回输入值** (aabbccdd00112233 → 同值; 0000…→同值)
→ GUID32 缓存 = Java b(100) 直填 (非 native 计算) — 与 §11.8-§11.10 缓存读取器定案闭环
- 遗留: getHDID = 空 (40hex 缓存缺 b-cell) — 登录 32hex (ed0db8) 与 GUID/MID 缓存非同一格
- 产物: so/libhydeviceid_merged_r2.so (缓存正常版)
## §11.28 hydeviceid r2-so hdid 字符串地图 + init b-cell 全集 (R5)
- init b-cell 调用 = {3, 100, 101, 102, 104} (SDKver/GUID/MID/empty) — 32hex 不在此路径
- r2-so "hdid" 字符串 (6 处):
- 0x3ba484: "id: " / "hdid: " = 日志格式 (32hex 打印点!)
- 0x3dec8c: "hdid"/"sdid" 键对 = device 映射 (setSafeDeviceId 存储)
- 0x3c8930/0x3ddef4/0x3e0040: mid/hdid/guid 键集
- 0x3ba2f4: hdid/ok/adb_ (调试)
- OTP mid 长度前缀族 (JCE varint/1/2/4B BE/LE + 消息名前置) = 零命中 → 组合含更深的 cred_packls 字段流
- 下轮: 0x3ba484-xref (hdid: 日志的调用者 = 32hex 装配函数) + cred_packls 静态串流
## §11.29 A路径实测边界 + getSDID 新值族发现 (R6)
### A路径 (unidbg 跑全 Java 登录流) = 实验性定界
- APK 直载成功 (createDalvikVM(apk) ✓), 双库加载 ✓, HuyaAuth 类可解析 ✓
- **纯 Java 方法不可调** (callStaticJniMethod 只走 JNI 符号; unidbg 无 dex 字节码解释器)
- 若要跑 Java 侧 → ProxyClassFactory 主机 JVM 方案 (dex→class 编译登录链 = 重型)
### getSDID 新值族!
- getSDID@0x20ede8 = `*hZrPb62GskrEeTYcLeUTL1fJ1OR8ky3x9qXQq0s6ICJV5v9T` (=36B base64)
- **输入不透明性: 换指纹 GUID/MID 全变, SDID 恒定** = 静态钥材料计算的 native 证书
- SDID 36B: 16B 块 × encode_aes(8钥)/decode_aes(6钥)/md5/sha1/hmac = 全零命中; 帧字段×SDID 签名族 = 零
- 与 ed0db8 关系未定 (golden 帧 = 08-24 态; harness SDID 恒定同值 ⇒ 若关系存在则可验!)
### OTP 消息名差分
hypasswordLogin/MsgLoginReq/LogLoginReq → mid 各异, tail 恒定 8b38f1 → appSign ≠ OTP 任何段
### 下轮: setSafeDeviceId 直调后 getHDID 状态变化 + 0x64670 "hdid: " 装配函数直调
## §11.30 真实 getOtp 调用语义全解 (R7) - 直调路线就绪
### OTP 双真实调用方 (BusinessCfg::getOtp!)
- 268ca0 (getOtp(y, string&, int&)) + **0x269324 (getOtp(AppLoginData&) = 登录路径!)**
- 269324 参数: x0=to_string(serviceTime)!, x1=2, x2=AESkeyMgr计数器(w25), x3=AppLoginData+0x40,
x4=AppLoginData+0x10, x5=全局串(0x484000+0xa0), x6=4, x7=out, [sp]=m(串)
- **可直调**: BusinessCfg::getInstance@0x281270 + getOtp(AppLoginData&)@0x26916c (T/W 符号)
- AppLoginData 字段布局: +0x40/+0x10/+0x8 (多 string), 全局材料=0x484000+0xa0
### OTP tail = f(c,d) 差分 (推翻"固定"结论) + 真实布局空输出 (参数微差待调)
### 下轮 (R8): getInstance→getOtp(AppLoginData) 直调, 金样本值注满结构 → 输出 vs ed0db8
## §11.31 getOtp 直调成功 (R8) - C2 全链路在 emulator 内跑通
### 实验定案
- BusinessCfg::getInstance@0x281270 直调 ✓ → instance=0x12491540 (C2 真初始化!)
- BusinessCfg::getOtp(AppLoginData&)@0x26916c 直调 ✓ 无崩溃 (此路可行!)
- AppLoginData 伪造: std::string 双形态 (SSO<23 内联 / heap: len<<1|1 + [8]ptr + [16]cap) ✓
- OTP 真布局 (269324): x0=to_string(serviceTime), x1=2, x2=counter(w25=0首调), x3/x4=AppLoginData+0x40/+0x10,
x5=全局串, x6=4, x7=nonce-scalar, [sp]=OUT& (nonce 不入 mid!)
- mid = f(in, cnt, s3, s4, s5); OTP 输出=[h6][00][16B mid][s5 加密块...]
### 新候选: 堆 0x127d4b90 = 275d0ff676c0d65114acdefbd2ad87a3 (32hex! 来源待查)
### R9: ① hook 269328 (post-OTP, X29 读 [x29-0x40] = OUT!) ② 0x123M-0x130M 扫窗 before/after 差分
## §11.32 Hook 突破 (R9) + getOtp 结构校验 + 275d0ff6 排除
### Hook 首次触发! (此前"不触发"=地址错)
- unicorn2 hook_add_new ✓ → getOtp 入口 0x26916c 命中:
x0=0x12491540(this) x1=0x127c2700(AppLoginData&)
- OTP 调用点 0x269324 未到 → getOtp 在 2691a4 "cmn x20,#0x8; b.hs 2695dc(函数尾)" 提前返回
= 对伪造结构判定失败 (x20-layout 未知, 需读 0x26916c-0x2691a4 前导)
### 275d0ff676c0d65114acdefbd2ad87a3 = 调用前已有 (pre/post 扫描同现) → 非 getOtp 输出, 排除
### 堆窗 pre/post 差分法就绪 (getOtp-pre/post 双扫描框架)
### R10: ① hook 2691a4 读 x20 (分支条件) + 读 0x26916c-0x2691a4 前导定 AppLoginData 布局
### ② 布局修正 → getOtp 走到 269324 → hook 抓 OTP 实参+OUT
## §11.33 C2 新钥表 + AppLoginData 真布局 + getOtp 流程走通 (R10)
### C2(getInstance) 注入的 AESkeyMgr 新钥表 (堆 0x127ddbe0, 8×24B, 与已知11钥族完全不同!):
SHBfgytjtoikooru+hogji7ER / KNSDNjfohweeromn+mkladj3g / xnkdDFIERRIPT5df+hfgiJ0FD
NDFiroqpmvd4JDIJ+hidtiwex / fNoMrJhbEMXm8nHc+HXTNovaL / novwSHidrhDg1ADU+KLkejnHR
hgtuiouilbsdjwEH+HMYU5gjt / masldDSIFGJjdfio+5hkhsSDF
→ 新钥 × enc/decode/md5/sha1/hmac 对 ed0db8 = 全零
### AppLoginData 真布局 (jadx): hyOpenId@0(8B) + userId@8(24B) + userIdState@0x20(4B) + emailMask@0x28 + ...
→ 重构后 getOtp 流程穿透: 26916c→…→2691c0 (x20=0 首串校验过) 不再早退!
### 未捕获: OTP 调用点 269324 hook 未触发 (流程在 2691c4-2692fb 未hook窗口内或 4542c0 getServiceTime 段)
### 状态: appSign 生成复刻仍未完成 - 差距仅剩"抓到 getOtp 内部真实 OTP 输出"
## §11.34 getOtp 全流程钩子追踪定界 (R10b)
- 全覆盖 hook (0x2691a4-0x269330) 40 命中: 流程走通到 **0x269278 (bl to_string@0x4543c0, to_string(serviceTime=0))** 后无后续
- OTP 调用点 0x269324 未达 → **流程死在 to_string-CALL (libc++-PLT 0x4543c0)**
- x0=serviceTime=0 (仿真时钟 0) — 需查 0x4543c0 解析/或给 getServiceTime 注入真实时间
- 剩余步骤: ① 过 to_string (hook 0x4543c0 验证) ② OTP 调用 0x269324 [sp]=OUT& ③ 输出 vs ed0db8
## §11.35 getOtp 全流程执行成功! + 配置依赖定界 (R11)
### to_string@0x4543c0 = UC_ERR_READ_UNMAPPED (libc++ 符号缺失) → NOP 补丁 + hook 预写 [x29-0x58]
### 里程碑: OTP 调用 0x269324 实弹命中, 139 指令全流程走通 (getOtp 完整返回!)
- 真实参数: x0=in串(预写) x1=2 x2=counter(1) x3/x4=BusinessCfg+0x40/+0x10 x5=len0-SSO x6=4 x7=nonce OUT@[sp]
- OUT 槽=全零 + s3/s4 全空 → 根因: BusinessCfg 实例(0x12491540) 配置字段空 (真机=服务器 112B config 填充!)
### 下步: 112B config(2AQq9oUCCZ8M...) 解析→注入实例 +0x10/+0x40 → OTP-OUT 出现 → 差分进 ed0db8
## §11.36 实例注入实验 (R11b)
- 112B config × C2钥 decode = 全垃圾 (非AES)
- BusinessCfg 诸符号: load/saveLoginData/getLoginData@0x26984c etc. (+0x10/+0x40 = 登录数据字段源)
- 注入 +0x10/+0x40 (name/sha1) 后 getOtp 流程 269280-2692a4 段 = 新 UC_ERR_READ_UNMAPPED (s4 拷贝路径)
→ 实例字段注入 = SSO 布局细节需修 (s4 拷贝读 [x8+0x10+0x10] cap 越界?)
## §11.37 🎯 OTP-OUT 内容捕获成功! (R11c)
### getOtp 内部 OTP 输出完整读取:
content=0401 7c0e461e8c52e9360e90b5af264af667 0f127c3af8c69c3428ef3d1e9b7a8618 (34B)
结构 = [04=ver][cnt=1][16B mid][16B tail]
- libc++ 长串对象: [0-7]cap [8-15]size [16-23]ptr → 内容读取法定案
- SSO-only 注入 (s3=s4="hy_300023887") 后 getOtp 全流程 985 hooks 无错误!
### mid = f(in, cnt, s3, s4, s5) (s5 也入 mid!) — 差分闭环就绪
### 下步: 全组合爆破 (in/cnt/s3/s4/s5 候选族) → mid == ed0db8