- dex直读判定: NativeBridge.b/a/c/g/h/klog全为Java(classes5), NativeEntry全native; .data描述符块=GetStaticMethodID反向调Java的name/sig常量表 - GUID值流完整还原: native getGUID=纯缓存getter(pref hydeviceid_guid -> WupHelper.getGuid() -> b(100) -> 回写) YYProtoSdkModule.initHuyaUdbSdk(classes17)->HyDeviceProxy.j(appid,WupHelper.getGuid(),mid)->pnc.a WupHelper.getGuid->HalImpl.getGuid->sGuidProperty<--live-launch服务端LiveLaunchRsp.sGuid(32hex硬校验) - 修正结论: GUID非本地指纹公式, 铸币=服务端doLaunch/device_register按虚拟设备指纹签发sGuid - unidbg: 新harness回归根因=C++ locale/facet在unidbg libc++上ABI崩溃; 旧harness+当前merged恢复金测试MATCH, 实证native缓存写回流 - 新工具: tools/dex_probe_native.py(dex access_flags判定), tools/dex_scan_calls.py(跨dex调用点扫描)
302 lines
21 KiB
Markdown
302 lines
21 KiB
Markdown
# 虎牙 32hex hdid 算法生成路径分析(不用采集,纯算法铸币)
|
||
|
||
> 前置结论(见 `HUYA_HDID_RESEARCH.md`):32hex 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 生成器(最终形态,周级)
|
||
|
||
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);拉 unidbg(zhkl0228/unidbg);
|
||
2. 把 decrypt dump 载入 ARM64 仿真,hook:JNI 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...`),观察:注册是否成功、登录是否放行、滑块/风控是否升级。
|
||
- 若服务端只校验"格式/自洽/已注册",铸币路径畅通;若校验设备库真实性,则需评估
|
||
深层对抗(那是另一个量级的问题,先实测出结论再投入)。
|
||
|
||
## 七、推荐执行序列(蓝线)
|
||
|
||
| 阶段 | 动作 | 依赖 | 产出 |
|
||
|---|---|---|---|
|
||
| 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 通过 | 生产落地 |
|
||
|
||
## 八、风险清单
|
||
|
||
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 dump(r--+rw- 全段,`phone_dump_hydev_full.py`)+ 解密数据合入原文件
|
||
(`merge_decrypted.py`,unidbg 重定位自会覆盖指针槽)。
|
||
- 喂真机种子(ANDROID_ID / hydeviceid_config / NativeBridge.b 实测值):
|
||
- **`androidId` pref = `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)`,输入全部可控可计算。
|
||
- 下一步两条线(任选其一即可闭环铸币):
|
||
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/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 侧)
|
||
1. **歧义清除**:确认 NativeBridge.b/klog 到底是否 native(dex 反射 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 判定 = 纯 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` → 空则 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 侧),这些在 §六 的注册链实测中一并验证。
|