- 8活体POST存 evidence (magic 10B@70 一致, 头同族7cda11) - 25 JNI + 2 XTEA核 + 2 Java = 全0调用 (报告=隐藏native路径!) - rtk_known/ 44B known pair (native ct 45542bb3...) 后续差分用
1530 lines
99 KiB
Markdown
1530 lines
99 KiB
Markdown
<!-- ⚠️ 命名规范强制约束:本档中所有设备标识必须带前缀。规范见 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授权令牌。
|
||
旧段落中裸词按上下文语义理解:登录相关=HDID32,launch/铸币=GUID32,40hex=DEVID40。 -->
|
||
|
||
# 虎牙 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 生成器(最终形态,周级)
|
||
|
||
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。⚠️ 旧假设 —— 已于 §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_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。
|
||
- 前置条件不变:**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 确定性签发同一 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) | **签名通过**, 进风控 (非签名错误) |
|
||
|
||
### 结论: 铸币机的边界精确化
|
||
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
|
||
|
||
## §11.38 首次全组合爆破 (R11d)
|
||
- 30976 combos (in×cnt×s3×s4×s5 候选族交叉) = 8.1s, 零命中
|
||
- 结论: 真实输入不在候选族内 → 需 s3/s4/s5 真源 (loadLoginData 持久化数据 / AppLoginData 真实 x20-s5)
|
||
- 差分引擎就绪 (30K calls/8s, 可 10 倍扩容)
|
||
|
||
## §11.39 R12: s5 零串长度族 (0-40) × S3精选 × in × cnt = 23616 combos / 5s / 零命中
|
||
- 累计差分 53K+ combos 零命中; s3/s4 真源 = native BusinessCfg (Java 无类), 需 loadLoginData/配置注入路线
|
||
|
||
## §11.40 R12b: nonce 影响 mid (推翻"豁免"结论)
|
||
- direct(nonce=0)=f0012daf / nonce=1=34bd49f5 / nonce=real(0x1a049abb20b0000)=561269ff
|
||
- nonce=real 仍 ≠ getOtp 内部 7c0e461e → 剩余差异 = s5 槽 junk 字节 / OTP 内部状态
|
||
- 差分空间扩展: mid = f(in, cnt, s3, s4, s5, nonce)
|
||
|
||
## §11.41 🏆 nonce 算法反汇编破解 (R12): nonce = counter | (serviceTime << 16)
|
||
### nonce_next@0x32ff10 直读:
|
||
[0x4933b0] 存 base; 若 arg0(serviceTime) 变化→counter 清零+存新 base; 否则 counter++
|
||
return counter | (serviceTime << 16)
|
||
### 验证完美: 直调 (in="0",cnt=1,s3=s4="hy_300023887",s5="",nonce=0x1a049befebf0000) = getOtp 内部 04c7537f 逐字节一致!!
|
||
### 差分靶向化 (5184 combos: ST×cnt×s3×s4) = 零命中
|
||
- 遗留: ①金样本真实 serviceTime (13位ms) ②s3/s4 真值 (BusinessCfg 登录数据)
|
||
|
||
## §11.42 【会话交接总结】R1-R12 全景 (新会话从这读起)
|
||
### 已铁证 (勿重做)
|
||
1. encode_aes 复刻 15/15 ✓ (tools/huya_aes_replica.py)
|
||
2. getkey = 裸 24B 钥; 双钥表 (11 已知 + C2 8×24B SHBfg族) × enc/dec/hash 全穷举 vs ed0db8 = 零
|
||
3. getOtp (BusinessCfg::getOtp(AppLoginData&)@0x26916c) 直调可执行:
|
||
- BusinessCfg::getInstance@0x281270 → instance; AppLoginData 真布局 (hyOpenId@0/userId@8/userIdState@0x20/emailMask@0x28)
|
||
- 269278 to_string@0x4543c0 = UC_ERR_READ_UNMAPPED → NOP 补丁 + hook 预写 [x29-0x58]
|
||
- OTP 调用点 0x269324 实弹: [04][cnt][16B mid][16B tail]; OUT@[sp] 读取法 (libc++ 长串 [0]cap [8]size [16]ptr)
|
||
4. **nonce 算法破解**: nonce_next@0x32ff10 = counter | (serviceTime << 16) (counter 按 serviceTime 变化清零)
|
||
5. **直调逐字节复现成功**: reproOtp(in,cnt,s3,s4,s5,nonce) = getOtp 内部完全一致 (验证值 04c7537f)
|
||
### 当前边界 (新会话主攻)
|
||
- appSign(ed0db8) = 金样本 (in=to_string(st), cnt, s3/s4=BusinessCfg 登录数据, s5, nonce=cnt|st<<16) 的 OTP-mid
|
||
- 未知输入: ①金样本 serviceTime (2024-08-24 手机时钟 ms, 禁 attach) ②s3/s4 真值 ③s5
|
||
- 差分引擎: 直调 ~500 calls/s; 天窗口 86M st × s3/s4 族 = 后台爆破可行
|
||
### 关键工具
|
||
- tools/unidbg/hydev/src/hydev/AesProbe.java (全能探针: callOtpReal/reproOtp/goldBurst2/seqOtp/hooks/NOP)
|
||
- 编译运行: cd tools/unidbg/hydev && CP=$(cat /tmp/unidbg_cp.txt):...+apk-parser... ; java -cp $CP:out hydev.AesProbe so/libudbauthunify_merged.so
|
||
- 金样本: account hy_300023887 / mid 1e8bdf7d4f7a01d3 / dev40 7c5387.../ sdid *hZrPb62... (36B)/ appSign ed0db8334cadd236c00cadf7e11ab5a5
|
||
|
||
---
|
||
|
||
## §11.43 R13: OTP 真机逐字节复现 + resinfo 解密 + ed0db8 定论前夜 (2026-08-29)
|
||
|
||
### 1) 里程碑: 真机 146B OTP 逐字节复现 (GoldSweep V2 模式)
|
||
- 引擎: tools/unidbg/hydev/src/hydev/GoldSweep.java (直调 0x32fa24, ~2500 calls/s)
|
||
- 输入 (真机 final_capture 六元组): in="1471224845212", cnt=2, s3="5008", s4=K1, s5=完整114B hyCred(0a60...), nonce=0x1a037881a430000
|
||
- 输出 = 真机 out **292/292 hex 逐字节一致** (0402b4024c4a6069cb3c38...)
|
||
- **此前"零命中"根因 = mid 提取窗口 bug**: midOf(out)=out[4:36] 对 146B 输出 = cipher[2:18] (偏移2字节!!)
|
||
正确首块 = **out[2:34]** (cipher[0:16])。旧 A/P/F/C/X/W 全部扫错窗口, 作废。
|
||
- 真明文结构 (AES 密钥 312334e88d8c35cb 解密确认):
|
||
`[02][u16le 12][xxtea 12B][u16le 114][hyCred 114B][零垫]` — s5=完整 cred, xxtea 12B 精确一致
|
||
- OTP 全部语义锁定: AESkey=md5_char16(s4+getkey(cnt)) | plaintext=[02]∥cred(xxtea(nonce,key=in))∥cred(s5)
|
||
- 金样本六元组 (待命中, 已正确化): **in = 账号稳定 uid = "1199666914671"** (hy_300023887; req-json 的 1471238907296 为会话号, 非 xxtea 密钥), cnt 1..15, s3="5008", s4=K1, s5=hyCred(0a80ee...), nonce=st<<16, st=金样本时窗 — GOLD 模式实测零命中
|
||
|
||
### 2) 差分覆盖统计 (全部 out[2:34] 正确窗口, 无命中)
|
||
| 模式 | 范围 | 次数 |
|
||
|---|---|---|
|
||
| H | 登录窗 st[5165..5172.7]×cnt1..15×nc0..2×in{uid,"0"}×s5{cred,""} | 1.39M (98%处崩) |
|
||
| H2 | st 单点{090906,102531,167182,172449,172452,172455,172458}×in4×cnt0..15×nc0..2×s5 2 | 3072 |
|
||
| I(完成) | 装机窗 st[1787582670000..1787582810000]×cnt1..15×nc0..2×in"0"×s5"" (8 并行 JVM) | 6.3M, **零命中** |
|
||
|
||
### 3) resinfo 文件层解密成功 (工具: python Crypto AES-ECB)
|
||
- 文件: /data/user/0/com.duowan.kiwi/files/hydevice/resinfo (528B)
|
||
- **密钥 = `HuyaUdb1928374650qwertyuiop` 前16B, 标准 AES-ECB 零垫**
|
||
- 明文 JSON:
|
||
```json
|
||
{"channelKey":"865a4924a40897ac1fcfe6b4c2cbb045","channelKeyVersion":"10",
|
||
"huyaDeviceId":"7c5387e0539c023c31c4ff0e807e7256117385ee",
|
||
"phoneInfo":"{\"resultCode\":\"103000\",\"desc\":\"true\",\"securityphone\":\"195****6018\",\"operatorType\":1}",
|
||
"safedeviceid":"PQwemAN9...","time":1787949486}
|
||
```
|
||
- 结论: **resinfo 含 channelKey/DEVID40/safedeviceid, 无 32hex hdid** — ed0db8 不在任何持久化文件
|
||
- channelKey(865a49...cbb045) = dfpReport t1 设备指纹; k1/sessHex32(865a49...cbb0e3) 为 BusinessCfg+0x10, 两者差末4 hex
|
||
|
||
### 4) 真机 getter 全量定案 (evidence/diag_phone/hdid_read.json 实测)
|
||
- getGUID=0a7dfaa882938a6ab502511452142c57(32hex) | getMID=1e8bdf7d4f7a01d3(16hex)
|
||
- **getHDID=7c5387e0539c023c31c4ff0e807e7256117385ee(40hex!!)** — 非 32hex 登录 hdid
|
||
- getCDID=02df3987...(40hex) | getSDID=PQwem...(180B b64)
|
||
- 32hex hdid (ed0db8) 不存在于任何 getter / resinfo / files / prefs (唯一出现点=登录 WUP t1.t0 本身)
|
||
|
||
### 5) harness 修复记录 (本轮)
|
||
- HyDeviceId.java: b(100) 误喂 MID → 改喂真 GUID=0a7dfaa8... (init 现 MATCH GUID/CDID/SDID/MID)
|
||
- 后端 Dynarmic → Unicorn2Factory 修复 init 崩溃 (dynarmic 在 datadiv 区误仿)
|
||
- 补桩: getApplicationInfo/getPackageName/targetSdkVersion/b(6)=账号/b(2001)=渠道/applist JSON 等 → init 完整跑通
|
||
- **但 init 不产 32hex**: 32hex hdid 计算不在 NativeEntry.init() 路径 (getHDID 缓存槽无人填充)
|
||
|
||
### 6) Frida 真机捕获实验 (结论)
|
||
- 真机唯一稳定注入通道 = spawn 挂起 + 只加载 `bypass_msaoaid_maps_art_callsite.js` (STATUS.md)
|
||
- **登录可带 frida (两阶段配方)** — 2026-08-29 实测 2 次真实密码登录成功, app 存活 170s+/多轮, OTP 全链捕获; "不可登录"旧结论作废 (msaoaidsec 标记上报风险仍存在, 仅限低风险实验)
|
||
- 捕获成功部分: 6 个导出钩子全部定位 (hyudb_otp_encrypt/0x32fa24, getOtp/0x26916c, setSafeDeviceId/0x26a2e0, getHdid/0x26a484, getkey/0x26a71c, md5_char16/0x32fb7c) — 偏移与 merged so 完全一致 => **lib 版本无漂移, 差分引擎可信**
|
||
- 工具: tools/frida/run_capture.py + hook_otp_capture.js
|
||
|
||
### 7) 真机持久化文件存档 (evidence/live_device/)
|
||
- resinfo.bin(528B) / hydckey.b64(148B, = /dckey/check 下发, 前缀 AAAAAMC1eP4iV43WYoI57ZOu0) / uuid.b64(172B) / guid.xml(GUID=0a7dfaa8...)
|
||
|
||
### 8) 剩余开放问题 (按可能性)
|
||
1. **st 在装机窗外/更早**: I 模式进行中; 若落空 → hdid 非"首启计算"或非 OTP 系
|
||
2. **ed0db8 非 getOtp-mid**: 1.55M 差分 + resinfo + getters 全面证伪 → 真源 = libhydeviceid 内 "hdid:" 装配函数(0x3ba484 区) 或 setDeviceInfo 上报前的独立计算
|
||
3. 服务端校验: hdid = 注册锚 (dfpReport 加密体=设备身份), 纯代码铸造需先破 dfpReport 加密体 (未破解#2)
|
||
|
||
### 关键工具 (本轮新增)
|
||
- GoldSweep.java: V2(真机复现)/H/H2(登录窗)/I(装机窗并行)
|
||
- tools/frida/run_capture.py + hook_otp_capture.js
|
||
- python: HuyaUdb1928374650qwertyuiop[:16] AES-ECB 解 resinfo (evidence/live_device/resinfo.bin)
|
||
|
||
### 9) R13 终局判定 (2026-08-29 05:04)
|
||
- 全部差分合计: H 1.39M + H2 3072 + I 6.3M = **~7.7M 次 OTP 调用 + AES 密钥直解 + resinfo + getters = 六路证伪**
|
||
- **定论: ed0db8 (WUP t1.t0 appSign) 不是 libudbauthunify getOtp 的输出** (prob > 99%)
|
||
- 剩余真源候选: ① libhydeviceid "hdid:" 装配函数 (0x3ba484 格式串, 0x64670 区, OLLVM 混淆)
|
||
② setDeviceInfo(msgType 0xb000021) 上报前的独立 32hex 计算
|
||
③ dfpReport 加密体内的设备身份派生 (未破解#2)
|
||
- 后续路线: (a) libhydeviceid 0x64670 区静态攻坚 (datadiv 已解, OLLVM 状态机)
|
||
(b) frida 真机 (stable bypass) 抓 setDeviceInfo 入参 → 32hex 直读
|
||
(c) 接受"hdid=设备级证书不可纯代码铸造"结论, 维持金样本 hdid 共用方案 (已跑通多账号)
|
||
|
||
### 10) R13b: frida 稳定性优化 (2026-08-29)
|
||
- 配方定案: **spawn 挂起 + 只注入 bypass_msaoaid_maps_art_callsite.js + resume**, 后延迟+6s 注入业务钩子
|
||
(同时注入 frida_bypass.js/javaexit = 模拟器向配方, 会与 msaoaid 补丁冲突 → 真机启动卡死, 勿用)
|
||
- 自愈运行器: tools/frida/run_capture.py (多轮自动重 spawn, 事件汇总单 JSONL, 进程死亡检测)
|
||
- 实测: 两阶段配方下 app 存活 >120s, crypto-otp/getHdid 全触发 (vs 旧三脚本配方 <60s 被杀)
|
||
- 已知残余: msaoaid solist 快照面 (G2-0019) 未掩; 登录流程仍不推荐带 frida (标记上报)
|
||
|
||
|
||
---
|
||
|
||
## §11.44 R14: 登录路径 OTP 全链 LIVE 验证 + appSign 结构性证伪 (2026-08-29)
|
||
|
||
### 1) 决定性捕获 (tools/frida + hook_huya_crypto.js 全链 hook, 用户真实密码登录 ×2)
|
||
捕获证据: evidence/frida/capture_20260829_052648.jsonl
|
||
|
||
| 链节 | LIVE 值 | 结论 |
|
||
|---|---|---|
|
||
| hyudb_otp_encrypt arg0 | frida 入口读 "" (误读!!) | 入口寄存器读不可靠 |
|
||
| **xxtea 内部密钥** (crypt_util xxtea hook) | **"1471259186024" / "1199666914671" = 账号稳定 UID** | xxtea key = uid ✓ |
|
||
| AESkeyMgr::getkey(1,2) | MKDKeridjing7avnsasdSDHI | getkey 表实锤 ✓ |
|
||
| getkey(1,3) | nskdI7MDGKSDJsnadjdoonvs | 表序实锤 ✓ |
|
||
| md5_char16 输入 | `865a4924a40897ac1fcfe6b4c2cbb0e3` + `MKDKeridjing7avnsasdSDHI` | AESkey = md5_char16(k1+getkey) ✓ |
|
||
| UdbAESUtil::encrypt 明文 | `02 0c00 [12B xxtea] 7200 [114B cred]` | 与引擎模型逐字节一致 ✓ |
|
||
| AES 密文 | b0a0af88aaf53a411b0eb1d283c88ff8... | OTP out[2:34] = 密文首块 ✓ |
|
||
| nonce | nc=0, st=调用时刻 | nonce=counter?st<<16 ✓ |
|
||
|
||
**结论: getOtp 全链语义 100% 定案** — in=账号uid, s3=非空门(值不进计算), s4=k1, s5=cred, AESkey=md5_char16(k1+getkey(1,cnt)), 明文结构如 V2。
|
||
|
||
### 2) 差分终态 (全部零命中)
|
||
| 模式 | 语义 | 次数 |
|
||
|---|---|---|
|
||
| GOLD | in="1199666914671"(正确uid) s3="5008" s5=金cred st登录窗+单点 | 346,545 |
|
||
| J2 | 全笛卡尔 in{uids,"0",""} × s3{5008,"",k1} × s5{cred,""} 登录窗 | 6,233,760 |
|
||
| I | 装机窗 in"0" s5"" | 6,300,000 |
|
||
| H/H2 | 旧uid窗+单点 (含 K1C 补齐) | 1.39M+3072 |
|
||
| **合计** | | **~14.3M** |
|
||
|
||
### 3) ★ 结构性证伪: t1.t0 appSign ≠ getOtp 输出 (QED)
|
||
- ed0db8 **跨账号稳定** (device_profiles.json: 多账号共用同一登录 hdid) — 设备级值
|
||
- getOtp 六元组含 **s5=账号级 cred** (每次登录轮换 → 每次 OTP mid 不同 — 08-25 biztoken mid vs 今日两次 mid 均不同)
|
||
- **账号级输入不可能产出设备级稳定值** → t1.t0 不可能是 getOtp 的 mid
|
||
- 推论: 770万+ 差分验证的是一必败假设; OTP 引擎已完全解码 (V2+live 双证), 但 **32hex appSign 由 libhydeviceid 设备级装配产出** (0x3ba484 "hdid:" 格式串 / 0x64670 区 / setSafeDeviceId 上报链)
|
||
|
||
### 4) 旧文档更正 (R13 → R14)
|
||
- ~~登录路径六元组 in=""/s3=""~~ → **in=账号uid, s3 非空门** (frida 入口读 "" 为误读, xxtea/内部 hook 为真值)
|
||
- ~~登录不可带 frida~~ → **两阶段配方可带登录** (2026-08-29 实测)
|
||
- ~~金样本 in="1471238907296"~~ → **in="1199666914671"** (稳定 uid; req-json 1471238907296=会话号)
|
||
- ~~I 模式"运行中"~~ → 完成零命中
|
||
|
||
### 5) 下一步 (appSign 真源)
|
||
- 反汇编登录 WUP 构建器 createWupRequestData<AppCommonData>@0x38dab0 → ProtoInfo.appSign 填充点 → 找 32hex getter
|
||
- 或 frida 全 hook BusinessCfg 设备 getters + setDeviceInfo 链直接读 appSign 来源
|
||
|
||
---
|
||
|
||
## ★ §11.45 R15: 🏆 appSign(t1.t0) 算法完全破解 — ed0db8 逐字节复现 (2026-08-29)
|
||
|
||
### 公式 (实机全链捕获 + 复现验证)
|
||
```
|
||
t1.t0 appSign (32hex, "hdid") = MD5( appId + "_" + appVersion + "_" + k1 )
|
||
金样本: MD5("5008_13.4.22_865a4924a40897ac1fcfe6b4c2cbb0e3")
|
||
= ed0db8334cadd236c00cadf7e11ab5a5 ✅ 逐字节命中
|
||
```
|
||
- appId = "5008" (byte-appId), appVersion = 版本名 ("13.4.22"), k1 = BusinessCfg+0x10 设备钥 (865a49...cbb0e3)
|
||
- 与网页 appSign 公式同族: web=md5(appId+vCode+k1)[:8] (60576904) — native 用版本名+完整32hex
|
||
- HuyaMd5 类 = 标准 MD5 封装 (实机 toString 输出 = ed0db8 直接命中)
|
||
|
||
### 发现链路 (本轮)
|
||
1. createWupProtoInfo(ProtoInfo&)@0x273f1c 反汇编 → appSign = HuyaMd5(拼接串) — 3 个全局单例串 @base+0x484000+0xe0 (+0x10/+0x28/+0x40)
|
||
2. frida 读全局串: "5008" / "13.4.22" / k1 (SSO 旗标字节混读修正后)
|
||
3. HuyaMd5::toString() 实机输出 = **ed0db8334cadd236c00cadf7e11ab5a5** (WUP 公共帧构建时触发, 无需登录)
|
||
4. python: md5("5008_13.4.22_865a4924a40897ac1fcfe6b4c2cbb0e3") = ed0db8 ✅
|
||
|
||
### 结构性解释 (与 R14 证伪一致)
|
||
- appSign 只依赖 {appId, appVersion, k1} — 无账号输入 → **跨账号稳定** ✓ (device_profiles 多账号共用)
|
||
- k1 = 设备级键 (dfp t1/ channelKey 同族: cbb045/cbb0e3) → 版本更新则 appSign 变化 → 服务端按版本校验
|
||
- 与 web appSign 公式同族: web=md5(appId+vCode+k1)[:8] (60576904) — native 用版本名+完整32hex
|
||
- OTP 链 (getOtp) = 账号级 (s5=cred) — 与 appSign 职责分离 ✓
|
||
|
||
### 工程意义
|
||
- 制造任意 (appVersion, k1) 组合的合法 appSign — 登录帧 t1.t0 可自铸
|
||
- 现有多账号方案 (金样本 hdid/shareConfig) 可升级为"按设备钥动态计算"
|
||
- 设备身份 (dfpReport 加密体) 仍是仅剩的铸造缺口 (#2)
|
||
|
||
### 工具/证据
|
||
- tools/frida/hook_otp_capture.js: createWupProtoInfo@0x273f1c + HuyaMd5C1/toString 全链 hook
|
||
- capture_20260829_054317.jsonl: ctor inHex=353030385f31332e342e32325f383635... (5008_13.4.22_k1) + tostr=ed0db8...
|
||
- 复核: md5("5008_13.4.22_865a4924a40897ac1fcfe6b4c2cbb0e3") = ed0db8334cadd236c00cadf7e11ab5a5 ✅
|
||
|
||
---
|
||
|
||
## §11.46 R16: dfpReport 加密体侦察 (未破解#2 现状) (2026-08-29)
|
||
|
||
### 结构解码 (evidence/dfp_chain_golden.json 全链 WUP)
|
||
```
|
||
getDfpConfig: tReq{version="5008"} -> tRsp = 81B 密文配置 (lNLlGtxbxSB2FhJg...=, 非JSON)
|
||
selectOperator: _wup_data{version"1.0", hyudb_<ts10>, "5008", "LV", SDID(PQwem...)}
|
||
dfpReport: tReq = [10B 魔数 57 18 82 cf 66 4b b3 94 01 ee][3988B 加密采集体]
|
||
tRsp = code"10" + t1(865a4924a40897ac1fcfe6b4c2cbb045=channelKey!) + SDID
|
||
```
|
||
|
||
### 侦察结论 (静态全量)
|
||
- **魔数/协议串不在任何静态库**: APK 147 lib 全扫 + 设备 lib + 本地 merged = 零 (协议名/魔数均运行时装配或混淆)
|
||
- dfp = **腾讯 turing 反欺诈体系** (app_turingdfp/app_turingfd 目录, libturingga.so, 108298=appid)
|
||
- 关键材料已拉取: app_turingfd/mpdc_108298_1 = 32hex 设备号(0399ED0BEADBA7124D445AA7D7843BF9!)
|
||
app_turingfd/12/108298_ga_2 (1860B), app_turingdfp/1/.turing.dat (76B)
|
||
- 链路: getDfpConfig(密文配置) -> selectOperator(交SDID) -> dfpReport(3988B密文体) -> 服务端回 t1/SDID
|
||
- Java 侧无 dfp 类 (classes12 仅 URL 黑名单配置); 协议全 native; turing = 工业级混淆 SDK
|
||
|
||
### 影响评估
|
||
- 唯一剩余缺口 = "纯代码铸造全新设备身份" (需破 turing 加密体, 多日专项)
|
||
- 现有多账号方案 (金样本设备身份) 不受影响; 缺口#1 不影响 hdid/appSign 本身
|
||
|
||
### 工具/资产
|
||
- tools/frida/hook_dfp_java.js (Java 全量 hook — 太重, 会闪退, 弃用)
|
||
- /tmp/turing/: mpdc1/ga2/tur 数据存档
|
||
- 建议: turing-dfp 立为独立子项目 (证据链齐全); 或轻量运行时 trace 追 tReq 装配点
|
||
|
||
---
|
||
|
||
## §11.47 R17: dfpReport 密文体实时捕获成功 (2026-08-29) — turing 加密体现场!
|
||
|
||
### 捕获 (SSL_write 明文扫描, hook_ssl_magic.js)
|
||
- 端点: **POST wsapi.huya.com** (okhttp) — 非 udbdf 域名!
|
||
- 3798B 请求: hyudbwebuif/dfpReport WUP -> tReq{tag6="android-", tag1=[0dcb]3531B = [10B魔数 571882cf664bb39401ee] + 密文}
|
||
- 证据: evidence/dfp_live/dfpReport_post_full.bin (完整 3798B HTTP POST)
|
||
- 组件: libturingmfa.so(v87, tss;rfr;ite) + libturingga.so(v2.92.2) 动态插件 (app_dynamic_so_64/)
|
||
- 密钥材料: mpdc_108298_1=32hex(0399ED0B...), getDfpConfig tRsp=81B密文配置, ga2(1860B), .turing.dat(64B)
|
||
|
||
### 触发方法 (重注册)
|
||
- 备份 tar (604MB) -> 清 app_turingdfp/app_turingfd/resinfo -> 启动 -> 自动重注册 -> SSL_write 命中 -> 状态已再生 (ga2/resinfo 恢复)
|
||
- 无副作用 (备份在 /data/local/tmp/dfp_backup.tar)
|
||
|
||
### 下一步 (turing-dfp 独立子项目)
|
||
- 解析 WUP 完整结构 + 抓 SSL_read 响应
|
||
- 破 getDfpConfig 81B 配置 (含加密钥?) -> 破 dfpReport 密文
|
||
- 或运行时 trace 密文装配点 (SSL_magic 命中时刻的调用者)
|
||
|
||
---
|
||
|
||
## §11.48 R18: turing 密文流式模式判定 + 全双工捕获 (2026-08-29)
|
||
|
||
### 三样本差分 (同设备 08-24/06-14/06-25)
|
||
- 密文长 3531~3931B; **全三样本仅 5 稳定字节**: [7c da][XX][69 c9 f5] (位置0-2 + 3-5)
|
||
- 其余全运行时变 -> **非确定性ECB, 是带 nonce/头部的流式(CTR)加密** — 5B稳定段=头部/标志
|
||
- 密码结构: [10B魔数 571882cf...][5B头][nonce/数据...][密文流][尾部 14B]
|
||
- 尾部特征: 400c0b8c980ca80c (两次 fresh 均有, 疑包尾/校验)
|
||
|
||
### 全双工捕获 (SSL_write+SSL_read, hook_ssl_magic.js)
|
||
- 请求 4198B (POST wsapi.huya.com, okhttp) + 响应帧 108B (826a...hyudbwebuif resp)
|
||
- 证据: evidence/dfp_live/dfpReport_post_full_0625.bin
|
||
- SSL_write 回溯 = ART/Java 直呼 (JAVA 侧拼 body, 非 turing 直接写 TLS)
|
||
- 采样集: golden(3908B) + f1(3531B) + f2(3931B) 三份密文齐备
|
||
|
||
### turing-cipher 专项起点 (会话内到此为止)
|
||
1. 密文=流式+nonce头; 明文=采集数据(可能gzip先压 — SSL_read 见 1f8b0800 gzip 流)
|
||
2. 密钥材料候选: mpdc_32hex / getDfpConfig 81B配置 / ga2 / turing.dat
|
||
3. 下一步: (a) frida 追 turing 内部 encrypt 调用 (JNI 层), (b) 静态 OLLVM 破 ga/mfa,
|
||
(c) 若只求实用: 重放已捕获密文即可 (同设备身份)
|
||
|
||
---
|
||
|
||
## §11.49 R19: turing 运行时路线终态 (2026-08-29) — 专项交接
|
||
|
||
### 本轮排查结论
|
||
- turing 类定位: com.tencent.turingfd.sdk.ams.ga.{Lemon,Sultana,Solar,Blueberry,Pineapple,...} (果汁混淆系)
|
||
- Java 侧载荷 hook = 0 触发: turing 采集/加密全 native, Java 仅薄壳 (payload 不经过 Java 方法) -> Java-hook 路线关闭
|
||
- 密文体流式模式实锤 (nonce 头 7cda..69c9f5, 仅5稳定字节)
|
||
- 静态密钥假设 (XOR-mpdc/内生钥) 全否定: 81B 配置/64B turing.dat = 真加密非简单 XOR
|
||
|
||
### 破解 dfp 加密体的可行路线 (专项, 估时)
|
||
1. native 静态: libturingga/mfa (296KB, OLLVM, -fvisibility=hidden) — 多日
|
||
2. native 动态: turing 加密函数的内存级 buff 追踪 (SSL 命中时刻向前追) — 日级
|
||
3. 密钥链: 破 getDfpConfig 81B 配置 (服务端下发镜钥) — 依赖 1/2
|
||
4. 实用主义: **重放已捕获密文 = 同设备身份注册成功** (现状方案不受影响)
|
||
|
||
### 交接资产 (evidence/dfp_live/ + docs)
|
||
- 密文采样 ×3: golden(3908B) + f1(3531B) + f2(3931B)
|
||
- 请求全文: dfpReport_post_full*.bin (3798B/4198B)
|
||
- 组件: libturingga.so / libturingmfa.so / 32hex mpdc / 81B config / ga2 / turing.dat
|
||
- 工具: hook_ssl_magic.js (全双工) / hook_turing_jni.js / run_capture.py 自愈
|
||
- 触发方法: 清 turing 状态重注册 (备份 tar 可回滚)
|
||
|
||
---
|
||
|
||
## §11.50 R20: turing 运行中 dump 工程化 (2026-08-29)
|
||
|
||
### 捕获工具定案 (hook_str_magic.js — 每轮可靠命中!)
|
||
- **字符串过滤 > 魔数过滤**: SSL_write 明文含 "dfpReport/hyudbwebuif" 串才 dump (魔数过滤会漏轮次, 串过滤 100%)
|
||
- 每轮: POST wsapi.huya.com + hyudbwebuif/dfpReport WUP (libjavacrypto okhttp) — 全量请求快照
|
||
- 触发: 清 app_turingdfp/fd 重注册 (重注册稳定); pm-clear 全清+登录 = 新身份再注册 (也稳定)
|
||
|
||
### 采样集 (7+, 同设备多身份)
|
||
| 样本 | 密文长 | 头 12B | 身份 |
|
||
|---|---|---|---|
|
||
| golden 3908 | 3898 | 7cda0169c9f5 489d4c4695ef | 08-24 |
|
||
| f1 3531 | 3521 | 7cda11 69c9f5 289c4d46956f | 08-29 A |
|
||
| f2 3931 | 3921 | 7cda11 69c9f5 289c4d46a1de | A |
|
||
| f4 4310 | 4033 | 7cda11 75c8f5 349c4d0390de | A |
|
||
| 0647 4258 | 3991 | 7cda11 69c9f5 289c4d46a1de | **新身份 B** |
|
||
- 新身份 B 密文头 = 旧身份 A 的 f2 完全一致 12B → **nonce/加密流与身份无关(设备公共)**; 计数位 28/34 递增
|
||
- nonce 块结构: [7cda][版本位 01/11/13][69c9f5][计数位][9c4d46...稳定]
|
||
|
||
### 运行时路线终态 (明文未获)
|
||
- Java 桥 0 调用 (加密全 native 内部) / co-located 扫描 0 (工作缓冲已回收) / SSL 回溯全 Java 帧 (加密提前完成)
|
||
- 明文/密钥获取 = turing native 静态破 (OLLVM) 或 harness 装载同库直调内部偏移
|
||
|
||
### 工程交付
|
||
- 触发→捕获→存档 全自动化 (清状态+spawn+str_magic+杀) — 每次 ~40s
|
||
- evidence/dfp_live/: 请求全文 ×5 + 采样集 + 组件 + fresh 状态文件
|
||
|
||
---
|
||
|
||
## §11.51 R21: turing native 静态突破 — JNI 表解出 + harness 装载 (2026-08-29)
|
||
|
||
### 💡 重大进展: turing mfa JNI 原生方法表解出 (静态!)
|
||
- 从 libturingmfa.so 的 JNI_OnLoad (导出@0x25414) 反汇编 + JNI 方法名串交叉定位
|
||
- **JNI 表 (data.rel.ro @0x45040)**:
|
||
- `b87_D2D99D56FECC73FD (Landroid/util/SparseArray;[BLjava/util/Map;I)Landroid/util/SparseArray;` **impl@0x25c98**
|
||
- `f87_D2D99D56FECC73FD (SparseArray;[BI)` (impl 邻近) — v87 是加密核心入口族!
|
||
- 方法名带版本哈希: D2D99D56FECC73FD = mfa v87 (ga 侧 2A742FA224BA8A2A 同类, 在 turingga.so)
|
||
|
||
### ELF 布局洞察 (harness 装载崩因实锤)
|
||
- turing 库程序头正常 (LOAD-1 rx 0x0-0x43988, LOAD-2 rw @vaddr0x45648) — 但 **LOAD-2 vaddr 未4K对齐** (0x648)
|
||
- 真机按页映射覆盖 0x45000-0x45648 间隙; **unidbg 按精确边界 → 间隙未映射 → JNI_OnLoad 的 GOT 读 (0x45000+#dd0) 必崩**
|
||
- harness 补映射 (mmap2 gap) 后过导出调用, 再崩于 turing 共享指针原子操作 (x0 未映射 = 对象模型深度问题)
|
||
|
||
### harness 状态 (TuringProbe.java)
|
||
- ✅ unidbg 装载 turing 双库 + 预注册 11 个 turing 类 (FindClass OK)
|
||
- ✅ gap 补映射 (mmap2@0x45000)
|
||
- ⏳ JNI_OnLoad 内 x0 原子崩溃 (turing 自研共享指针 vs harness 对象)
|
||
- ⏳ b87_@0x25c98 直调: DvmObject→jobject 封送待办 (addLocalObject int 句柄机制)
|
||
|
||
### 下一步路线 (已提前到位)
|
||
1. **直调 b87_@0x25c98** (JNI 封送: vaList/int-handle) — 喂真实捕获 blob → 输出 SparseArray = 证据事件 = 明文!
|
||
2. JNI_OnLoad x0: 追踪 onLoad 内部分配链 (0x12b6c 原子函数的调用者)
|
||
3. ga 侧 JNI 表 (turingga.so 同法解剖) → a209202_ 族入口
|
||
|
||
### R21-补充: b87_ harness 直调结果 (2026-08-29)
|
||
- addLocalObject 句柄封送 (int 句柄作 jobject) — **调用执行成功** (不再 Unsupported arg)
|
||
- 三种输入 (全POST/cipher-only/magic+cipher) × int{0,1} = 全部确定性 **ret=-1**
|
||
- 判定: jobject 封送通; -1 = turing 全局初始化态缺失 (config/dat 未载入) 或 SparseArray 内容格式不符
|
||
- 下一步: (a) onLoad 初始化链修复后直调 (x0 原子问题先解), (b) 或喂 SparseArray 事件内容探格式
|
||
|
||
---
|
||
|
||
## §11.52 R22: harness onLoad 迭代修复进展 (2026-08-29)
|
||
|
||
### 崩因链逐级突破 (每补一个槽位推进一段)
|
||
1. **0x48a90 全局槽 NULL → 引用计数崩溃** (0x12b6c ldxr x0):
|
||
- onLoad 的 init 函数 0x11690 先读该槽 (adrp 0x48000 + ldr #0xa90) 再 refcount
|
||
- 真机上该槽在 init 链前序已赋值; harness 中静默为空
|
||
- **补丁: 塞 8B dummy 对象指针 → 过此关!**
|
||
2. 新崩溃: BR-x8 空跳 (X8=0x0, PC=0x0), X0=X1=0x1203ced5 (rodata 字符串)
|
||
- 变体函数指针表空槽 / C++ 虚表未建 / PLT 未解析
|
||
3. 根因深化: turing 类定义 (字段/方法) 缺失 — 预注册的空类无法支撑 JNI 静态字段链
|
||
- unidbg VM 无 dex 加载 (仅 setDvmClassFactory/ProxyClassFactory 宿主代理)
|
||
|
||
### 状态判定
|
||
- onLoad 全仿真 = 类定义工程 (ProxyClassFactory) 或连续空槽 wack-a-mole — 小时级
|
||
- 替代: 跳过 onLoad, 直接按 JNI 表构造 b87_@0x25c98 的 C 结构对象模型直调
|
||
- 进展固化: gap补映射 + 预注册类 + 全局槽补丁 = 已进 TuringProbe.java 可复现
|
||
|
||
---
|
||
|
||
## §11.53 R23: ga 侧 JNI 解剖状态 + 双线进展固化 (2026-08-29)
|
||
|
||
### ga (turingga.so) JNI 面
|
||
- 方法名族: a209202_2A742FA224BA8A2A .. n209202_ (14个, 哈希=ga v2.92.2)
|
||
- JNI 字符串在 .rodata (vaddr 0x4bd7/0x4c4e/0x5164...); **注册表 = 非文件字面三元组**
|
||
(281 个 RELATIVE 重定位扫描 = 0 命中 -> 表为栈构建/运行时填充, 需 JNI_OnLoad 反汇编)
|
||
- ga JNI_OnLoad 符号存在 (symtab 隐藏), harness 调用崩溃: PC=0x31ec8, X0=1 (运行中-非空指针)
|
||
|
||
### 双线状态 (顺序第1步受阻, 已固化)
|
||
- 线1 (mfa onLoad): 引用计数关已过 (0x48a90 槽补丁); 后续空槽 wack-a-mole ~小时级
|
||
- 线2 (ga 表): 需 JNI_OnLoad 栈构建表反汇编 — 与线1 同深度
|
||
- 两线公用的更优路径: **运行中 RegisterNatives 捕获** (frida vite 已多路试, ART 内部符号未暴露 —
|
||
备选: ART JNIEnv vtable 正确索引再试 / halt_handler)
|
||
|
||
### 建议下一步 (下会话入口)
|
||
- A: mfa onLoad 空槽批量补丁 (0x48000-0x48a90 全槽扫描, 非零即赋 dummy) 冲过初始化
|
||
- B: ga JNI_OnLoad 反汇编解栈构建注册表 (RegisterNatives 调用参数恢复)
|
||
- C: 重置方向 — 解析已捕获 7 样本的 nonce/计数位规律 (零依赖纯分析)
|
||
|
||
---
|
||
|
||
## §11.54 R24: C线纯分析终局 — 密钥流每次独立 + 头部字段解码 (2026-08-29)
|
||
|
||
### 7 样本对分析结论
|
||
1. **同 nonce 组 (f2/0647 头13B相同) 异或 = 98.6% 随机差** (共享散点41个≈期望15, 无ASCII结构)
|
||
-> **密钥流每次运行独立 (防重放), 同头≠同密钥; 头部非密钥**
|
||
2. LCP 分组: GOLDEN=2B(版本位01) / f1-f2-0647=10B / f2-0647=13B — 分歧从 byte10
|
||
3. byte2 版本位: 01(golden原始注册) / 11(今日重注册) / 13 — 疑重注册计数/模式
|
||
4. **byte6-7 = 运行秒数低16位** (39944/39976/39988/40392 ≈ 40s-uptime!) — 报告时刻微秒窗
|
||
5. 库内无格式常量/无gzip/zlib魔数 (bb80@turingga-0x11599=巧合)
|
||
|
||
### 判定
|
||
- 密文 = 每次独立的密钥流 (真防重放); 头16B = 加密元数据块(版本+计时+填充)
|
||
- **纯样本分析无法破** (无密钥派生链); 密钥源 = getDfpConfig 81B 服务端下发镜钥 + 设备材料
|
||
- 破解必须回到加密内核: A (harness onLoad 空槽冲关) / B (ga JNI_OnLoad 注册表反汇编)
|
||
- 已获价值: 报告时刻可判定 (头 byte6-7 ≈ uptime-ms), 版本/重注册模式可区分
|
||
|
||
---
|
||
|
||
## §11.55 R25: 💥 加密器识别 — turing mfa XXTEA 变体 + harness 直调运行 (2026-08-29)
|
||
|
||
### 关键突破 (IDA 导出 + unidbg 双线合流)
|
||
1. **JNI 表全解 (11 方法)**: a87_@0x25680 b87_@0x25c98 c87_@0x262a8 d87_@0x26418 e87_@0x26768
|
||
f87_@0x26edc g87_@0x271d0 h87_@0x274c0 i87_@0x276d0 j87_@0x27a78 k87_@0x27b74
|
||
(+ onServiceConnected@0x37324) — 类 TNative$aa (com/tencent/turingface/sdk/mfa)
|
||
2. **b87_ 语义**: JNI 分发壳 (a6==1 → sub_2E4A4!) — **sub_2E4A4 = 解包链**:
|
||
sub_2E250 { sub_252EC [拷贝 + **sub_24D58 = XXTEA变体解密** + 尾4B长度校验] + sub_34A00 [inflate!] }
|
||
-> "resp" 存 SparseArray — **输入 = [XOR/XXTEA密文][4B len] → inflate → 明文!!**
|
||
3. **sub_24D58 反编译 = XXTEA 变体**: 负长度参数 + 0x9E3779B9(2B16!) + 掩码混淆运算
|
||
(constant: 6CC17688/933E8977/D3115A55/5613AC09/A9EC53F6/B13795CC/4EC86A33/73067D6A/8CF98295)
|
||
key索引 = (sum>>2&3 变体) — 自定义变体非标准XXTEA
|
||
4. **harness 直调 sub_24D58 成功**: (v-ptr, -n, key@3DF88) — n=977/976/978 全量运行无崩!
|
||
输出非zlib — key槽或输入格式待定
|
||
|
||
### harness 进展
|
||
- 导入补丁 115/121 解析 (ASensor 族缺 libandroid)
|
||
- 0x48A90/0x48A98 构建器全局按语义手工构建 (rc@0 len@8 data@16!)
|
||
- 日志链桩 (11890/11928/3e80/3f70 = mov-w0-#0;ret)
|
||
- Unicorn2 = ldxr 原子缺陷; Dynarmic = 过 init 到注册段 (JNI-Env 回调 abort); 三后端均可跑纯函数
|
||
|
||
### 下一步 (解密侧续攻)
|
||
- key 槽验证: 3DF88 结构内偏移 / 或 key 由 config 派生
|
||
- b87_ 全链 harness 直调 (喂真实捕获密文 → inflate → 明文!)
|
||
- a87_@0x25680 = 加密侧 (sub_27DA4 链) — "纯代码铸造" 的核心
|
||
|
||
## §11.56 R25.5: 🎯 XXTEA 变体加密侧回环验证成功 — 3DF88 密钥确认 (2026-08-29)
|
||
|
||
### harness 直调 sub_24D58 回环实验
|
||
- `sub_24D58(buf, +n, key@3DF88)` = **加密** (a2 正参数分支)
|
||
- `sub_24D58(buf, -n, key@3DF88)` = **解密** (负参数分支)
|
||
- 44B 已知明文 → ENC → DEC → **100% 还原** (roundtrip match=True!)
|
||
- **key@0x3DF88 = 真密钥**: d2785ef1-c4c23330-25a9c8ba-3a867e67 (u32 LE)
|
||
- 三层后端对比: Unicorn2 直跑 XXTEA 无原子指令 ✓ (dynarmic 在 2E250 链 JIT 崩)
|
||
|
||
### ga (turingga.so) JNI 表全解 — harness 注册后读表 @0x4d1a0
|
||
14 方法: a209202(SparseArray,Context,Map,Map,I)@0x&1fcb8 b209202(SparseArray,[B,Map,I)@0x20110
|
||
c209202(SparseArray,Context)@0x20574 d209202(SparseArray,Context,I)@0x20688 e209202(SparseArray,Context,Map,I)@0x20984
|
||
f209202(SparseArray,[B,I)@0x20fc4 g209202(SparseArray,Context,Map,I)@0x211e8 h209202(...)@0x21468
|
||
i209202(SparseArray,Context,Obj,Obj,Obj)@0x21624 **j209202()String@0x21948 (取DID!)**
|
||
**k209202([B)[B@0x219d8 (字节进出 = 传输加密!)** l209202(InvocationHandler,AtomicReference,ClassLoader)@0x42d50
|
||
m209202(SparseArray,Context,Map)@0x21bdc n209202(SparseArray,[B,I,String,J)@0x43900
|
||
- ga JNI_OnLoad @0x1faf0 (dynsym!) — 注册 = JNIEnv vtable-215 + 表@0x4d1a0 + 14计数
|
||
- ga 传输加密入口 k209202@0x219d8: strncpy@plt + 内部链 (0x19378/0x13eb8×2/0x1c640×2/0x24334/0x19258)
|
||
|
||
### 现状判断
|
||
- **捕获密文 ≠ mfa 的 b87_ 内部序列化** (sub_2E250 全偏移网格拒绝 = key 或格式不匹配)
|
||
- **捕获密文 = ga 传输层加密** (k209202 家族) — ga 调用返回 -1 = SDK 全局状态未初始化
|
||
- mfa 侧 a87_@0x25680 = 事件收集序列化 (sub_27DA4: timing-tags + 0x11-version-key + sub_2C76C 收集器) — 非传输加密
|
||
- 捕获密文 10B 魔数 7cda... = 运行时合成 (无反编译常量)
|
||
|
||
### 下一步 (两条关键路径)
|
||
1. **用户导出的 turingga.so IDA export** → k209202@0x219d8 反编译 → 传输加密链静态全解 (同 mfa 方法论)
|
||
2. harness 继续喂 ga 运行时状态 (0x48XXX 构建器等价物) → k209202 真调用 (同 b87_ -1 问题)
|
||
|
||
## §11.57 R26: ga k209202 = 16B 块加密器 + 状态门全打通 (2026-08-29)
|
||
|
||
### k209202@0x219d8 反汇编解构 ([B)[B)
|
||
- **GetArrayLength; `cmp w0,#0x10; b.ne FAIL` = 严格 16B 输入** (非大块传输加密!)
|
||
- GetByteArrayElements + strncpy(16B) → bl 0x19378 (对象创建!) → bl 0xc8a4 (builder-init)
|
||
→ bl 0x13eb8×2 (序列化!) → bl 0x1c640×2 → **bl 0x24334 (块加密核心: calloc+memcpy+全局0x48000+c18状态+0x5754串!)**
|
||
→ bl 0x19258 (收尾) → NewByteArray(0x580)+ 结果 vs 全局计数器 cmp
|
||
- **0x24334 = 带状态的块加密机器** (读 [0x48000+0xc18] 指针再解引用 → 计数器对比 w21!)
|
||
- 失败路径: 0x21b4c (len≠16 → x20=null) / 0x21bb0; 成功 = NewByteArray-route
|
||
|
||
### harness 状态门全打通
|
||
- **mfa: 0x48C18 = 单例 48BD8 对象字段 a1+64 (id!) — 补非零 → b87_ 全链 LIVE!!**
|
||
(sub_347C8(map-check) → GetByteArrayElements → sub_2E4A4 → XXTEA+inflate 全部执行!)
|
||
- b87_ 对捕获密文三种输入 (strip/only/magic-full) 全 -1 = **确定性拒绝 — 捕获 ≠ b87_'resp' 格式**
|
||
- ga: 0x48c18 状态指针已补 → k209202 仍 -1 (0x19378 对象创建需更深的 SDK 全局态)
|
||
|
||
### 结论 (本阶段定论)
|
||
- **捕获密文的密钥 = 设备材料派生 (mpdc 16B→ga 16B块加密→主密!) — 非静态 3DF88**
|
||
- mfa 的 XXTEA (24D58) = 内部序列化 (resp-解码侧!) — 已 100% 回环验证
|
||
- **传输加密链 0x19378/0x13eb8/0x24334 的静态明文 = 需要 turingga.so 的 IDA 导出**
|
||
(同 mfa 方法论: 反编译这些函数的 key 派生/块加密逻辑 = 密钥再推导!)
|
||
|
||
### 立即下一步
|
||
1. **用户导出 turingga.so 的 IDA decompile** (k209202 全家: 0x219d8/0x24334/0x19378/0x13eb8/0x1c640)
|
||
2. harness 继续: mfa 单例 34D68 全量 ctor (48BD8) + 各字段填充 → b87_ 对正确 resp 输入成功
|
||
3. 或 frida 捕获一次 **服务器 dfpConfig 响应** (b87_ 的 resp 输入 = 密钥推导的旁路!)
|
||
|
||
## §11.58 R27: 🎯 完整加密链验证 — deflate+XTEA 闭环出炉 (2026-08-29)
|
||
|
||
### 加密链全解 (mfa IDA 反编译闭环!)
|
||
- **sub_2E164 = 加密封装**: `deflate(a1, a2, &def, &dlen, 0xFFFFFFFF)` + `sub_25234(...)`
|
||
- **sub_25234 = XTEA 封套**:
|
||
```
|
||
v5 = len + 4*(len&3!=0) + 4 (对齐到4B + 长度字!)
|
||
calloc; memcpy; 尾字 = 明文长度!
|
||
sub_24D58(buf, +n, key@3DF88) (加密!)
|
||
```
|
||
- **解密对偶**: sub_252EC (XTEA-dec + 尾字长度校验) → sub_34A00 (inflate!)
|
||
- **链条**: 明文 → deflate(zlib) → [pad + 4B len] → XTEA-enc@3DF88 → 密文!
|
||
→ XTEA-dec → 尾字取长 → inflate → 明文 ✓
|
||
|
||
### harness 全链运行成功!
|
||
- Java-deflate 44B→47B (zlib) + **真 sub_25234 ret=1 密文 52B** (4012a3a8e4...)
|
||
- **GOT RELRO 坑找到: 导入槽在只读页!! — mprotect(0x45648,0x3440,7) 后补丁才真正写入**
|
||
(之前所有 -1/崩溃 = 未写成的 GOT 槽 br-0!)
|
||
- 修复顺序: load → gap-mmap → **mprotect RELRO→RWX** → patchImports → 调用!
|
||
- 52B 密文 = 48B (pad+XTEA 块) + 尾 4B 长度字段的所有权 = XTEA 加密涵盖
|
||
|
||
### 捕获密文格式确认
|
||
- **捕获 = [13B 请求头][XTEA-wrap(key=设备派生)]** — 13B 头 = 请求协议 (7c da/版本/nonce/uptime)
|
||
- 密钥 ≠ 静态 3DF88 (设备材料 mpdc/ga2/turing.dat 派生)
|
||
|
||
### 下一步 (密钥推导战)
|
||
1. 候选密钥批量解: mpdc-16B / ga2 / turing.dat / sha256(materials) / 13B-nonce
|
||
2. 13B 头语义解码 → nonce→key 关系 (R24 已证: 同 nonce 对 13B 头相同!)
|
||
3. ga k209202-16B 块 = 设备材料块加密器 (状态补全后 = 主密钥源!)
|
||
|
||
## §11.59 R28: 头结构破译 + 密钥候选批量否定 (2026-08-29)
|
||
|
||
### 13B 头结构 (4 样本对比!)
|
||
```
|
||
0625: 7c da | 11 | 69 c9 f5 28 9c 4d 46 | a1 de 05 | 43 48 82
|
||
0647: 7c da | 11 | 69 c9 f5 28 9c 4d 46 | a1 de 05 | 11 bb 80
|
||
0649: 7c da | 01 | 89 c8 f5 08 9e 4c 48 | e3 ec 07 | 13 55 a7 (fresh-identity-B!)
|
||
full: 7c da | 11 | 69 c9 f5 28 9c 4d 46 | 95 6f 21 | 10 bb 80
|
||
```
|
||
- [2B 魔数 7cda][1B 版本: 01=首次/11=重注册][7B 会话ID (同会话 3 样本相同!)] [3B 会话动态][...]
|
||
- 0649 (fresh-B) 会话ID 完全不同 → 会话ID = 身份相关 (非纯随机!)
|
||
|
||
### 密钥候选批量解码 (真 sub_24D58, 20 密钥 × off-11 3920B)
|
||
- mpdc/mpdc_rev/md5/sha1/sha256(mpdc|sess/mpdc|sess|turing)/sess7pad/sess7trace/
|
||
mpdc_xor_sess/sha256(sess)/dat 派生 — **全部 lencheck=False** (尾字非长度!)
|
||
- 结论: 密钥 ≠ 简单材料哈希组合 — 派生逻辑含未知变换 (版本化/轮换!)
|
||
|
||
### 判定
|
||
- 完整加密链已验证 (deflate→pad+len→XTEA@24D58!) = 纯代码铸造的可执行底座
|
||
- **密钥派生 = ga 传输层内部 (k209202-16B块/0x24334 状态机!) — 需 turingga.so 的 IDA 导出**
|
||
(同 mfa 方法论: 反编译 = 派生逻辑可读!)
|
||
|
||
## §11.60 R29: 🎯 ga 全密钥/编码链映射 (IDA 导出到手!) (2026-08-29)
|
||
|
||
### ga 导出核心突破
|
||
1. **密钥槽: `&unk_5788` = f15e78d23033c2c4bac8a925677e863a — 与 mfa 3DF88 完全相同!!**
|
||
(turing 共享代码段常量 key!)
|
||
2. **ga XTEA 核心 = sub_1F1CC** (24D58 同款变体!) — 封套:
|
||
- 加密 sub_1F918 = [pad+4Blen] + sub_1F1CC(+n, key)
|
||
- 解密 sub_1F9C8 = sub_1F1CC(-n, key) + 尾字长度校验!
|
||
3. **解码链 sub_27DBC** = XTEA-dec + **sub_2E704 inflate** (全部确认!)
|
||
4. **b209202@0x20110 (SparseArray,[B,Map,I) JNI 分发**:
|
||
- a6==1 → sub_27FF8 = 解码 (27DBC + 5788!)
|
||
- a6==0 → **sub_23DF8 = 编码器** (sub_1BAD8 上下文 + sub_1AA64 喂入 + finalize!)
|
||
5. **k209202@0x219d8 16B 块链**: 16B→sub_19378对象(RiskDetectExt.ExtData schema!)
|
||
+ sub_24334 状态拷贝 + deflate(sub_2E904) + XTEA(sub_1F918, **key=16B输入!**)
|
||
6. sub_192B0: **"RiskDetectExt.ExtData" schema-string + sub_1A7A8("int32","string")!**
|
||
|
||
### harness
|
||
- ga 导入 127 槽补丁 (mprotect 0x48720 RELRO) + 直调 sub_1F1CC 5788 全偏移扫描
|
||
- **所有静态 5788/3DF88 解密 lencheck=False** → 请求密文密钥 ≠ 静态 key (进一步派生!)
|
||
|
||
### 下一步
|
||
1. sub_1BAD8/1AA64 (编码上下文!) — 请求密钥路径
|
||
2. b209202 a6=1 harness 全调用 (喂捕获密文 → 解码输出!)
|
||
3. 或: 版本字节 01/11 的密钥变体
|
||
|
||
## §11.61 R30: 💎💎 JAVA 侧全逆 — 请求 XXTEA + 会话密钥架构 + Lyra TLV 格式 (2026-08-29)
|
||
|
||
### Cprivate.java = 请求加密器 (jadx 直读!)
|
||
```java
|
||
// b(data, key) = 加密 (XXTEA!):
|
||
// a(key) = key.length > 16 ? MD5(key) : key ← 密钥变换!
|
||
// words; v[last] = data.length ← 尾字=明文长度!
|
||
// XXTEA: sum -= 0x9E3779B9; e = (sum>>>2)&3; k[(i&3)^e]! ← 与 native sub_24D58 同构!
|
||
// a(data, key) = 解密 (140行!): 逆循环 + 长度校验 (i11<=i3<<2!)
|
||
```
|
||
- **KEY 变换确认: >16B → MD5; 16B 内 → 原样**
|
||
|
||
### 密钥架构 (Perseus.java + Guava.java!)
|
||
- **密钥 = SecureRandom 16 字符 [A-Za-z0-9] (62^16!)** — 内存字符串 guava.c!
|
||
- RSA-encrypt(hex(key), 内嵌公钥 MIIBI...) → 帧1 上送服务器 (guava.b 信封!)
|
||
- "EP_TuringMM" 标识串 + Guava{seq, envelope, KEY-STRING, sessionId, expire}
|
||
- 请求密文 key = guava.c 的 UTF-8 字节 (16B 不触发 MD5!)
|
||
|
||
### Lyra/Lynx TLV 格式 (请求结构!)
|
||
- 帧头 = (type<<4)|tag; type==15 → 扩展 tag 下一字节!
|
||
- type: 0=1B 1=2B 2=4B 3=8B 4=4B 5=8B 6=lenB+str 7=4Blen+bytes 8/9=集合 10=map 11=END
|
||
- 请求 = 帧10(配置) + 帧11(CIPHER 字段2!) — 捕获密文 = Cprivate.b(序列化事件, session-key!)
|
||
- 13B 头 decode 进行中 (first: '7c' = type7-tagC bytes!)
|
||
|
||
### 结论
|
||
- **捕获密文 = Java Cprivate.a(data, 16字符会话密钥) 可解 — 密钥在 app 内存 (guava.c)!**
|
||
- 静态/哈希密钥全错的根因: 每次会话随机 16 字符!
|
||
- 纯代码铸造 = 伪造会话: RSA 信封自己签不了 → 但可 frida 内存取 key!
|
||
|
||
### 下一步 (R31)
|
||
1. frida 抓 guava.c (16字符) — Java 层单点 hook (Perseus.a 本地串 / Guava 实例)
|
||
2. 用真 key 跑 Cprivate.a(捕获密文) = 明文事件!
|
||
3. Lynx 全解码器 (13B 头语义 + 帧结构)
|
||
|
||
## §11.62 R31: 💎💎💎 活体重抓 3 密文 + Java 全逆确认 + native 全程零调用 (2026-08-29)
|
||
|
||
### 配方复现 (用户协助)
|
||
- frida-server 必需 `-l 0.0.0.0:31878` (否则连接closed!)
|
||
- 触发 = 只清 turing 状态不 pm-clear (保留登录!) + spawn + hook_str_magic → ~40s 自动重注册上报! (用户"退出重新登录不触发" = 登录态在)
|
||
- 3 个全新 POST: 4384B(cipher 4117@70) / 4267B(4000@70) / 4261B 同刻捕获!
|
||
|
||
### Java 侧全逆 (jadx classes7.dex — 与 native 同构确认)
|
||
- Cprivate(实类名 ga.private!) = XXTEA: b(enc)/a(dec), key>16B→MD5, 尾字=明文长
|
||
- Perseus.a() = 会话: SecureRandom 16字符[A-Za-z0-9] → RSA(MIIB...公钥)封装 → guava{seq,env,KEY,d,expire}!
|
||
- Lyra/Lynx TLV: 帧头=(type<<4)|tag, type 0-13 (0=1B 1=2B 2=4B 3=8B 6=str 7=bytes 8/9=集合 10=map 11=END)
|
||
- 请求 = Cthrow{Csuper[]} → Octans.a(Lyra帧) → Cextends.a(deflate) → Cprivate.b(data, 16字符UTF8字节)!
|
||
|
||
### native 零调用铁证 (同一活体会话!)
|
||
- xtea-hooked mfa@24D58 + ga@1F1CC ✓ 但 0 次 xtea-got (报告期内核从未被调!)
|
||
- nat b209202@20110 + k209202@219d8 hooked ✓ 0 次 nat-ent
|
||
- Java Cprivate.b/Perseus.a hooked ✓ 0 次调用
|
||
- → 报告加密在 turing 的第三路径 (非 JNI 方法族!): 需hook已知 JNI impl (mfa b87_@25c98 / ga 27DBC 解码族!) 或 native 内部更多偏移
|
||
|
||
### 下一步 (R32)
|
||
1. hook mfa b87_@25c98/27DA4 + ga 27DBC/23DF8 (JNI impl 直挂!) — 关键参数 (byte[], map, mode!)
|
||
2. 或: mfa 动态符号/内部调用大范围探 (OLLVM 静态已尽)
|
||
3. 解密器待 key: cprivate.py (Cprivate.a 全等直译) 已就绪 — key 一到即解 3 个新密文!
|
||
|
||
## §11.63 R32: 8 密文集 + 全钩零调用定论 + Java/native 差分 (2026-08-29)
|
||
|
||
### 8 密文集 (存 evidence/dfp_live/)
|
||
4384/4267/4261/4263/4267/4261/4261/4263...B POSTs + bodies (magic 10B 一致 @70!)
|
||
- 0647-0856: 8 个活体 POST (同配方重注册触发!)
|
||
- 头部同族: 7cda 11 {session} {cnt} {stable} — 计数位 28/34 递增等
|
||
|
||
### 三重钩全零调用铁证 (原生 25 JNI + XTEA 2 核 + Java 2 钩!)
|
||
- mfa 11 JNI (a87_@25680..k87_@27b74) + ga 14 JNI (a209202@1fcb8..n209202@43900) = 全部安装且 0 次触发!
|
||
- mfa sub_24D58 + ga sub_1F1CC XTEA 核心 = 0 次!
|
||
- Java ga.private(Cprivate).b + Perseus.a = 0 次!
|
||
- → 报告密文 = 隐藏 native 路径 (JNI 方法族之外!)
|
||
|
||
### Java Cprivate 与 native sub_24D58 差分
|
||
- Java (decompile 直读) = 标准 XXTEA (sum-=DELTA!)
|
||
- native (OLLVM 模糊!) — known pair 校准失败 (44B: native ct 45542bb3... vs python 全部变体)
|
||
- → native 核心 = 变体 (OLLVM 掩码深处) 或 harness 参数语义差异 (raw-core vs envelope-len-word)
|
||
- rtk_known/ 存校准对 — 后续对照用
|
||
|
||
### 下一步 (R33)
|
||
1. native 信封/收集器全钩: mfa 2E164/2E250/25234/252EC/34B98/34A00/2E4A4 + ga 1F918/1F9C8/27DBC/27CE0/23DF8!
|
||
2. 或用已知对差分出 native 变体的确切公式 (再校验 Java 解密器)
|