Files
live-hub-py/docs/HUYA_HDID_ALGORITHM_GEN.md
T
yml2213 49aae8b74c docs(huya): 算法生成路径分析 - 标准密码学原语定位/固定密钥候选/决定性实验/双路径(静态对拍+unidbg)
- dump 静态取证: AES S-box@0x36be82 / MD5 IV@0x36c190 / SHA1 K@0x36c1a0 / base64@0x3e8880
- .data 明文密钥候选: 7jbF5...(=AES-256 key) / HuyaUdb1928374650qwertyuiop / 865a4924...×2 / 961c151d...(SHA1)
- getGUID(0x20f8c8) 反汇编确认标准 OLLVM 平坦化 + 内部调用锚点 0x46140/0x1d6e68
- 指出'单机不可铸造'实验未覆盖 fileshydckey/全清数据/IMEI/qimei → 种子集可能可再生成
- 决定性实验 E1-E4(输入图+差分语料) + 路径A(纯Python黑盒对拍, 不需要解全部OLLVM) + 路径B(unidbg 仿真金测试)
- 服务端验收闭环: 铸币→dfpReport注册→WUP登录, 从第一天起验证
2026-08-28 12:55:20 +08:00

165 lines
11 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 自动过验能力可兜底。