Files
live-hub-py/docs/dfpReport破解进度.md
yml2213 eccc00e090 docs/code(huya): 设备标识符命名规范落地 - 全库HDID32/GUID32/DEVID40前缀化去混
- docs/HUYA_ID_NOMENCLATURE.md: 规范总表 + 三条强制规则 + 代码变量对应
- 4 篇主文档头部去混声明 + §11.x 被证伪假设行标注更正
- 代码: GOLDEN_HDID→GOLDEN_HDID32, device_profile.HDID→HDID32, wup_encoder/app_login 注释前缀化
- 后续所有表述必须以 GUID32(doLaunch) / HDID32(登录t1.t0) / DEVID40(t5) 前缀书写
2026-08-28 23:02:21 +08:00

65 KiB
Raw Permalink Blame History

dfpReport 破解进度总结 (待破解#2)

当前状态:加密算法已确定为逐字节 XOR keystream(非 XXTEA),明文 JSON 已全部拿到。 剩余关键问题:keystream 每会话动态生成,需找到其生成函数/种子。 IDA 已导出两个 so 的完整反编译+反汇编:/Users/yml/Downloads/so/{libhydeviceid,libudbauthunify}.so_export_for_ai


一、已确定的结论(铁证)

1. dfpReport 加密 = 逐字节 XOR keystream(不是 XXTEA!)

证据链:

  • 同会话 5 次 dfpReport(wire 间隔 3-6s),密文两两 XOR 非零位置 3947/3984 → keystream 每会话动态
  • 密文前 10B 魔数恒定 57 18 82 cf 66 4b b3 94 01 ee(跨天、跨会话)→ 魔数是明文头 XOR 固定值或者固定前缀
  • 8.23 各样本位置 11-22 部分相同 → 是明文固定字段,不是 keystream 泄漏
  • 若为 XXTEA(hyudbxxt),则密文任何字节变化会扩散到全部 → 但实测前 10B 不动 → 排除 XXTEA

2. tag2 密文结构(3984B)

tag2 = [魔数 10B][XOR密文 3974B?]
  • 明文 ≈ JSON(554B) + 采集二进制(3420B) ≈ 3974B,与"密文长度-魔数"吻合
  • 但注意:3954B 对齐实验(偏移 0..9 all ~1% 相同率)未找到明文精确起点 → 加密输入可能不是纯 JSON,JSON 只是其中一部分(pa 结构待最终确认)

3. 明文 JSON 完整捕获(554B,可铸造)

{"appId":"5008","appVer":"13.4.22","appkey":"865a4924a40897ac1fcfe6b4c2cbb0e3",
 "channel":"xiaomi","deviceId":"02df398797432eadefcc12767119ad5e80999389",
 "deviceName":"M2102J2SC","hdid":"7c5387e0539c023c31c4ff0e807e7256117385ee",
 "heightPixels":"2120","isCloud":0,"isForbidLog":1,"isHome":0,"isPre":0,"openAppId":"",
 "savePath":"/data/user/0/com.duowan.kiwi/files/huyaudb","sdkVer":"1.0.80138",
 "servantName":"huyaudbwebui","shareAppDataPath":"/data/user/0/com.duowan.kiwi/files/huyaudb",
 "systemInfo":"android","systemVer":"M2102J2SC,30,11","terminalType":1,"testEnv":0,"widthPixels":"1080"}
  • hdid 字段 = 服务端返回的 device_id (t5) 7c5387... → 服务端回显本地明文,本地可完全控制设备身份!
  • appkey 865a...cbb0e3 ≠ 静态 t1 865a...cbb045 → appkey 来自运行时配置

4. 响应回显验证(t1/t5 本地可铸造)

  • 重放任意 wire body → HTTP 200,344B 响应,同一 t1+t5,全新 t2
  • t1(865a4924a40897ac1fcfe6b4c2cbb045) 静态埋于 libhydeviceid .data(0x3bb190, 0x3bb230, 0x3e0e50),datadiv 加载时解密
  • patch 明文实验:改明文 JSON 的 hdid/appkey/deviceId(等长替换)后 app 崩溃 → 说明有校验或时序问题,但方向正确(见下"待办")

5. keystream 前 24B(从 5 组同步 pair 恢复)

# 修正对齐 off=0 (密文[10:] XOR 明文[0:]):
t=1379: 07 f8 60 f9 b8 bc 30 bf 76 4e d4 8d a8 f6 54 21 31 fa 47 53 72 63 ef 29
t=7268: 17 f9 70 e5 b8 3c 6c be 77 e6 ac cb c3 2a 6a 8c ef aa cd ce eb e5 67 a7
t=3695: 17 f9 70 f9 b1 3c 4c be 77 74 14 ee 5e 7c e0 88 3a e2 c3 2c 72 63 8c 10
...

各会话 keystream 不同 → 动态生成,极可能种子 = 时间戳或会话随机 + 固定密钥


二、IDA 导出可用资源(用户新提供,重点!)

/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/
  decompile/     # 3895 个 C 反编译
  disassembly/   # 逐函数汇编
  exports.txt    # 1832 个导出 (含 70 个 .datadiv_decode)
  function_index.txt, strings.txt, memory/...
/Users/yml/Downloads/so/libudbauthunify.so_export_for_ai/
  decompile/252700.c  # hyudbxxt::xxtea_encrypt 标准 XXTEA (dfp 无关,登录用)

优先排查的加密函数:

  • decompile/1107CC.c 引用 0x5718 魔数常量 ×3!(sub_1107CC,调用者 0xa6a3c)← 最可疑是真加密/解密函数
  • decompile/1D52C8.c1D5844.c294FB0.c7BF8C.c 也含 0x5718
  • 大 XOR 循环候选:17F75C, 212B04, 21F98C, 2332D4, 23755C, 23C534, 26D7B0, 274BBC, B5A18

三、动态 hook 关键资产

抓同步 (明文, 密文) 对 —— 已成功!

scripts/capture_pair_sync.py:spawn+bp3 绕过 → SSL_write 命中 dfpReport 时 80ms 后全内存扫描 {"appId":"5008" → 同时刻明文。5 组同步 pair 在 evidence/dfp_pair_sync.json

环境

  • RE repo venv: /Users/yml/codes/Reverse-Engineering-Agent-Universal-v3.0/.venv/bin/python -u
  • 远程 frida: adb forward tcp:31877 tcp:31877 + frida.get_device_manager().add_remote_device("127.0.0.1:31877")
  • 绕过:spawn 后先 bypass_msaoaid_maps_art_callsite.js + mask_frida_maps_only.js → resume → 11s 后 patch_guard_block_termination.js
  • hook 规则:后台任务 + tail 实时看 + 命中即 pkill(见 docs/Hook脚本运行与捕获约定.md)

四、下一步行动(按优先级)

  1. 读 IDA 反编译确认加密:重点 decompile/1107CC.c(引用 0x5718!)及其调用者链 0xa6a3c → 找 keystream 生成函数(输入时间戳/随机种子 + 固定 key,输出 3974B 流)
  2. hook 加密函数:定位后 hook,抓 (明文输入, key/种子, 密文输出) 三元组 → 直接复现纯 Python 加密
  3. 验证 keystream 种子假设:比对 5 组 keystream 的 Δ 与 wire 时间戳 Δ(3-6s 间隔,keystream 差异是否对应计数/时间)→ 若种子=时间戳,纯代码可复现
  4. patch 明文实验修复:上次改明文崩溃可能因 datadiv 重解码或时序;改用 hook 加密函数输入 buffer 后直接改明文再放行,避免物理内存写
  5. 最终目标:纯 Python 生成 dfpReport 请求体 = 构造明文 JSON(改 hdid/deviceId/appkey 铸造新设备) + 魔数 + 复现 keystream 加密 → 重放得到全新 t1/t5

五、账号/数据资产

  • 账号 hy_300026595 / aa778899(老 ref hy_300024708)
  • HAR:/Users/yml/Desktop/抓包/hy/8.23/登录-{1,2,3,5}.har(6 个 dfpReport wire,t2 len 3408-4237)
  • 同一设备(小米 M2102J2SC, Android 11, 快照 5dd8c93f)所有 wire 魔数头相同
  • 关键回显:hdid 7c5387e0539c023c31c4ff0e807e7256117385ee = 明文 hdid = 响应 t5

六、关键文件索引

文件 内容
evidence/dfp_pair_sync.json 5 组同步 (密文+明文) pair(最新最有价值)
evidence/dfp_pair.json 早期 3 组 pair(wire len 4278/4397/4413)
evidence/dfp_wire_stack.json 8 事件 5 dfpReport wire HEX
evidence/dfp_plain_big.json 187 个堆命中(明文 JSON @0x7814096d19 等)
evidence/dfp_datadiv_full.json .data 7895 个已解码字符串(t1 所在)
scripts/capture_pair_sync.py 同步 pair 抓取脚本(可复用)
/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/ IDA 反编译(新!)

七、新会话起点(最重要的下一步)

IDA 反编译确认加密:/Users/yml/Downloads/so/libhydeviceid.so_export_for_ai/decompile/1107CC.c

  • 引用 0x5718 魔数 ×3,调用者 0xa6a3c → 极可能是 dfpReport 加解密核心
  • 同时看:1D52C8.c, 1D5844.c, 294FB0.c, 7BF8C.c(均含 0x5718)
  • 反汇编对应文件在 disassembly/.asm

验证 keystream 种子:evidence/dfp_pair_sync.json 有 5 组同步 pair, 两组 keystream 差 vs 时间戳差 → 判断种子是否时间戳/计数器。


八、本会话重大发现(新!)

1. 输入结构确定: [恒定JSON 554B][collection ~3400B 每轮变化]

  • 恒定 JSON(554B,已有全部字段,含 hdid/deviceId):{"appId":"5008",...,"widthPixels":"1080"}
  • collection 是大 JSON:{"touch_events":[...],"net_intfs":"...","phoneInfo":"{...}","qimei16/36":"...","risk_apps":"com.topjohnwu.magisk|bin.mt.plus|","scanned_appname":"<binary ~2900B>",...}
  • collection 尾部含 Poseidon 反 frida 检测字段:"Poseidon_D1":"libhydeviceid.so|hooked:libhydeviceid.so|hooked:libc.so|", Poseidon_D3:"zygisk|", Poseidon_D5:"/product/bin/su\n|", adb_input_list:"device_name=Virtual|"(huyaudb 读 /proc/self/maps 检测 hook)
  • collection 每轮变化(触摸事件/权限/网络/反检测结果/扫描二进制)

2. keystream 每轮变化,但跨轮/跨会话有强共享结构(非纯随机!)

多 session(6+ 个 pipeline 证据文件)9+ 轮 ks[0..553]=cipher[:554]^JSON 分析:

  • 跨会话模板复用:相同 10B 前缀在多个独立 session 反复出现!
    • 07f970f9b8bc50be774e ×3(155601-r1, 160316-r1, 160316-r2)
    • 17f97019b93c6cbe7764 ×3(155601-r3, 160316-r3, 160855-r2, 170031-r2)
    • 17f97ef9b83c6cbe774a ×3(args session 多轮)
    • 07f870f9b8bc50be774e ×2(v2)
  • 同一 session 连续轮次可完全相同(160316: r1 = r2 = 07f970f9b8bc50be774e)
  • 每位置 ks 值域很小:8 轮样本中大部分位置只有 28 个不同值(随机期望 8),前 6 字节仅 23 个值
  • 高共享对:r2(160855,t=18273) vs r5(170031,t=100816) 共享 140/554B(25%;随机期望 0.4%),最长连续 20B;r0 vs r4 共享 80B
  • 结论:keystream 不是每会话随机种子动态生成,而更像 固定模板库 + 状态选择(生成器需进一步确认,但"所有字节每轮全新随机"的假设被推翻)

3. 反 frida 检测: hook 了 libhydeviceid 的事件, collection JSON 的 Poseidon 字段值相应变化(每轮检测结果不同)

4. 骨架 pipeline hook 已稳定(轻量,符合约定)

  • scripts/hook_dfp_pipeline.py 只 hook 6 函数(topA_20e924/midA_5a938/cryptoA_5b1f4/copy_61098/pktA_7f744/pktB_92128)
  • 必须遵守 hook 约定:spawn → bypass x2 → resume+11s → patch_guard → 最后加载主 hook JS(顺序颠倒会卡启动界面,已踩坑多次)
  • 事件每轮:copy_61098 ×2(MAGIC 缓冲)、pktA/pktB(MAGIC + taf 段)、wire(密文全长)、jsonhits(内存搜 JSON 完整 554B)
  • GOT 桩方案已放弃:144 个 br x17 桩 hook 太重,App 卡启动/卡点击(动态间接调用表 0x3ba000/0x3b9000 运行时才解密,静态也解不出)

5. 本地 capstone 反汇编(ELF vaddr==fileoff)

  • cryptoA_5b1f4: 16 个 bl 目标(0x5f1b4 首调、0x45aa0×26、0x76ab0×9、0x461c0×6、0x6ec68×4、0x7398c×3、0x1f07ec×2…)
  • 0x461c0/0x45aa0 = 间接调用桩(adrp x16;ldr x17,[x16,#off];br x17),GOT 表运行时解密失败静态解析
  • 0x5f1b4 = 字符串构建函数(状态机混淆),非生成器;cryptoA 主体无标量/NEON XOR 循环 → XOR 内核在间接调用或查表中

6. 下一步(铸造路线评估,优先!)

  • 服务端旧结论:重放任意 wire body → HTTP 200,hdid/device_id = 明文回显
  • 若 keystream 真是模板库,铸造新设备 = 改明文 JSON(hdid/deviceId) + 复用任意一轮 keystream XOR → 重放
  • 行动:纯 Python 构造新设备 t2(改明文 + 模板 ks)重放验证 200 → 若成功,铸造路线打通,生成器破解降级为可选

九、铸造路线实验(本会话最新,部分成功!)

1. 纯 Python 重放 100% 可行(不需要 keystream!)

  • scripts/forge_replay.py:取证据里一帧完整 wire → 重构 HTTP/1.1 POST(Content-Length 等长) → socket+TLS 直连 wsapi.huya.com:443原样重放返回 HTTP/1.1 200 OK + 344B 响应(稳定复现)
  • XOR 自反性铸造:新密文 = 原密文 XOR (原JSON XOR 新JSON),JSON 区(554B)等长替换 hdid/deviceId/appkey,collection 区不动 → 不需要 ks、不需要生成器、不需要设备!
  • 服务端确实解析 t2 明文(见下),不是纯透传 200

2. 响应结构(344B taf/wup 包)

  • 4B 总长 + 头 + huyaudbwebui dfpReport + 字段:
    • t1 (tag22): 865a4924a40897ac1fcfe6b4c2cbb045 + & + base64(~43B,每次变化,像会话加密数据)
    • tag70: actionV(<40hex> (设备标识回显)
  • 40hex 之后有 taf 尾字段(0x0b 8c... 未解析)

3. 服务端行为(受控实验)

请求 actionV(40hex)
baseline 原样 7c5387e0...17385ee(= 明文 hdid!)
只改 hdid 新值(≠ 新 hdid,每次实验都不同)
只改 deviceId 新值(≠ 新 deviceId)
用 actionV1 当 hdid 再上报 又一个新值(不稳定)
  • actionV 对 hdid/deviceId 变化均敏感 → 服务端解密解析了 t2 明文
  • baseline 时 actionV == 明文 hdid(服务端回显/认可当前设备)
  • 改身份后 actionV 每次都是新值且不稳定 → 未确认"下发新设备身份"机制,可能 actionV 含时间戳/会话随机,或需配合后续登录流程

4. 待验证决策点

  1. actionV 是否 = HMAC/带时间戳编码(需要多次同明文请求对照:两次同明文 → actionV 相同则与明文确定性相关;不同则含随机)
  2. 需要抓完整登录流程(登录请求也用同一 hdid?)确认铸造出的新身份能否通过登录
  3. 若 actionV 含随机/时间戳 → 服务端可能靠 t1 或其它字段做设备绑定 → 需重新审视铸造入口

十、29 轮 keystream 全量分析(决定性结论!)

合并 6+ session、29 轮 ks[0..553] 分析:

1. keystream 前缀模板复用(铁证)

17f97019b93c6cbe7764  x4: 160316r3, 160855r2, 170031r2, 175740r2   ← 4 个不同 session 同轮次位置!
07f970f9b8bc50be774e  x3: 155601r1, 160316r1, 160316r2
17f97ef9b83c6cbe774a  x3: args.j r3/4/5
07f870f9b8bc50be774e  x2: v2 r1/r2
  • 完整 10B 前缀跨 session 精确重复,不是随机

2. 同 session 完全重复

  • args.j r3 = r4 = r5: ks[0..553] 全部 554 字节相同!(shared=554)
  • v2 r1≈r2(51 共享),160316 r1≈r2(36 共享)

3. 高共享对(跨 session)

  • 160855r2 vs 170031r2: 140/554 共享,最长连续 20B
  • 170031r3 vs 175740r3: 97 共享
  • 170031r2 vs 175740r2: 70 共享,最长连续 31B
  • 160855r1 vs 160855r4: 80 共享(同 session 首尾)

4. 结论

  • keystream 与"轮次序号/状态"强相关,同一序号位置在多个 session 复用相同(或高相似)ks
  • 不是每 session 随机种子;更像 固定生成器 + 状态/序号选择(同轮次位置 → 同 ks)
  • 对铸造的含义:任意一帧真实 ks 即可加密伪造明文(服务端行为见下节,这是关键限制)

5. 服务端铸造验证(forge_replay.py 实测,重要!)

  • 原样重放:HTTP 200,344B 响应稳定,actionV = 7c5387...(= 真 hdid,稳定回显)
  • 改明文 JSON 任何身份字段(hdid/deviceId/appkey):200 OK,但 actionV 变为 [32hex 类MD5] + "10" + [6hex] 动态 token,同明文两次上报 token 不同
  • 响应 t1(tag22)恒定 865a4924a40897ac1fcfe6b4c2cbb045&base64(base64 部分每次变化)
  • 推论:服务端识别"真设备"靠 明文三元组 (hdid, deviceId, appkey) 与库匹配; 纯改明文不匹配 → 动态 token → 铸造新设备需同时伪造三元组一致的完整设备身份 (可能还要 collection 里 qimei/device_id 对应)否则服务端不认可

6. 下一步两个方向

A. 铸造:找出服务端设备三元组的匹配逻辑(抓真实 App 首次注册流程/或抓登录后的完整身份链), 纯 Python 全真伪造(JSON+collection 齐改)重放验证 actionV 稳定性 B. 生成器:既然 ks 与轮次序号相关,继续定位生成函数(0x5f1b4 初始化 + cryptoA 调用链), 复现 keystream 生成,任意轮次任意长度可算


十一、服务端设备绑定校验(差分实验定论!)

对 t2 密文做逐区差分修改重放,观察服务端响应:

修改内容 actionV 结果
baseline 原样 稳定回显真 hdid 7c5387...
JSON 非身份字段(widthPixels 1080→1081) 稳定回显真 hdid(不受影响!)
JSON 身份字段 hdid 动态 token [MD5样式32hex]10[6hex],每次不同
JSON 身份字段 deviceId 动态 token
JSON 身份字段 appkey 动态 token
collection 区任意 1 字节(@560/@617/@753/@953 等) 动态 token(波及全 collection)

结论:服务端绑定校验 = (hdid, deviceId, appkey 三元组) + (collection 完整性)

  • 服务端解密 t2 → 解析 JSON 三元组 + collection → 与库中设备匹配 → 匹配则回显设备 hdid,不匹配则返回动态 token
  • 动态 token 不可复用(同明文两次上报不同)→ 不是"注册下发",更像"未知设备拒绝标记"
  • 铸造的真正瓶颈不是加密(已破解),而是服务端以 (三元组, collection) 识别设备
  • collection 含 qimei16/36、scanned_appname 等指纹 → 需与三元组自洽才能被服务端认可

后续(铸造完整方案待定)

  1. 抓真实 App 首次注册/重置设备后的第一帧 dfpReport(看服务端如何初始分配设备 id)
  2. 或逆向 collection 的指纹算法(哪些字段参与绑定)
  3. 验证"设备上等长 patch hdid + App 自行生成 t2"路线(修复 patch 崩溃:可能因 JSON 长度校验/内存写坏)


十二、请求链捕获定论(冷启动全链,决定性!)

scripts/hook_reqchain.py(全请求链捕获器)冷启动一次,抓全 SSL_write/SSL_read:

1. dfpReport 是冷启动自动触发,非登录专属

  • 启动后 ~7.6s / 14.7s / 17.6s 自动连发 3 次 dfpReport(无需任何用户操作)
  • 每次 dfpReport 后紧接 dckey/check(744B 请求,响应 665-667B),两者强关联
  • 三次 dfpReport 密文均不同(len 4010/4137/4187/4262),但响应 actionV 全部同一 7c5387...

2. dfpReport 响应 = 344B 稳定 taf(关键!)

  • 三次上报,每次返回 606B HTTP(200 OK) + 344B body,actionV 恒 = 真 hdid
  • 与 wsapi 重放实验完全一致:body 就是 actionV(40hex) + t1 + base64 会话字段

3. 服务端识别机制最终定论

  • 服务端识别设备 = 只靠明文三元组 (hdid, deviceId, appkey) 是否在设备库
  • 三次上报 collection/keystream 全不同,但 actionV 恒同 → collection 不参与设备识别
  • 三元组在库 → 回显真 hdid;不在库 → 动态随机 token(拒绝)
  • 这也推翻了文档第十一节 "collection 参与校验" 的猜测:实际是三元组唯一主导

4. 对铸造的真正阻碍

  • 不是加密(XOR 已破解,任意帧 ks 可加密伪造明文)
  • 不是 collection(不参与识别)
  • 是服务端设备库:只有 (hdid, deviceId, appkey) 已在库的组合才会被认可为设备
  • 光改明文三元组 → 不在库 → 拒绝。铸造需要伪造"库中已存在"的三元组,或走设备首次注册流程
  • 行动:抓"全新设备首次 dfpReport"(换新设备/清 huyaudb 数据后第一帧),看服务端是否为 unknown 三元组分配 hdid、分配后该 hdid 是否入库可复用 → 判断铸造是否可行

5. 清数据后的冷启动验证(新结论!)

  • adb shell pm clear com.duowan.kiwi 后冷启动:dfpReport 与 dckey 均未自动触发
  • 14 个请求全是杂项(cfg/日志/广-告/exapp/audid 等),无任何虎牙身份链请求
  • 结论:设备身份链(dfpReport→dckey)只在 App 数据已含身份 或 用户触发登录 时才发,清数据后启动不会自动重建身份
  • 完整看清全新设备首次 dfpReport 需 用户触发登录(clear 数据 → 登录 → 抓 dfpReport+登录链)

6. 再次验证:清数据后 dfpReport 仍自动触发且 actionV 恒同(重大修正!)

  • 推翻上一节结论:清数据后冷启动, dfpReport 在 t=1708/8695/11126/12274 仍自动连发 4 次
  • 4 次 actionV 全部 = 7c5387...(真 hdid)!
  • 真相: 清数据只是清 App 本地缓存, 明文三元组 (hdid/deviceId/appkey) 由固定密钥/算法派生,不受清数据影响
  • 服务端 4 次都识别为同一设备 → 设备身份不是"注册分配",而是固定派生 + 服务端查库匹配
  • 铸造含义: 三元组无法通过"重新注册/清数据"刷新; 服务端认的是已入库的真正设备的新三元组变化 → 铸造难度进一步确认(见节十一/十二)

7. 决定性结论: 设备三元组 = 固定派生, 铸造的生死线

  • 证据: 三次清数据(pm clear)后的冷启动, dfpReport actionV 全部恒 = 7c5387...(跨 3 个独立 session)
  • 含义:
    1. 三元组 (hdid=7c5387..., deviceId=02df..., appkey=865a...e3) 完全固定,与本地数据无关
    2. 服务端把"这三元组"当已知设备 → 回显 hdid; 任何与这三元组不同 → 动态随机 token(拒绝)
    3. 铸造新设备 = 需要一组服务端已入库的 (hdid, deviceId, appkey); 或逆向三元组派生算法, 但即便派生成功, 服务端也不认(不在库)
  • 因此铸造可行性取决于:能否获得/伪造一组"服务端库中已存在"的三元组。若不能, 纯改明文铸造不可行
  • 可验证方向: A. 抓服务端对从未见过的新三元组的初始动作(需真·全新设备——如换一台真机/模拟器首次启动), 看是否为 unknown 三元组分配 hdid 并入-库 B. 逆向三元组派生产生逻辑(hdid/deviceId/appkey 从何而来), 看是否能构造"合法派生的新三元组"
  • 备注: "登录超时"现象可能与捕获/清数据导致的会话状态有关, 与三元组恒定无直接关系


十三、三元组派生逆向 (静态, OLLVM 混淆)

1. 静态线索

  • libhydeviceid.so 内 .rodata @0x36c190 有明文 MD5 初始消息种子 + A/B/C/D 常量
  • MD5 特征移位 ror #7 @0xc37c0(全 SO 唯一), 附近为大量循环左移/加法/状态机(mov w#; cmp; b.eq 状态分发)
  • 0x36c230 有 hex 小写表 0123456789abcdef + 大写表 0123456789ABCDEF(hash 输出转 hex)
  • 结论:三元组派生用自实现散列(MD5 系)+ OLLVM 扁平化混淆,非标准库,常量被魔改/内联
  • 三元组已知: hdid=40hex, deviceId=40hex, appkey=32hex, 见第 7 节

2. 静态快速尝试

  • 常见设备因子组合(M2102J2SC/30/11/xiaomi/包名/等)拼接 MD5/SHA1 均不匹配 hdid/deviceId
  • → 派生混入随机盐或复杂组合, 纯猜测难破

3. 效率判断

  • 纯静态解 OLLVM 混淆成本高, 性价比低
  • 更优: 运行时 hook 派生点 → 直接抓 (输入, hash输出) 真实对应关系, 避开解混淆
  • 待办: hook MD5 实现(0xc37c0 附近) 的入参/出参, 抓三元组派生的真实输入


十四、模拟器探索(多号路线)

目标

  • 用户想"每个号一个独立设备身份"避免风控, 尝试用模拟器作为"全新设备"环境

已就绪

  • 模拟器(型号 PHU110, Android 12 API32, arm64, 已 root)
  • 装了 huya apk (apks/huya-13.4.22-115315-live.apk)
  • frida-server 15.2.2 (14.2.18 在模拟器上 spawn 超时; 15.2.2 spawn 正常)
  • python frida 升级到 15.2.2 (uv 装到 venv)
  • adb forward tcp:31878 -> 31878 (模拟器 frida 独立端口, 不与真机 31877 冲突)

关键坑

  1. spawn 导致 EGL 崩溃: App RenderThreadFailed to create context, error = EGL_NOT_INITIALIZED SIGABRT
    • 这是 spawn 挂起导致渲染时序问题, 非反调试/frida 检测
    • 非 spawn 正常启动 App 则稳定存活
    • → 需用 attach 而非 spawn (但 attach 慢会错过冷启动 dfpReport 窗口)
  2. 反调试: attach + SSL_write hook 未触发崩溃(与真机不同, 模拟器检测较弱)
  3. 模拟器 adb/frida 连接可能因模拟器窗口关闭而中断 (端口探测全失败)

当前状态 (被模拟器连接中断卡住)

  • 待模拟器重新连接后: 用 attach 抓冷启动 dfpReport, 验证"全新模拟器"服务端给的 actionV 是否为新 hdid


十五、模拟器全新设备验证 —— 决定性突破!

结论: 全新模拟器 = 独立设备身份 (服务端认可!)

  • 模拟器(未登录过的全新环境)spawn+frida bypass 后, 冷启动自动发 dfpReport
  • dfpReport 响应 actionV = e97dcfc3e46b0b4278d8cab1044c5fb01198c7fd
  • 与真机 hdid 7c5387e0539c023c31c4ff0e807e7256117385ee 完全不同
  • 服务端把模拟器识别为一台独立设备, 返回了独立稳定的 hdid
  • → "一个运行环境 = 一个设备身份" 成立

多号方案的技术基础确认

  • 每个干净模拟器实例 = 一个独立三元组 → 独立 hdid → 设备身份隔离就不同
  • 用户"很多号"可分配在不同模拟器/环境, 每个号用独立设备身份
  • 不再共用真机的一个固定三元组 → 风控关联大幅降低

技术要点 (模拟器 frida)

  • USB真机已拔, 只用模拟器 (PHU110, Android12, arm64, root, adb 127.0.0.1:5555)
  • frida-server 15.2.2 (/data/local/tmp/fs152, 端口 31878); python frida 15.2.2 (uv 装)
  • spawn 必须【立即 resume】, 挂起太久 App 的 RenderThread EGL 崩溃 (EGL_NOT_INITIALIZED)
    • 挂起加载 bypass 后要快速 resume, 加载本身没问题(用户提示 "不要自己制造困难")
  • bypass 三件套 (bypass_msaoaid + mask_frida + patch_guard) 可直接复用 (lib 内偏移, 同 apk 版本)
  • 脚本: scripts/hook_emu_spawn_bypass.py (OUT=evidence/emu_spawn_bypass_dfp.json)

待办

  • 提取模拟器三元组 (hdid/deviceId/appkey): 用 XOR 差分或 hook 加密前明文
  • 验证 e97d 身份是否多次稳定 (非动态 token)
  • forge_replay 用模拟器三元组纯 Python 重放验证 (跨机器可重放性)


十六、多环境独立设备身份验证(核心成果!)

验证: 每台(重置)模拟器 = 独立设备身份, 且可纯 Python 重放

环境 hdid (actionV) 说明
真机 M2102J2SC 7c5387e0539c023c31c4ff0e807e7256117385ee 基准
模拟器1 PHU110 (Android12) e97dcfc3e46b0b4278d8cab1044c5fb01198c7fd 已验证纯Python重放
模拟器2 SM_G998B (Android13) 3a13955c404c1eea620c75f59b5c6de211863f2f 已验证纯Python重放

关键验证(决定性)

  • 三台环境, actionV 完全不同 → 服务端按环境分离设备身份
  • 每台 spawn 抓一帧 dfpReport wire → 纯 Python socket+TLS 原样重放 → 服务端返回相同 actionV
  • 证明"每台干净模拟器 = 一个纯 Python 可重放的独立设备身份"成立

模拟器2 (Android13 SM-G998B) 技术要点

  • spawn + 纯 OpenGL(Vulkan)渲染 解决 EGL_NOT_INITIALIZED 崩溃(src: 用户在模拟器设置选"只有 OpenGL")
  • Android 12 那台靠"3-6s窗口"抓; Android13 这台纯OpenGL后 App存活久, 第一次尝试就抓到
  • frida 15.2.2 spawn + bypass_msaoaid_maps_skip_cleanup.js(frida反调试全过) → 抓 frame
  • frame 文件: evidence/frame_221833.json (dfp_wire + actionV=3a13955c...)

多号方案落地路径(成立)

  1. 每台(重置)模拟器 → spawn 抓一帧 dfpReport → 获得该环境设备身份(actionV)
  2. 之后纯 Python 用该身份与虎牙交互(可重放,已验证)
  3. 每台配不同账号 → 各设备独立身份 ∈ 不同 hdid → 风控关联大降

十七、设备身份派生因子 排查(克隆模拟器)

测试: 改各种设备标识, 观察 hdid(actionV)是否变化

变更 方法 hdid 变化?
IMEI 用户手动改 869390046034281866084040768472 不变 (e97d)
Android ID/SSAID settings put secure android_id 不变 (e97d)
ro.serialno magisk resetprop 改 不变 (e97d)
  • 结论: clone 继承源身份(必然); 改常规标识无效 → 身份派生源是更底层字段(待 frida hook 定位)
  • 延申: 真机/Android12/Android13 三环境 actionV 全不同 → 环境级标识决定身份.

十八、完整设备身份登记表(全部经纯Python重放验证)

环境 hdid (actionV) 状态
真机 M2102J2SC 7c5387e0539c023c31c4ff0e807e7256117385ee ✓重放
模拟器 PHU110 (Android12) 原机+clone e97dcfc3e46b0b4278d8cab1044c5fb01198c7fd ✓重放, clone继承相同
模拟器 SM_G998B (Android13) 3a13955c404c1eea620c75f59b5c6de211863f2f ✓重放
模拟器 GC3VE (Android12, 全新实例) 234548246275beedf8d93a242ef50e8f1178c3b2 ✓重放

已验证的确定性规律

  • 全新(非clone)模拟器实例 = 独立新 hdid(GC3VE 产生第4个唯一身份)
  • clone 复制 = 继承源实例身份(共享 e97d)
  • 纯 Python 重放可稳定代表各环境身份(不依赖模拟器存活)

换身份(在单台上)已穷尽失败

  • 改 IMEI / Android ID / ro.serialno / 整机型号(fingerprint) / 删App身份文件 / pm clear
  • 全部不影响 hdid → 身份钉在模拟器底层硬件标识(qemu实例), 上层改动无效

十九、DeviceInfo(wup) 结构逆向 —— libudbauthunify.so (真机运行时确认)

关键: libudbauthunify.so 符号未混淆, 是正确逆向载体

libhydeviceid.so 是其下层的"采集+加密"黑盒; libudbauthunify.so 负责 wup 打包,符号清晰可逆。

createWupDeviceInfo(0x2746a0) 输出结构 (真机 M2102J2SC 运行时 dump)

DeviceInfo偏移 值(真机) 含义
+0 (dword) 1 DeviceInfo type
+8 std::string M2102J2SC deviceName/model (来自全局 4915C8)
+32 std::string (heap, xxTeaAndBase64) 加密设备字段(deviceId等, byte_491998==0时)
+56 std::string android systemInfo (来自全局 491610)
+80 std::string M2102J2SC,30,11 systemVer (来自全局 491628)
+104 2120 heightPixels (来自全局 4915E0)
+128 1080 widthPixels (来自全局 4915F8)
+152 std::string BusinessCfg::getHdid() hdid

分支逻辑 (byte_491998)

  • byte_491998==1: DeviceInfo[8/32/56/80] = 静态全局字符串(运行时datadiv解密后)
  • else: 走 UdbUserFilterUtils::xxTeaAndBase64 编码 (加密设备字段, 可能是deviceId/appkey加密)

运行时解密全局(真机确认值)

  • 4915C8='M2102J2SC' 491610='android' 491628='M2102J2SC,30,11'
  • 4915E0='2120' 4915F8='1080' 491720=1

getHdid(0x26a484) / getSafeDeviceId(0x26a3c4) / setSafeDeviceId(0x26a2e0)

  • 三者都在 BusinessCfg 单例(getInstance)上操作
  • setSafeDeviceId: 写 this+1008=safeDeviceId, this+1088=hdid
  • getHdid: 读 this+1088 (锁保护)
  • hdid 是提前经 setSafeDeviceId 存进 BusinessCfg, 再由 createWupDeviceInfo 取出填 DeviceInfo+152
  • setSafeDeviceId 调用者 none(动态注册) → 真正 hdid 派生在 libhydeviceid(混淆区)

确认: keystream 每帧独立随机(同设备连续多帧密文仅27/4031字节巧合相同)

→ 帧间差分不可行; 恢复明文只能靠已知明文锚点+值推断

hook 经验(真机 frida 15.2.2)

  • 真机 spawn+G2-0055 bypass 稳定(nont crash), DeviceInfo 可反复抓到
  • setSafeDeviceId 在 spawn窗口不触发(更早或另一路径)
  • getHdid __usercall(a2@X8) 的 x8 在 frida onLeave 难取, 改读 this+1088 更实用

二十、真机完整明文 JSON 恢复成功 (libudbauthunify 逆向成果)

确认: 明文 JSON 完整模板 (真机 M2102J2SC, 双重验证)

  • 前34B ({"appId":"5008","appVer":"13.4.22") XOR 恢复的 ks 前缀与模板完全一致
  • hdid = 7c5387e0539c023c31c4ff0e807e7256117385ee 与文档真机 actionV 一致 (双重验证)

真机完整明文 JSON (586B):

{"appId":"5008","appVer":"13.4.22","appkey":"865a4924a40897ac1fcfe6b4c2cbb0e3","channel":"xiaomi","deviceId":"02df398797432eadefcc12767119ad5e80999389","deviceName":"M2102J2SC","hdid":"7c5387e0539c023c31c4ff0e807e7256117385ee","heightPixels":"2120","isCloud":0,"isForbidLog":1,"isHome":0,"isPre":0,"openAppId":"","savePath":"/data/user/0/com.duowan.kiwi/files/huyaudb","sdkVer":"1.0.80138","servantName":"huyaudbwebui","shareAppDataPath":"/data/user/0/com.duowan.kiwi/files/huyaudb","systemInfo":"android","systemVer":"M2102J2SC,30,11","terminalType":1,"testEnv":0,"widthPixels":"1080"}

三元组 (真机):

  • appkey = 865a4924a40897ac1fcfe6b4c2cbb0e3 (32hex)
  • deviceId = 02df398797432eadefcc12767119ad5e80999389 (40hex)
  • hdid = 7c5387e0539c023c31c4ff0e807e7256117385ee (40hex)

JSON 结构推导 (字段偏移, 真机模板):

appkey 值区间 [44,77] 32hex; channel [89,96]; deviceId [109,150] 40hex; deviceName [165,175] 变长; hdid [184,225] 40hex; systemVer [518,534] 变长 其余字段值固定(savePath/sdkVer/servantName/systemInfo='android'/heightPixels='2120'/widthPixels='1080' 等)

重要结论:

  • 所有设备 JSON 结构/固定值一致, 仅 hdid/deviceId/appkey/channel/deviceName/systemVer 不同
  • 因此"python 零设备自生成"的 JSON 部分: 已完全确定结构, 只需能生成三元组+(deviceName/systemVer 可读自系统)
  • ⚠️ 真正的唯一障碍 = XOR keystream 生成器 (tag2 的 571882cf664bb39401ee 魔数后那段密文)

dfp 加密路径归属判定:

  • createWupRequestData/createWupPackage → 标准 taf/wup + base64 (通用业务请求)
  • dfpReport 的 tag2 加密(XOR keystream) → 在 libhydeviceid.so (OLLVM混淆), 与 wup 路径不同
  • getOtp/hyudb_otp_encrypt → 登录 OTP (AES + nonce), 非 dfp 路径

createWupPackage 事务流 (37B020, FindPassword示例):

createWupReqHeader + createWupDeviceInfo + createWupProtoInfo → UniPacket → createWupPackage → put → doEncode → CBase64::Encode (标准wup二进制+base64)

hook 经验补充:

  • setSafeDeviceId 在 spawn 极早期一次性调用(dlopen后立即), 轮询命中不到
  • 内存扫描找明文JSON太重(SSL_write触发时scan来不及), 弃用
  • 真机 frida15.2.2 spawn+G2-0055 稳定抓 dfp wire (4298-4316B 每帧)

二十一、dfpReport 完整 wire 格式确认 (重要, 修订此前的"MAGIC后全XOR密文"理解)

真实 wire 结构 (真机抓到, SSL_write是明文!):

POST / HTTP/1.1
i-ver: 1
Content-Type: application/octet-stream
Host: wsapi.huya.com
User-Agent: okhttp/3.14.9
(blank)
body = taf/wup 二进制包 (4111B)

SSL_write 抓到的就是明文 HTTP (dfp 用 OkHttp Java TLS 发送, 调用栈 libjavacrypto.so)

body (taf/wup TLV) 结构:

00 00 10 0f            4B 大端总长
0c "huyaudbwebui"      servantName
0f "dfpReport"         functionName
0d "tReq"→ "android"   字段/参数
2d 00 01 0f ...
[ M MAGIC: 57 18 82 cf 66 4b b3 94 01 ee ]   (0x57=tag2类型标记)
[ cw: 明文JSON^keystream(586B) + collection^keystream(3445B) ]

cw 分两段:

  • cw[0:586] = 明文JSON XOR keystream (JSON结构已全解, 见第二十节)
  • cw[586:] = collection XOR keystream (3445B, 动态: 每帧不同, 长度也变3445/3460)

triple in 明文JSON (真机):

hdid=7c5387e0539c023c31c4ff0e807e7256117385ee deviceId=02df398797432eadefcc12767119ad5e80999389 appkey=865a4924a40897ac1fcfe6b4c2cbb0e3 channel=xiaomi deviceName=M2102J2SC systemVer=M2102J2SC,30,11

唯一黑盒剩余:

  • XOR keystream 生成器(JSON可解586B, collection明文未知不可解) 在 libhydeviceid.so(混淆)
  • collection 的内容/构建(动态随机, 含appkey? deviceId? 时间戳?)
  • 三元组派生(在 libhydeviceid 混淆区)

模型脚本: scripts/dfp_model.py (wire解析+重建验证)

二十二、★★ XOR keystream 生成器定位 (重大静态突破) ★★

在 libhydeviceid.so (OLLVM混淆) 找到了 keystream 生成链:

sub_1D1A68 (caller 0x1d3ab0) 是 keystream 种子生成器, 其解码后关键调用:

sub_8ECCC(buf, a1, 0);        // 用a1初始化随机状态
v9 = time(nullptr); srand(v9);   // ★ 种子 = time(NULL) (秒级时间戳!)
v58 = rand();                    // rand() 取首随机值
sub_1D0BB8(v58, keystream_state) // 用 rand() 扩展成keystream

链: srand(time) → rand() → sub_1D0BB8 → sub_1D14C4 (ostream格式化) → 输出keystream字节

关键意义:

  • keystream 种子 = time(NULL) 秒级时间戳 → 理论上若能复现 sub_1D0BB8 对 rand() 输出的扩展, 即可预测 keystream
  • libhydeviceid 内置 .srand(0x464d0)/.rand(0x46060) — 非 libc, 自有实现
  • sub_1D0BB8: rand()初始值 经 sub_1D14C4(ostringstream, num_put格式化) + 大量C++ std, 生成流
  • 动态验证受挫: libhydeviceid 延迟加载, spawn窗口 hook不到 base; 需 wait 模块 + 对内部 .srand/.rand 地址 hook

待逆:

  • sub_1D0BB8 具体如何把 rand() 一个 u32 扩展成 4031B 字节流 (核心)
  • keystream 是否 = rand()连续值 的某种字节拼接, 或经 ostream 格式化的十进制/hex文本

二十三、确认 keystream 使用 libc bionic 的 srand/rand (决定性)

objdump 证实: 0x46060=rand@plt, 0x464d0=srand@plt

  • libhydeviceid.so 内 .rand/.srand 是标准 PLT thunk, br 跳转到 GOT → 实际解析到 bionic libc 的 rand/srand
  • 因此 sub_1D1A68 里 "srand(time); rand()" 调用的是 libc 标准 rand (glibc/bionic 兼容, 可预测)

完整 keystream 生成链 (已确认):

sub_1D1A68 (由 0x1d3ab0 调用): sub_8ECCC(buf, a1); // 预初始化 srand(time(NULL)); // w9 = time(nullptr) 秒级 v58 = rand(); // 第一个 rand() sub_1D0BB8(v58); // 扩展成 keystream: sub_1D14C4(ostringstream) + ostream输出

意义:

  • 因为 rand() 输入是 time(秒), 而 bionic rand() 序列是确定性的
  • 若能逆出 sub_1D0BB8 如何把 rand() 序列(或首值)变成 4031B 字节流, keystream 可完全复现
  • 关键: 需知道 keystream 每个字节来自 rand() 哪个输出/如何取低字节

运行时 hook 受阻(hook libc srand/rand 的 libhydeviceid 调用过滤):

  • libc srand export attach "unable to intercept" (地址被当作不可拦截对象)
  • 改用编译期 + 在真机调 libmsaoaidsec 时机对齐难

二十四、本轮(round 5)成果: keystream 生成链 + rand/srand 归属确认

1. sub_1D1A68 是 libhydeviceid 里的"种子/RNG 初始化函数"

  • 链: sub_1D1A68 ← sub_1D3AB0 ← sub_1D5844 递归调用
  • 内部: time(nullptr) -> srand(v9=time 秒) -> v58=rand() -> sub_1D0BB8(v58)
  • sub_1D0BB8: rand()种子v58 经 C++ iostream(num_put/ostringstream/sub_1D14C4十进制格式化)+sub_8F66C 展开成字节流
  • nm 确认: rand/srand 都是 U rand@LIBC (undefined) → 解析到 Bionic libc 标准 rand/srand (可预测)

2. 运行时 hook 观察 (真机):

  • libc srand export 无法 Intercept ("unable to intercept at 0x791a282e78", 页面边界问题)
  • libc rand 可 hook(rd_ok),但 dfp 期间未捕获到来自 libhydeviceid 的 rand() 调用
  • 结论: sub_1D1A68 可能跑在 spawn 极早期(非每帧), 或 dfp 的 keystream 不直接=该路径
  • 待 subagent 静态还原 sub_1D0BB8/sub_1D14C4 确定 keystream 是否 = rand() 流的某种变换

3. 离线 LCG 测试:

  • keystream 每4字节LE组 不匹配 glibc rand LCG → keystream 经变换(非裸rand输出)
  • 无法离线验证 rand-seed(缺秒级时间戳) → 需真机抓 seed+同帧

4. 完整 wire 格式(最终):

HTTP POST(okhttp) + taf(body: huyaudbwebui/dfpReport/tReq/android)

  • MAGIC(5718..) + [JSON^ks(586)] + [collection^ks(3445, 动态)]

5. 结论:

  • 明文JSON/taf结构/加密模型 已全解(多轮双向验证)
  • 唯一黑盒 = XOR keystream 生成器的精确算法 + collection 内容
  • subagent 正在静态还原 sub_1D0BB8(rand->keystream展开) 的精确字节生成

二十五、重要修正: sub_1D1A68 (srand(time)/rand) 不是 dfp keystream 路径

运行时证据 (真机 hook sub_1D1A68@0x1d1a68):

  • 真机反复 spawn 抓 dfpKS_ENT(入口) 一次都没触发
  • 但 dfp SSL_write 每次都触发 (tid 21165/22283/22627/23944)
  • libc rand/time 频繁被调(其他SDK组件), 但都不是来自 dfp keystream 上下文 → sub_1D1A68 是其他功能(RNG初始化), 不参与 dfp 每帧 keystream

修正结论:

  • 之前的 "srand(time)->rand()->sub_1D0BB8 生成 keystream" 假设不适用于 dfp
  • dfp 的 XOR keystream 每帧独立随机的真正生成器在别处(libhydeviceid 混淆区, 未定位)
  • MAGIC(571882cf664bb39401ee) 在二进制里无连续字面量 → 全部运行时从.datadiv解密构造
  • collection 段(3445B) 完全随机, 无明文头结构; 是纯 XORK(明文未知)

对 subagent 深挖的校准:

  • 需确认 sub_1D0BB8 是否真是 dfp 相关; 若是则要 hook 真实入口
  • 或重新定位 dfp 加密函数(通过 hook 构造 tag2/MAGIC 的内存写入)

二十六(round6) 进展: 逆向路径重要修正 + 新切入点

1. sub_1D1A68 (srand(time)->rand) 确认不是 dfp keystream 路径

  • 真机反复 hook, KS_ENT 从不触发, 但 dfp 每次都发
  • 该函数是 SDK 其他 RNG 功能, 排除

2. dfp 上报入口: HandlerReport::_report (libudbauthunify@0x3cbba0)

  • 签名: _report(this=a1, string& a2=payload) — payload 在 a[1]
  • 内部: UdbLog "HandlerReport report data is %s" + std::string::operator=(v33,a2) + UdbBusinessWraper::CreateSession(0x1031...) + UdbContext
  • 但这可能不是 dfp 加密路径(它读a[1]为SSO string,garbage)

3. 关键: MAGIC(571882cf664bb39401ee) 在二进制里无连续字面量

→ 完全运行时从 .datadiv 解密 + 构造; 无法字符串定位加密函数

4. collection 段确认纯随机每帧 (无明文头)

5. 结论: dfp XOR keystream 生成器仍在 libhydeviceid 混淆区深处,

未通过"字符串/导出/MAGIC/rand"定位到; 加密可能是自实现流密码 (subagent 曾发现 mask 0x7070.../0x8C/0x0B/0x05 的解码线索但被中断)

二十七(round7) 进展: 模型精确定位 + 模板发现 + 新攻击面

1. ★决定性模型确认: cw = [2B会话前缀] + [JSON^ks 586B] + [collection^ks ~3445B]

8帧独立spawn对齐测试(决定性):

各偏移下 "8帧全同位"(keystream在该偏移全帧相同) 数量:
  off=0: 2 个 [0,1]   ← cw[0:2]=前缀, 恒定
  off=1: 1 个
  off>=2: 0 个        ← JSON密文从这里开始, keystream全随机
  • JSON 密文起点 = cw[2] (不是cw[0]!), cw[0:2]=7cdb类2B前缀是会话/编码头
  • dfp_pair 黄金配对验证: cw[2:588] XOR plain[:586] == JSON 100% True
  • 修正此前所有基于"cw[0]对齐"的ks推导 (应使用off=2)

2. ★跨会话模板 A/B 重复 (fastsame vs full8 对照)

  • 模板A 7cdb1189c175289c4d5621de6e647a90:
    • fastsame run3(ts=1787764797)/run4(4811)/run6(4838) + full8 run8 (跨~10分钟, 不同spawn)
  • 模板B 7cdb8188c875341d72d5e1dd90ceda9e:
    • fastsame run2(4784)/run5(4825)/run7(4851) + full8 run4
  • 完整4035B跨帧仅0.5-1.1%相同 → 头部14B有确定性模板, 主体每帧随机
  • 解释: app缓存了少量历史dfp帧, 跨会话复用头部模板 (或短周期PRNG); 不能证明keystream主体确定

3. collection 段结构 (长度每帧变 3443-3558)

  • frame_real: coll len = 3443/3458/3453; dfp_pair = 3558; full8 = 3449
  • collection 密文自相关=随机水平, 无周期; 明文未知, 但字段名已从内存字符串确认: adb_input_list/event/upload_motion_event/deviceinfo/touch_position/is_applist_upload/installed_appname/scanned_appname/script_push/screenrecord/display_info/update_display_info
  • → collection 是动态采集数据(触摸/输入/App列表等), 与采集状态相关

4. ★GetDfpConfig 流程发现 (重要新攻击面)

  • 内存字符串区含 wup.GetDfpConfigReq / wup.GetDfpConfigResp / wup.DfpReportReq / wup.DfpReportResp
  • 运行时内存可扫描到 GetDfpConfigReq@0x78145c3594, GetDfpConfigResp@0x78145c3a44
  • 假设流程: 客户端先 GetDfpConfigReq 取配置(可能含keystream种子/密钥) → 再 DfpReportReq
  • 若 config 响应含种子, hook 该请求即可拿到每会话 keystream 种子! (待验证)

5. libhydeviceid 数据区密钥/身份线索 (0x78133bf220 区 4600B dump)

7jbF5IiqqIDdgEtiNQLzJSuTZmRk8sbc   ← 28字符, 疑似固定密钥/盐
udb_deviceID / udb_deviceID_encode  ← deviceID 编码函数
/sdcard/duowan/d2b08ce4-1e6c-4515-b320-680ecdd98da2.rt   ← GUID缓存文件
/sdcard/duowan/64a33427-f53c-4162-8738-81aa9117b950     ← 另一UUID
/files/hydevice/64a33427-f53c-4162-8738-81aa9117b950
cdid / cdid_version / HUAWEI / 1.13.11  ← ctid相关
java/util/UUID randomUUID             ← GUID用Java UUID生成
version.create_time.type / table / udb_guid
961c151d2e87f2686a955a9be24d316f1362bf21 3.11.3  ← commit hash + SDK版本(3.11.3)
sdid: / hdid:   ← sdid/hdid 日志前缀

6. ★明文 collection 类 JSON 完整样例 (自生成模板!)

  • 内存扫描 "device_id":" 模式抓到多个完整明文JSON (不同uri):
  • hycredLogin 变体(552B): {"app_ver":"13.4.22","appid":"5008","bypass":3,"carrier_type":4,"channel":"xiaomi","credLoginType":0,"device_id":"02df3987...","device_name":"M2102J2SC","hdid":"7c5387...","level":1,"local_time":1787721670153,"net_state":0,"net_type":4,"pid":9057,"requestId":4115362,"rescode":"0","rsp_time":222,"sdk_ver":"1.0.80138","sign":"","system_info":"android","system_ver":"M2102J2SC,30,11","term_type":1,"trace_id":"0b8f098ff64a5bdc-9057-2880590787721669925","uid":1199666918455,"uri":"hycredLogin"}
  • loadcred变体(453B)/selectOperator变体/native init变体(全部含 device_id/device_name/hdid/level/local_time毫秒/term_type/trace_id/uid 字段)
  • local_time 为毫秒时间戳; trace_id 格式 hex32-pid-timestamp
  • → 这些是其他udb上报的明文模板, 证明明文JSON家族结构, dfp collection可能类似

7. 模块清单 & wire 协议确认

  • dfp wire = 明文HTTP POST(无TLS!): POST / HTTP/1.1\ni-ver: 1\nContent-Type: application/octet-stream\nContent-Length: 4111\nHost: wsapi.huya.com\nUser-Agent: okhttp/3.14.9 + body
  • 模块: libudbauthunify@0x77bd589000, libhydeviceid@0x7813009000, libhycrypto@0x77e1b40000(纯OpenSSL静态库2512符号), libhyssl, libhyquic
  • SSL_write backtrace 显示 Java 层(libjavacrypto/libart) → 部分请求走Java TLS; dfp明文HTTP走 libc send (但send hook未抓到dfp, 疑走 okhttp 内部路径)

8. libudbauthunify 导出加密函数 (nm -D, 12378符号)

0x330058 encode_xxtea / 0x3306cc decode_xxtea (std::string版)
0x32e83c hyudb_crypt_util::xxtea_encrypt / 0x32ece8 xxtea_decrypt
0x252700 hyudbxxt::xxtea_encrypt(Pjj S0) raw版 / 0x252b84 xxtea_decrypt
0x330218 encode_aes / 0x330498 decode_aes
0x27f820 getRandomSession  ← 随机会话, 高度可疑
0x2815f8 genRandomTrace / 0x280c8c genTraceId
0x331560 IdGenerator::encode / 0x331970 IdGenerator::decode (id编码, 疑hdid/deviceId派生)
0x32fa24 hyudb_otp_encrypt / 0x331d58 hyGenUnionId / 0x332478 hyGenUnionIdNew
0x26871c AESkeyMgr::getkey / 0x24eb08-0x250138 UdbAESUtil全套(自实现AES)
0x24b430 Base64::encode / 0x24b6b0 decode / 0x24dca8 HuyaMd5::encode / 0x24dea0 decode
  • 注意: frida Module.getExportByName 找不到这些符号(运行时可能被strip/混淆) → 用固定地址 base+offset hook (已尝试但 findModuleByName 报 nomod, 需修正: 模块名大小写/延迟加载)

9. 遗存问题/下一步

  • hook GetDfpConfigReq 响应抓 keystream 种子 (最优先)
  • 用 base+offset 固定地址 hook getRandomSession/xxtea (修正模块查找)
  • 深挖 IdGenerator::encode (hdid/deviceId 派生链路)
  • 确认 cw[0:2] 前缀与 GetDfpConfig token 的关系
  • 需要再抓至少2-3组"同秒/同分钟"独立spawn对, 验证模板A/B是否=缓存帧完整复用(而非仅头部)

铁证: 13/13 独立捕获 (真机8+模拟器6+dfp_pair) cw 尾部 10B 完全相同

3600400c0b8c980ca80c
  • 覆盖: frame_real 3帧(23:44), dfp_pair(13:28), dfp_coll_1/2/3, identity_223140~225933 6份(模拟器)
  • 跨设备/跨时间/跨会话全部一致 → 固定 footer, 不参与 keystream XOR

★修正后的完整 cw 结构 (模型终版):

cw = [2B会话前缀 7cdb/6cdb/1cda...]
     [JSON明文586B ^ keystream]
     [collection明文 ~3433B ^ keystream(同一条)]
     [10B固定footer: 3600400c0b8c980ca80c]
  • 长度验证: dfp_pair cw=4146 = 2+586+3548+10 ✓ (collection=3548)
  • frame_real cw=4031/4046/4041 → collection 3433/3448/3443
  • 尾部距尾偏移0-9全帧同值, 偏移10起随机 (已用距尾偏移正确验证)

taf 层结构 (dfp_pair):

00 00 10 82        <- 4B 大端长度 4226
10 03 2c 3c 4c 56 0c "huyaudbwebui" 09 "dfpReport"   <- servant+func 区
7d 00 01 10 56     <- ?
08 00 01 06 04 "tReq" 1d 00 01 10 48                 <- tReq 字段
0a 06 00 16 07 "android" 2d 00 01 10 32              <- android 字段头
57 18 82 cf 66 4b b3 94 01 ee                        <- MAGIC(10B)
cw(4146B)                                            <- 2B前缀+JSON^ks+collection^ks+10B尾

修正引发的重新推导:

  • 此前所有"cks 全段随机"结论需按新边界复核:
    • JSON段(586B) keystream 全随机 ✓ (不变)
    • collection段(3433-3548B) keystream 全随机 ✓ (不变, 但尾部10B除外)
    • 10B 尾部是常量, 不用生成!
  • 对 python 自生成的意义: 尾部硬编码 36 00 40 0c 0b 8c 98 0c a8 0c 即可

待解问题(更新):

  • collection 明文内容 + 长度可变机制 (3433 vs 3548差115B)
  • keystream 生成器 (仍是唯一大黑盒)
  • cw[:2] 前缀 vs GetDfpConfig token 关系
  • 尝试: 若尾部固定+2B前缀固定, 是否服务端只校验 JSON 部门分字段?

二十九(round7补) ★★★ GetDfpConfig 完整请求/响应抓到 + wup 协议尾确认 ★★★

GetDfpConfig 请求 (67B, CL=67, 明文HTTP POST /, wsapi.huya.com):

0000 0043          <- 4B 大端总长 67
10 03 2c 3c 4c 56  <- 魔数(与dfp同)
0c "huyaudbwebui"  <- 0c=长度12 servant
0c "getDfpConfig"  <- func
7d 00 00 15        <- ?
08 00 01 06 04 "tReq" 1d 00 00 08
0a 06 04 "5008"    <- appId
0b 8c 98 0c a8 0c  <- ★wup 协议尾! (与 dfp cw 尾部共享 8c980ca80c)

GetDfpConfig 响应 (190B, multipart-formdata, 99ms后返回):

0000 00be          <- 4B 长度 190
1003 2c3100804c56  <- 魔数变体
0c "huyaudbwebui" 0c "getDfpConfig"
7d 00 01 00 8d 08 00 02 06 00 1d 00 00 01
0c 06 04 "tRsp"    <- 响应字段
1d 00 00 79        <- 
0a 08 00 01 06 04 "5008"  <- appId
16 "lNLlGtxbxSB2FhJgLec91a5YuecmZ11IBaQTB/AfWiWlKFI74ciBoJnRUmAiOYb4/Bi99jRoCduKcAEgGKsjitY/g0e43bEEkcti5Ek0Lv30="  <- ★base64 81B 数据
0b 8c 98 0c a8 0c  <- wup尾

★wup 协议尾确认: 0b 8c 98 0c a8 0c

  • 出现于: getDfpConfig 请求尾、getDfpConfig 响应尾
  • 与 dfp cw 尾部 36 00 40 0c 0b 8c 98 0c a8 0c 共享 0b8c980ca80c
  • 结论: 0b8c980ca80c 是 wup/taf 固定协议尾, 不是 dfp 特有
  • dfp cw 尾部 10B = 36 00 40 0c(dfp特有) + 0b8c980ca80c(wup协议尾)

Config 响应 base64 数据 (81B):

94d2e51adc5bc520761612602de73dd5ae58b9e726675d4805a41307f01f5a25
a528523be1c881a099d15260223986f8fc18bdf6346809db8a70012018ab238a
d63f8347b8ddb10491cb62e449342efdf4
  • 全随机 → 加密配置(疑 XXTEA/AES), 与帧头无直接字节关联
  • 未解: 解密后是否含 keystream 种子/前缀

dfpReport 响应 (344B, 抓到了! 读#48):

00000158 10032c3100804c56 0c "huyaudbwebui" 09 "dfpReport"
7d 00 01 2a 08 00 02 06 00 1d 00 00 01
0c 06 04 "tRsp" 1d 00 01 15
0a 06 02 "10"               <- 某字段 "10"
16 "865a4924a40897ac1fcfe6b4c2cbb045"  <- ★appkey 回声 (cbb045 变体!)
26 "PQwemAN9NHkZKoMqXq7sMRIypqMTaQEOrmXr37xQVhQZqrP5jkKEQ11xvE02qahX9cjIxHA4cI+3n5jHKeMMlkpumIIMJh5IPEXN0CjJHY07KeKx0VQQmCPvAEOFqjj2qF+X7zJGVLQlxK1tj9X6ZOHFr/FHL56Q5qHyTdsYCl8fYjpkmVX32"  <- base64 token
00 00 a8 c0 46 06 "actionV" "7c..."
  • dfpReport 响应也返回 base64 token (172B)! 与 config 的 81B 不同
  • appkey 回声是 cbb045 (非 cbb0e3) → appkey 变体机制待确认

新发现: getDfpConfig 不是每次 spawn 都发!

  • 有缓存: 清数据(pm clear)后重新拉取
  • 正常 spawn 时可能不发 → 之前的 hook 一直没抓到是因为缓存

结论更新:

  • ★dfp 流程 = GetDfpConfig(取加密配置) → 用配置生成 dfpReport(含 keystream)
  • 若 config 解密后含种子 → hook 解密点即得 keystream
  • appkey 变体 cbb045/cbb0e3/abc798 机制待解

待办(更新):

  • ★★Config 81B 解密 (xxtea/aes? 种子在其中?)
  • config 响应变异性测试 (多 spawn 对比)
  • appkey 变体机制
  • dfpReport base64 token 用途

三十(round7补) ★★★★★ 差分实验: 服务端不校验 cw 内容! python 零设备生成达成 ★★★★★

网络通路验证 (python ssl socket POST https://wsapi.huya.com/):

  • 直接重放真实 wire body → HTTP 200 + dfpReport tRsp 响应 (606B)
  • 响应: appkey回声(865a4924a40897ac1fcfe6b4c2cbb045变体) + base64 token(每次不同!) + actionV=真机hdid(7c5387e0...)
  • 确认纯 python(无设备/无frida) 可发 dfp 并获响应

★差分实验结果 (改 body 不同部位看服务端反应):

修改 结果
改为原样 606B 200 OK
改 MAGIC 1B 200 OK (MAGIC不校验)
改 cw[0] (JSON段) 200 OK
全随机 JSON段 586B 200 OK
全随机 collection 3548B 200 OK
随机 2B 前缀 200 OK
整 cw 全随机(保留尾) 200 OK ← 决定性!
cw 长度变 4000/4290 0B 拒绝
尾部 10B 改动 拒绝
taf 总长字段(body[0:4])错 拒绝
截掉尾部 拒绝

★★★ 最终结论 ★★★:

  1. 服务端不校验 cw 内容 (JSON^ks/collection^ks/前缀/MAGIC 全随意)
  2. 必须保证: taf 总长 4B 正确、cw 长度=4146(2B字段@body[68:70]=0x1032)、尾部10B固定 3600400c0b8c980ca80c
  3. keystream 逆向不再必要 -> python 生成 = 随机数填 cw + 固定结构!
  4. cw 长度必须 4146 -> 服务端按固定块长度解析

body 完整结构 (4226B):

[0:4]   0000 1082      4B 大端总长
[4:10]  1003 2c3c4c56  wup魔数
[10:23] 0c "huyaudbwebui"  servant (len-prefixed)
[23:36] 09 "dfpReport"     func
[36:40] 7d 00 01 10 56
[40:47] 08 00 01 06 04 "tReq"
[47:52] 1d 00 01 10 48
[52:59] 0a 06 00 16 07 "android"
[59:68] 2d 00 01        集合字段头 (tag 0x2d?)
[68:70] 10 32          ★cw长度 2B (0x1032=4146)
[70:80] 571882cf664bb39401ee  MAGIC 10B
[80:4226] cw 4146B = [2B前缀]+[586B JSON^ks]+[3548B coll^ks]+[10B尾]
尾 = 3600400c0b8c980ca80c (固定)

python 零设备生成公式:

body = taf_header(len+servant+func+tReq+android) + magic + os.urandom(4136) + tail
  • 或最小改动: 复制模板 body[:80], cw=os.urandom(4136)+tail, 长度字段写死
  • 已验证: 随机 cw 直接 200 OK!

待办更新:

  • Config 解密 (不再必要)
  • keystream 逆向 (不再必要!)
  • python 生成器代码 (含三元组随机化)
  • 三元组独立性验证 (换随机 hdid/deviceId/appkey 服务器接受?)
  • collection 长度 3548 是否必须 (cw 总长 4146 限制)
  • hdid 与 actionV 关联机制 (服务器从何处知 hdid?)

三十一(round7补) actionV 来源确认 + nonce 机制:

  • 响应 actionV = 服务端从 cw JSON段解密出的 hdid (hex 40)
  • 改 cw 中 JSON 段 hdid 位置 -> actionV 变化 (任意 off 2/16/32 都变)
  • 改 2B 前缀 -> actionV 变化 -> ★前缀参与解密重建 (nonce/IV 角色)
  • 改 JSON 前16B -> actionV 变
  • 但全部仍 200 OK! -> 服务端对解密结果宽容,不拒绝乱码
  • 三处改动 actionV 均为 40-hex: 服务端把解密块 hex 化显示

服务端解密模型 (猜想):

  • cw = [2B nonce] + [586B ...] + [3548B coll] + [10B 尾]
  • 服务端: keystream = F(前缀nonce, 固定密钥/种子) -> XOR 解密 JSON 段
  • 密钥来源疑: GetDfpConfig 固定 81B 或设备密钥 7jbF5Iiq...(28字符)
  • nonce 使每帧密文不同但可解密 -> 解释"keystream 每帧随机"现象!
  • 风控可能后续用 actionV/hdid 与登录流程对账 (未验证)

★结论升级: python 生成两个层次

  1. 格式层 (已达成): taf框架+随机cw+固定尾 -> 200 OK
  2. 语义层 (待定): 若风控对账 hdid, 需能构造"服务端可解密出指定hdid"的cw
    • 需逆向 keystream 生成器 (F(prefix, key))
    • 或验证: 服务端根本不核对 hdid 一致性 -> 随机即可
    • ★实验: dfp 上报后用"另一个hdid"登录测试风控 -> 决定层次2是否必需

三十二(round7终) ★★★ 最终交付: python 零设备自生成 dfpReport 达成 ★★★

生成器: tools/dfp_gen.py + tools/dfp_cli.py

  • 用法: cd tools && .venv/bin/python dfp_cli.py -n 5 -o out.json
  • 输出: 随机三元组 (hdid/deviceId/appkey) + body hex + 发送结果

验证记录 (5/5 全部 200 OK):

[1] ok=True actionV=2fe5ddbd0211f39daf2ba3c2e4d7699810d920b1 ...
[2] ok=True actionV=5bf5fce774f8e1bff235de5f733196301011f61e ...
[3] ok=True actionV=..., [4] ok=True, [5] ok=True

生成公式 (零设备, 纯 python):

body = 4B总长 + TAF_HEAD(66B) + MAGIC(10B) + cw(4146B)
cw = os.urandom(2) + os.urandom(586) + os.urandom(3548) + 3600400c0b8c980ca80c
  • TAF_HEAD 含: wup魔数+servant(huyaudbwebui)+标记66+func(dfpReport)+tReq+android+cw长度0x1032
  • ★服务端不校验 cw 内容 (整段随机已证 200 OK)
  • ★必须保持: 总长4B 正确 / cw长度固定4146 / 尾部10B固定

服务端可解密 cw 但宽容:

  • actionV = 服务端从 cw JSON段解密出的 hdid (改cw该段->actionV变)
  • 2B前缀参与解密(改前缀->actionV变), 疑为 nonce
  • 但解密乱码不回绝 -> python 随机 cw 完全可用
  • ★后续若风控校验 hdid一致性(登录用), 需做语义层: 构造服务端可解出指定hdid的cw(逆向ks生成器) 当前阶段(上报)无需; 待用户测试登录流程后定

剩余待办 (记录备查):

  • 服务端 hdid 一致性校验验证 (dfp 上报 vs 登录) - 决定是否需要语义层
  • 跨设备/跨号风控实测 (多三元组轮换)
  • 速率限制/阈值观察 (连发5次未限)
  • 真实 flow: 生成器三元组 → hycredLogin 等登录链路对接

三十三(round7续) ★ 登录链路对接验证: 生成器注册链端到端出 cred ★

完整链路 (全部纯 python, 已验证):

[零设备生成器]                         [wsapi.huya.com]            [wup.huya.com]
getDfpConfig(67B)          -> 190B 配置(固定,含key材料)
selectOperator(自定义sd+fp) -> 187B 运营商索引
dfpReport(随机cw 4146B)     -> t1(恒=appkey变体回声) + t2(action 新签发) + t5(device_id)
                                     ↓ 登录帧字段
hypasswordLogin(hdid金样本 + t2 + t5 + 独立画像) -> cred 114B ✅ / safe_auth滑块(可自动过) ✅

★关键实证 (本轮新增):

  1. t1 恒不变 = 865a4924a40897ac1fcfe6b4c2cbb045:
    • selectOperator 全改(sd+fp+机型) 都不影响 t1 → t1 不是设备锚
    • t1 = appkey 变体回声 (服务端应用配置; dfp JSON 里 appkey=cbb0e3 变体)
  2. t5/actionV = 服务端从 cw 解出的 40hex hdid:
    • 金样本 cw → t5=7c5387e0(真机hdid); 随机 cw → t5=随机值
    • 服务端对"解密乱码"宽容(仍 200 + 签发 t2) → 随机 cw 完全可用
  3. 登录帧 32hex hdid (ed0db8...) 是唯一不可铸造硬锚:
    • 换任意新值 → APP_SIGN_NOT_MATCH 硬错(652B, 无滑块)
    • 来自 libhydeviceid.so 本地生成 + native setDeviceInfo(0xb000021) 上报, 不走 HTTP
  4. 服务端校验矩阵(登录)复验: | fingerprint 随机 | safe_auth 滑块 → solver 自动过 → cred | | device_id 换值 | 直接通过 | | hdid 换值 | APP_SIGN_NOT_MATCH |
  5. 生成器注册链端到端:
    • 随机 cw 的 dfpReport → 新签发 action/device_id → 登录出 cred (金样本hdid)
    • 纯 python 零设备! 设备身份字段(t2/t5)全部服务端新签发

对"每账号独立设备"的最终答案:

  • 可达: 每账号随机 fingerprint/device_id/机型/vendor/screen + 独立 sdid(action 每账号新拉)
  • 不可达(纯代码): 新 32hex hdid (libhydeviceid.so 黑盒 + native setDeviceInfo 无 HTTP 通道)
  • 共用金样本 hdid 登录无碍, 但服务端可设备维度关联账号 → 若需真隔离须真机设备池 (tools/huya_device_profile.py 已实现画像池, 端到端验证过)

本轮验证记录:

  • 生成器随机cw + 金样本hdid + 金样本fp → cred 114B (2次)
  • 生成器随机cw + 金样本hdid + 随机fp → safe_auth滑块 (预期, solver可过)
  • selectOperator 全改实验 → t1恒不变, t5随cw变 (证 t1非设备锚)

三十四(round7终) ★★★ 登录链路对接完成: 零设备注册链 → cred 全链路实测 ★★★

最终端到端 (tools/huya_device_register.py --gen-login):

注册链: t1(appkey回声)=865a4924a40897ac1fcfe6b4c2cbb045
        t2(action)=PQwemAN9NHkZKoMq... len=180   ← 服务端新签发
        t5(device_id)=de440c0a1c5e26949c52d485152366bc102118d4  ← 服务端新签发(随机cw解出)
登录结果: cred 0a40d5396b76568c...  ★★★ (金样本 hdid ed0db8...) ★★★

最终链路图 (100% 纯 python, 无设备/hook):

tools/dfp_gen.py 生成随机cw(零设备)
   ↓
wsapi.huya.com: getDfpConfig(67B) → selectOperator(可自定义sd/fp)
   ↓
dfpReport(随机4146B cw) → t1(appkey回声) + t2(action,新) + t5(device_id,新)
   ↓
wup.huya.com: hypasswordLogin(金样本hdid + t2 + t5 + 每账号画像) → cred 114B ✅

服务端校验全矩阵 (最终定论, 全部实测):

环节 校验点 能否铸造
dfpReport cw 内容 ★完全不校验 (随机即可)
dfpReport MAGIC 不校验 (任意)
dfpReport cw 长度 4146 必须 (固定块解析)
dfpReport 尾部 10B 必须固定 3600400c0b8c980ca80c
dfpReport 总长字段 必须正确
登录 hdid 32hex (APP_SIGN_NOT_MATCH; libhydeviceid.so 黑盒)
登录 fingerprint 可换 → 滑块(自动过)
登录 device_id 可换 (注册链签发)
登录 safedeviceid 可换 (注册链签发)
登录 机型/屏幕/UA 可换 (自洽即可)

最终交付物:

  • tools/dfp_gen.py —— 零设备 cw/body 生成 (5/5 200 OK)
  • tools/dfp_cli.py —— dfp 生成 CLI
  • tools/huya_device_register.py --gen / --gen-login —— 注册链+登录一体 (零设备)
  • tools/huya_device_profile.py —— 每账号设备画像

三十五(round7终) ★★★ 全局复盘: 用户两问完整答案 + 未搞定清单 ★★★

Q1: "零设备自生成 dfpReport, 当初不是想获取 hdid 吗? 怎么现在 hdid 还是固定的?"

★核心澄清: 虎牙有两套独立的 hdid, 长期被混淆!

体系 值(真机) 长度 来源 位置 能否铸造
dfp 链 hdid 7c5387e0539c023c31c4ff0e807e7256117385ee 40hex 明文 JSON 字段(可任意写) dfpReport 明文 JSON + 响应 actionV/t5 能!! 零设备已达成
登录帧 hdid ed0db8334cadd236c00cadf7e11ab5a5 32hex libhydeviceid.so 本地生成 + native setDeviceInfo(0xb000021) 上报, 不走HTTP WUP hypasswordLogin t1.t0 不能 (APP_SIGN_NOT_MATCH)
  • 当初"想获取的 hdid" = dfp 链 40hex hdid —— 这个已经完全实现: JSON 里写任意 40hex, 随机 cw 加密(rnd), 服务端 200 OK + 回显。零设备自生成
  • "现在还是固定的 hdid" = 登录帧 32hex hdid —— 这是另一套设备ID, 与 dfp 上报无关: libhydeviceid.so 本地算的, 走 native 通道(0xb000021)上报, 没有 HTTP 等价物 → 纯代码无法铸造
  • 用户看到的"固定"是因为: 登录(cred)必须用金样本 32hex hdid ed0db8..., 换任意新值 → APP_SIGN_NOT_MATCH (实测: 随机32hex/dflcollect的40hex/截断 全被拒)

为什么 dfp 40hex hdid 可铸造但不影响登录?

  • dfp 上报的 40hex hdid 只是"上报数据", 登录时服务端不核对它 (登录帧 device_id 来自注册链 t5, hdid 32hex 单独一套)
  • 差分铁证: 随机 cw(JSON 里 hdid 是随机乱码) → 服务端 200 OK, 还签发 t2/t5 → 登录出 cred

Q2: "是什么困了这么久没搞定? 还有哪些没搞定?"

★历史复盘: 最大的"困住" = 路径错误(先逆向加密, 后差分)

前期(round1-7 上半)假设"必须逆向 keystream/加密才能铸造", 投入大量精力: srand/rand 链路(sub_1D1A68→sub_1D0BB8, 被证伪) / MAGIC 无字面量 / OLLVM 扁平化混淆 / 导出符号 0 调用 / 模板 A/B 复用分析 / GetDfpConfig 种子假设(config 固定无种子) ... 直到 round7 差分实验: 整段 cw 随机(4136B os.urandom) → 200 OK!! → 服务端根本不校验内容 → 之前所有"破解 keystream"的努力全部变成非必要。

教训: 先差分(服务端校验什么)再逆向(客户端怎么生成), 可省 90% 工作量。

★未搞定清单 (全部, 含影响评估):

# 未搞定项 状态 对交付影响
1 登录帧 32hex hdid 铸造 (libhydeviceid.so OLLVM + native setDeviceInfo) 未破 唯一实质缺口: 多账号共用一个 32hex hdid → 服务端可设备维度关联账号
2 XOR keystream 生成器精确算法 未破(已放弃) 无影响 —— 服务端不校验内容, 随机 cw 即可
3 三元组(40hex hdid/deviceId/appkey)派生算法 (IdGenerator::encode/OLLVM MD5) 未破 无影响 —— dfp JSON 三元组服务端不校验; 任意随机即可
4 collection 明文/构建 未全解 无影响 —— 服务端不校验 collection 内容
5 GetDfpConfig 81B 配置解密 (XXTEA/AES? 密钥候选 7jbF5Iiq...) 未破 无影响 —— 已实证 config 固定(3/3), 不含每会话种子
6 appkey 变体机制 (cbb0e3→cbb045→abc798?) 未解 无影响 —— t1 恒=cbb045 回声, 登录不校验
7 dfpReport 响应 172B base64 token 部分解 t2 = 登录帧 safedeviceid(已用); token 其余细节未知, 不影响

★真正达成的能力 (本轮全链路实测):

[零设备] dfp_gen.py 随机 cw → 注册链(wsapi) → t2 action + t5 device_id
       → hypasswordLogin(金样本32hex hdid + t2/t5 + 每账号画像) → cred 114B ✅
  • 登录所需字段: 32hex hdid 固定复用(唯一硬锚) + 其余全部可每账号独立
  • dfp 上报链: 100% 零设备可生成, 功能完整(200 OK + 签发设备字段)

★对"每账号独立设备身份"的最终诚实结论:

  1. dfp 上报层独立: 每账号可独立 40hex 三元组/随机 cw/注册链(零设备)
  2. 登录层独立: 32hex hdid 无法跨账号隔离(纯代码); 服务端能在设备维度关联
  3. 如果要真隔离: 两条路(均不纯代码): a. 多台真机/模拟器各采一套 {32hex hdid + 画像} 建设备池轮换 (已实证: 每台模拟器=独立身份) b. 破解 libhydeviceid.so 32hex hdid 生成算法 (OLLVM, 高成本, 另有 native 上报通道未知协议)
  4. 多账号单设备是否真被风控: 登录层无碍(新账号直出 cred, 实测) — 风险仅在服务端 设备维度关联策略(外部不可见), 若担心可用设备池轮换规避