# hyCred 签发协议 — 侦察笔记(2026-08-25 傍晚) > 目标:任意账号取得有效 hyCred(114B),配合 tools/cert_forge 实现全自动多账号。 > 当前状态:**铸证已闭环**(见《虎牙证书算法闭环-差分实证.md》),本文记录签发链侦察结果。 ## 已确认事实 1. **cred 是长寿命凭证**:早上抓的 hyCred 在下午(且中间用户重新登录过一次后) 依然能通过 bind 铸证(实验C)。=> 每账号只需采集一次,非会话级短时效。 2. **cred 每次登录轮换值但旧的仍有效**:今晨 `0a80a762...` ≠ 午后 `0a50ec12...`,两者均可用。 3. **密码登录响应不含 cred**:`evidence/wup_response_login.bin`(1514B) 无 114B/152b64 结构。 4. **cred 存于本地登录态文件**:`BusinessCfg::saveLoginData` (@0x265fb0 / 0x265a6c) -> JSON(含"cred"字段) -> UdbAESUtil AES 加密 -> `UdbFileUtil::writeFileEx` 写盘。 `BusinessCfg::getCred(uid)` @0x265524 纯本地读(UdbLock+日志),无网络。 5. **签发走 cdn.wup.huya.com 长连接隧道**:HAR 里只有 CONNECT,载荷不可见; 相关消息族符号齐全: - `MsgRequestLgnCred` / `MsgResponseLgnCred`(请求/响应) - `HandlerRequestLoginCred`(@0x3a1820) / `HandlerResponseLoginCred`(@0x3a182c) - 已知方法: `handlerCredLoginOverTimeMsg` @0x3a00d0 - `UdbObjCreator_Msg{Request,Response}LgnCred`、`AppLgnCredentialLoginReq/Resp`(wup JCE) - `MsgGetCred`/`HandlerGetCred`、Anony 变体、`MsgResponseUpdateCred` 6. **dckey/check ≠ hyCred**:udbdf.huya.com/dckey/check 返回设备钥 dckey (固定前缀 AAAAAMC1eP4iV43WYoI57ZOu0),与账号 cred 不同物。 ## 本次抓取(evidence/cred_issue_trace.json) 用户重登时捕获 saveLoginData bean 内存头: `..."hy_300023887"...len前缀"1199666914671"..."4C5fozan"...` (bean 为 BusBeansLoginData 结构体,cred 字段在更深偏移,待下次 dump 全量) vtable 探测结论:slot2/3 是析构/通用槽(打中 MsgLoop::doMSG/sendMessage), 真正 onHandler 未命中 —— 下次应挂 `ResponseMsgHandler` 派生 onHandler 或 直接挂 `hyudb_packet_util::cred_pack/cred_unpack`(@0x333170) 收发两端。 ## 三条候选路线(按性价比) | 路线 | 做法 | 成本 | 备注 | |---|---|---|---| | A 混合采集(立即可用) | 手机登目标号一次,Frida 自动抓 cred 入库;此后该号永久全自动铸证 | 低 | 已验证可行,脚本齐备 | | B wsapi 短连接复刻 | 若 MsgRequestLgnCred 可经 huyaudbwebui servant 走 wsapi.huya.com HTTPS | 中 | 需 msgId+JCE 结构+OTP加密链(已有) 组包试验 | | C 长连接完整复刻 | 实现 cdn.wup.huya.com WSP 握手/心跳/加密隧道 | 高 | 最后手段 | ## 下次继续的抓手 1. 重跑 hook_cred_issue.py 增强版:saveLD_bean dump 640B 定位 cred 字段偏移 + backtrace 10 帧(找推送来源 handler)+ 挂 `cred_unpack`@0x333170 抓网络侧明文。 2. 静态:`MsgRequestLgnCred::getClassName`@0x2a37d0 / 各消息 getClassName 取类名串, 在 rodata 找 类名→msgId 表(参考 MsgGetH5InfoEx=184549387 的命名规律)。 3. 若走 B:用 tools/huya_wup_encoder.py 的信封 + OTP 加密链组 RequestLgnCred 发 wsapi 试探。 ## ★ 2026-08-25 晚 · 关键突破:cred 来源定位 增强 hook(bean 768B 全量 + backtrace)在用户重登时抓到决定性调用链: ``` BusinessCfg::saveLoginData ← _Z13saveLoginDataiR17BusBeansLoginData ← HandlerResponseLoginPassport ★ ← udbjce::JceInputStream (wup/JCE 反序列化) ``` - **cred 随"护照登录"响应一次性下发**(BusBeansLoginData 内),不是单独消息。 - 手机 App 的登录主通道是长连接上的 LoginPassport;我们的 HTTPS `/open/hy/passwordLogin` 是简化通道,响应(1514B)只有 uid/name/mask/token, 无 cred(已二次扫描确认:无 `0a 80` 高熵段、无 114B 结构)。 - bean 内存可见字段样例: "hy_300023887"、uid串、"01******7524"掩码、 user_action JSON 片段 —— 与 BusBeansLoginData 字段对应。 - 证据: /tmp/credissue2.log + evidence/cred_issue_trace.json(第二次覆盖保存) ### 路线修正 - 路线 B 修正为「补全 HTTPS passwordLogin 的请求字段」:App 端若也存在 HTTPS 登录路径,其请求参数比我们的最小实现多 ⇒ 响应可能就含 cred。 下一步:静态对比 HandlerRequestLoginPassport 组包 vs huya_wup_encoder 差异; 或 mitmproxy 抓一次 App 真实密码登录流量对照。 - 若确认必须长连接 → 路线 C(WSP 客户端)或路线 A(混合采集)兜底。 ## ★ B1 静态第一铲(rodata 字符串集群 @0x1c36db) 登录态 JSON 字段名: appId / sdkVer / cred / timestamp / biztoken_vec 新发现 24 字符密钥: ViDjMAKmkzvYEUFmrMdk9YPd (@0x1c3704, 疑似登录态文件AES钥或密钥表第4条) 消息类名串: MsgRequestLgnCred(@0x1c385a) / hyAppOtpLogin / LgnOtpLogin 下一步: 反汇编 BusBeansRequestLoginPassport::toString(JsonUtil)@0x2a6118 提取请求全字段, 对照 huya_wup_encoder 补齐 → 试发 HTTPS 端点看响应是否带 cred。 ## ★ BusBeansLoginData 完整字段表(B1 第二铲) toString 委托链: LoginData(0x26ca64) -> BusBeansAppLoginData::toString(0x26d518) AppLoginData 字段(JCE 序列): regOrigin userIdState uid hyid passport cred cookie mobileMask emailMask userId token timestamp subUid hyOpenId isHuya status biztoken_vec thirdParams => 护照登录响应一次含 cred/cookie(biztoken)/token; 铸证所需全在响应内。 剩余: RequestLoginPassport 六字段的 JCE tag 号 + 传输通道(wsapi or 长连接)。 ## ★ Round2: 载荷格式=JSON(非二进制JCE)! UdbRequestMsg::unPackageMsg(@0x2a57cc) = UdbContext::unPackageContext(str) -> JsonUtil::loadFromString => 消息上下文为JSON字符串, 字段名即线上的键: 请求: {name,password,userAction,isAuthLogin,lgnExtParam,bizAppids} 响应bean: {regOrigin,...,cred,cookie,token,...,biztoken_vec} 剩余: msgId定位 + servant/endpoint映射(huyaudbwebui?) + 密码格式(sha1?) ## Round3: msgId 线索 类名串引用点在 UdbObjCreator_Msg*::C2 注册构造器内: - "MsgRequestLoginPassport" 邻近 movz #0x1001 - "MsgResponseLoginPassport" 邻近 movz #0x2001 => 疑似 (方向类型<<12)|序号 或注册type常量; 完整id需反汇编 UdbObjCreator_MsgInit::C2 的 registerMsg(id,className,creator) 调用序列。 已知参照: MsgGetH5InfoEx=184549387(0x0B00000B)。 ## Round4: 注册常量=0x20 Request/Response LoginPassport 两个 Creator::C2 均以 movz w0,#0x20 构造 (UdbClassFactory), 地址 0x289b28/0x289c2c。0x20 与证书 key_idx 字节(0x20)同值! => 猜测: 0x20 是该消息族的类型号, 线上完整 msgId 需结合工厂注册表; 或 key_idx 本就是消息族类型号的镜像(服务端按它选处理分支/密钥)。 捷径备选: 设备hook UdbMsgHandler::sendMessage 抓真实线上id(需用户重登一次)。 ## ★ Round5: msgId 实锤 @0x289b48: mov w1,#0x1001; x2="MsgRequestLoginPassport"; bl UdbMsgFactory::RegisterMsg(uint, char const*) => MsgRequestLoginPassport id=0x1001(4097), Response=0x2001。 (前轮0x20为operator new大小, 红鲱鱼) 剩余: 传输端点+servant、password编码、活体发送测试。 ## Round6: 传输通道确认 servant 字符串 huyaudbwebui 与 Handler 族类名同池出现在 libudbauthunify.so (另见 wupudbrequest_v0 / KFWAH2cbkkNgFAgQPzriFPcU 等邻串)。 => MsgRequestLoginPassport(0x1001) 经 huyaudbwebui servant 走 WUP; dfp 的 func 名不在本库(getDfpConfig 计数=0), func 名或由Java层/运行时拼接。 下一步(需用户重登一次): hook UdbMsgHandler::sendMessage 抓真实线上包 (id/servant/func/JSON载荷), 一次拿全所有待定参数。 ## Round7: sendMessage(0x1001) 实锤触发! hook UdbMsgHandler::sendMessage 双重载: 登录时 id=0x1001 出现2次 (另有 0x0B000003/0x21/0x25 长连接族 + 0x102b/0x1072 等)。 wire id 双体系: 0x0Bxxxxxx=业务推送族; udb请求用裸id(0x1001...)。 payload读取bug: 参数是char*非std::string, 下轮改readCString重抓即得完整JSON包。 证据: evidence/cred_issue_trace.json (sendmsg:260 domsg:17) ## ★★ Round8: LoginPassport 完整请求包到手! sendMessage(0x1001, json) 载荷(char*直读): {"bizAppids":[],"isAuthLogin":false,"lgnExtParam":{}, "name":"hy_300023887","password":"","userAction":"{...}"} - password=SHA1 hex(40) 与 tools/huya_password_sha1 一致 - 六字段与静态预测完全吻合; 空对象/空数组原样传 剩余: 响应侧(doMSG a2偏移待修, cred响应未读到) + 信封包装层。 证据: evidence/cred_issue_trace.json ## Round9: bean为指针对象, 改挂getCred出口 saveLoginData bean(1536B dump)=C++对象, cred在堆上经指针引用, 非内联。 最直接抓点: BusinessCfg::getCred(ulong,string&out)@0x265524 -> onLeave 读 out(x2) std::string 即152字符hyCred原文。 scripts/hook_beandump.py 已建(时序模板可复用), 下轮改钩子地址重跑一次登录即得。 另: 响应JSON键已确认含"cred"键(0x1c36e8)。 ## ★ 终局验证(全链路) 活体采集(桥调getCred,零重登) -> hyCred(CiAXHUiF.../114B) -> cert_forge 铸新证书 -> bindQrLoginUser stage=2 -> tryQrLogin uid=1199666914671 + biztoken344B ✅ 多账号流程定型: 每号设备登录一次+桥采cred入库, 此后永久全自动。 纯Python长连接签发客户端(0x1001+JSON)留作后续可选优化, 参数已全部备齐。 ## ★ 纯Python路线启动(用户需求: 密码进cookie出, 零手动) wsapi信封实测(getDfpConfig 67B): [len][10 03][,LV]["huyaudbwebui"]["getDfpConfig"][}][params] rodata: wupudbrequest_v0 与 huyaudbwebui 同池 => 任意udb消息HTTPS透传func名。 实施: tools/huya_passport_client.py step1 复刻信封(huya_wup_encoder同构) step2 内层帧: id=0x1001+JSON, 盲发试错读错误码定位格式 step3 响应解JSON取cred -> cert_forge -> bind -> cookie ## 纯Python路线·传输层修正 登录6.har实证: 护照登录走 CONNECT https://wup.huya.com TLS隧道(代理不可见), wsapi/wupudbrequest_v0 仅用于dfp等短连流程。 内层帧突破口: createWupPackage(wup::UniPacket)@0x274ea4 - 函数体内引用 wupudbrequest_v0 与 huyaudbwebui 下步: 反汇编该函数+其调用链(经PLT), 提取长连接帧格式(msgId+JSON的JCE摆放), 然后 Python直连 wup.huya.com:443 组 TAF UniPacket 发送取cred。 ## 新目标R1: createWupPackage签名破解 _Z16createWupPackageP...PKci @0x274ea4 (0x610B): args=(UniPacket* pkt, const char* payload, int id) pkt+0x0=u16 3, +0x8=id(w8<-arg), +0xc0=u16 3; func硬编码"wupudbrequest_v0"; payload=x1。 => 对应TAF RequestPacket{iVer,cPktType,iReqId,servant,func,sBuffer,...} 下步: 找PLT调用点看运行时实参(payload格式/id值), 复刻帧->Python直连wup.huya.com:443。 ## ★★★ 纯Python护照登录打通! build_password_login_wup(前会话复刻包) POST https://wup.huya.com body=原始TAF字节(非b64!), CT=application/multipart-formdata, Dalvik UA → HTTP200 588B结构化TAF响应(huyaudbwebui/hypasswordLogin回显)! 假密码测试通过=帧格式实锤。下步: 解响应取rc/cred字段,真密码登录取cred->铸证->cookie。 ## R2: 响应解析+签名缺口定位 TAF响应解析成功: _wup_data/_wup_header 双map, JSON上下文回显 {id:4097(=0x1001!), associationId:8193(0x2001), session, traceId, step:1} 错误码: APP_SIGN_NOT_MATCH "APP签名不匹配" + SIGNAL_SERVICE_RET。 签名密钥候选: KFWAH2cbkkNgFAgQPzriFPcU (24字符) 注册于AESkeyMgr::C2@0x26fcc8! (与证书钥4VYc/xXED/3FMH同管理体系,疑为请求签名专用钥) 另发现 ybEWdvkjGjPQa2ugireHJCLL 邻串。 下步: 反汇编AESkeyMgr::C2取全部钥表+id; 结合登录时bigaes钩子事件复刻sign算法。 注: 发包body=raw TAF字节(b64是旧文档误导); HTTP200帧已通。 ## R3: 登录响应拿到真实数据! 金样本逐字节复刻(仅差1B)重发 -> HTTP200 1571B! 响应含: uid=1199666914671, hy_300023887, mobileMask=001******7524, b64url长串(AQA1Ie6L1o...疑似token/biztoken), uc_states。 APP_SIGN_NOT_MATCH消失(此前系字段值不全所致)。 无明文"cred"键 -> 字段为JCE二进制嵌套, 下轮完整解析_wup_data映射取cred。 ## R4: 响应bean结构初解 _wup_data 响应map解析(部分): tag0=INT uid(1199666914671), tag1=INT, tag2=name, tag3=BYTES, tag7=INT... 解析器在type=0xe(LIST)处断掉。 原始流中含b64url长串(AQA1Ie6L1o...)即token/cookie形态字段。 下步: 补全taf_dump的0xe/嵌套处理, 完整映射BusBeansAppLoginData取cred。 ## R5: 响应sBuffer精确定位 响应外层map: key"_wup_data"(06 09..) -> val head 1d 00, 长度JCE优化编码 (01 04 bf => 实际915B, taf_dump已证实len=915)。bean体起于 0a 0a(tag0嵌套struct) 与请求同构。旧dump在该bean的tag5(LIST元素为struct)处解析器断掉。 下步: 直接用core.huya.taf_protocol切出915B slice完整递归解析, 按BusBeansAppLoginData字段表(regOrigin..cred@?)对号取cred。 ## R6 ★★★ 响应bean完整解析 - cred字段定位! BusBeansAppLoginData响应结构(纯Python解析): t0{头:ver/meta/timestamp}, t1{业务bean}: t0=i64 uid(1199666914671) t3=BYTES[114] 起始0a50 => hyCred本体! t5子struct: t3=STR4[344]"AQA1Ie6L1o..."(biztoken/cookie) mobileMask/uc_states齐全 => 纯Python链路全通: 密码->TAF包->wup.huya.com->cred+token! 下轮: 精确切片cred(处理长度前缀) -> cert_forge -> 活体bind验证 -> 目标完结。 ## R7: cred切片偏差 - 待锚点校正 候选114B起始'72 0a50...'多出1字节; 去首后113B≠114 断言失败, 铸证bind被拒(uid=None,符合预期)。字符串锚点(STR4[344]/mobileMask/uc_states) 在t5子struct解析完全对齐 => 用它们回推t3 bytes精确边界。 注: 本次登录用假密码+复刻session, 服务端仍返回完整bean(会话级缓存/重放容忍), cred有效性需真密码登录后二次验证。 ## R8 收尾状态 t3字段精确解码: head 3d 00(INT8 elem) 00 72(INT8 len=114) -> 值114B 起始 72 0a 50... 与桥采cred格式(直接0a20...)不同 => 此字段或为包装/加密态cred, 或因假密码+复刻session返回的是降级数据。真伪需真密码登录实测。 已完成(全部验证): 密码SHA1->TAF组包->POST wup.huya.com->HTTP200->bean解析-> uid/token/mobileMask提取。待办: 真密码登录取新鲜cred->cert_forge->bind验证cookie。 工具就绪: huya_wup_encoder.build_password_login_wup + 本笔记响应解析器代码。 ## 真密码实测(R8末) hy_300023887/aa778899 SHA1=金样本哈希(同一账号历史登录)。 真密码登录: HTTP200 1571B, 响应含新鲜114B字段(0a40开头, 每次登录变化)。 铸证bind -> 40020 CERT_BIZERR_CREATE_ERROR 被拒。 => 该t3 bytes[114]与桥采cred格式有差异(桥采0a20开头), 或非plain cred (可能为加密态/hyid), 需对照BusBeansAppLoginData精确tag定位真cred字段。 纯Python链路已通到最后一环; 差异点已锁定在响应bean的cred字段识别。 ## 终验结论(本轮) 真密码登录返回的t3 bytes[114](0a40开头)铸证被拒(40020), 新旧nonce均拒。 对照桥采有效cred(0a20开头,bind通过): 该字段非plain cred。 可能性: ①响应中cred经会话钥加密(需解密) ②该字段为hyid/其他 ③真cred在bean其他位置(t2 bytes[162]? t4 bytes[177]?)。 下一步(精确): 设备登录时hook HandlerResponseLoginPassport::onHandler+ saveLoginData, 对照同账号纯Python响应字节, 直接对出cred真实位置/形态。 全部传输+组包+解析代码已就绪且验证通过。 ## 对照实验定论 设备当前cred(0a30开头,bind有效)不在纯Python登录响应中 => 每次登录铸新cred, 响应t3即新鲜cred本体。但新鲜cred铸证bind仍40020。 关键观察: cred第二字节 0x20/0x30/0x40/0x50 轮换 -- 与证书key_idx同域! => cred内部含钥代次信息, 新鲜cred或绑定登录会话上下文(需配套session/sdid), 或响应态与存储态之间有一步转换(saveLoginData前?)。 下一步: 设备登录时hook saveLoginData入参bean与getCred出口做同会话字节对照, 精确定位 响应t3 -> 存储cred 的转换函数。 ## 关键发现: 钥代次轮换 + 响应cred非直接可用 cred字节1轮换: 0x10/0x20/0x30/0x40/0x50/0x70/0x80(观测值)=代次计数器。 干净流水线(登录->新nonce铸证->立即bind)仍40020。 对照: 桥采cred(0a20,设备会话内)始终有效。 => 密码登录响应中的cred与getCred返回的cert-cred是两态: 登录后app还有后续步骤(getAppComomData等)才得到可用cert-cred, 或响应cred需会话上下文解密/兑换。 下一步: hook getCred的调用链回溯(谁写入该值), 或登录后逐func试调 (hyanonymousCredlogin/getAppComomData)观察cred0文件变化。 ## ★★★★ 终局: 纯Python全链路贯通(零设备)! 完整闭环实测通过: 密码aa778899 -> SHA1 -> build_password_login_wup(hypasswordLogin) -> POST wup.huya.com(raw TAF) -> HTTP200 -> 响应t3=新鲜cred(114B,0a开头,每次登录轮换) -> cert_forge(新nonce+key_idx0x20) -> 证书194B -> bindQrLoginUser -> ok:true uid=1199666914671 biztoken344B ✅ 关键教训: 40020拒绝系旧wupData信封过期(session/nonce TTL), 与cred无关! 信封需新鲜抓取(keycap钩子桥触发即可)。 多账号运营: 账号密码即全部所需, 全自动出cookie。 ## 新账号safe_auth定性: 可自动过的滑块! App渠道(WUP)对新账号返回 pt_auth.html(aq.huya.com,param=加密token), 主chunk解析证实走 udbrtt.huya.com + slide滑块(slideBlock*字段) = 与 core/huya/verification/solver.py 已实现的 udbrtt get3->verify3 闭环同源! (web渠道才是qr_auth需扫码; app渠道是滑块,可自动) 待做: solver适配param上下文(aq页初始化udbrtt的方式), 验证通过后重发WUP登录即得cred。 ## ★ 信封规则定论(v2紧凑窗口对照实验, 2026-08-25夜) 工具: tools/envelope_forge.py(TAF原位补丁) + scripts/probe_envelope_rules2.py。 同一份新鲜信封(239s旧)在紧凑窗口内连续4次bind: | 实验 | 操作 | 结果 | 结论 | |---|---|---|---| | X0 对照 | 原t3+信封原nonce换可信新cred铸证 | ✅ ok:true | 信封可复用, nonce无独立时效 | | X1 错配 | 原t3 + 新账号cred铸证 | ❌ 40020 | 服务端校验 cert.cred↔t3.uid 一致 | | X2 换号 | t3补丁成新uid + 新账号cred铸证 | ❌ 40020 | 仍有隐藏账号绑定 => 见下 | | X3 改session | 三处session一致随机化 | ❌ 100 SYS_ERROR | session被服务端实时校验, 不可动 | X2根因分析: 信封内uid仅t3一处(BE@688, metaJSON里uid=0/associationId=0x0B000030 为固定魔数); 补齐t3仍拒 => 最自洽解释: **证书nonce是SDK在"某账号登录态"下生成 的账号绑定令牌**(服务端可凭nonce反查签发账号)。与d29290a终局"新nonce成功"不矛盾 (那次同为可信账号自用)。 工程铁律(修正早前"短TTL"误判): 1. 铸证必须【复用信封原证书的rnd/指纹/key_idx】, 仅替换cred字段 —— 纯随机nonce必40020; 2. t3.uid 必须与 cert 内 cred 所属账号一致; 3. session三处(iRequestId/metaJSON/v0尾4B)保持原值, 不可随机化; 4. 同账号信封长期复用(X0@239s✅; 数小时前老信封E-B未单独验证但无失败证据); 5. 跨账号 = 每号设备首登一次(hook_cert_keycap自动抓该号信封)后永久纯Python。 safe_auth闭环(app_login_flow.login_cred)已实现: pt_auth页JS逆向完成, urlParamMap原样回传+wupData空串可过; 新账号触发风控时自动解udbrtt滑块并重发WUP登录。 ## ★★★★ nonce 生成机制完全破解 + 任意账号零设备闭环(2026-08-26凌晨) ### 动态hook实锤(scripts/hook_nonce_chain.py + /tmp/opencode/nonce_chain.json) 在真机上对 getQUrlData→getOtpEx→gen_biz_token→xxtea 全链路 hook, 拿到运行时实参: ``` serviceTime = 当前毫秒时间戳 (0x1a039b1fc81 ≈ 1.787e12) nonce_next = counter | (serviceTime << 16) # counter 同st内递增, 从0开始 xxtea key输入 = uid_str + k1 # k1 = BusinessCfg::this+0x10 xxtea key = MD5hex(uid_str + k1)[:16] # 注意取hex字符串前16字符! xxtea data = pack(serviceTime) + pack(nonce_next) # 各8B小端 xxtea 输出 = ceil(len/4)+1 words, 末尾word存原长度 → 16B输入=20B输出 ``` **rnd = XXTEA(pack(st) + pack(counter|st<<16), key=MD5hex(uid+k1)[:16])** ### 关键发现 1. **k1 是设备级常量**: 对 uid=1199666914671 和 uid=1199666911746 两次调用, k1 均为 `865a4924a40897ac1fcfe6b4c2cbb0e3`, 不随账号变化! (证据: /tmp/opencode/k1_multi.json, gbt_in.k1 两次同值) 2. **nonce 完全本地可算**: serviceTime=本地时钟, counter从0开始, k1固定 → 任意账号用 目标uid+k1 即可离线铸造合法 nonce! 3. **Python 复刻逐字节一致**: tools/nonce_forge.py 对设备两轮 rnd (80dd1dfa... / 0ba8f8b7...) 完全 MATCH。 ### 终极验证: 纯协议任意账号 bind + cookie 全通! | 实验 | 结果 | |---|---| | 同账号本地nonce铸证 bind (hy_300023887) | ✅ ok:true biztoken344B | | **跨账号**本地nonce铸证+patch uid (hy_300024708→uid1199666911746) | ✅ ok:true biztoken344B | | **新账号从未设备登录** 全链路: 密码→cred→本地nonce→bind→cookie | ✅ 全套cookie(udb_cred/udb_biztoken...) | ### 修正早前错误结论(X2"nonce账号绑定令牌"实为误判) X2(换uid+换cred+复用旧nonce)→40020 的真正原因: **nonce 内编码了 uid**(通过 MD5(uid+k1) 派生key), 服务端用 t3.uid 重新派生key解密 nonce, 旧nonce(绑旧uid)配新uid 自然解不开 → 40020。 **正确做法**: 换账号时用【目标uid】重新计算 nonce, 而非复用旧nonce! → 这也解释了为什么"纯随机nonce必40020": 随机值无法用uid+k1解出合法st/counter结构。 ### 新的工程铁律(v3, 推翻v2的"每号设备首登一次") 1. nonce = XXTEA(pack(st)+pack(counter|st<<16), key=MD5hex(uid_str+k1)[:16]), 本地可算; 2. k1 = 设备常量(865a4924...), 一次采集永久可用(evidence/nonce_k1.json); 3. 任意账号: 密码→登录取cred→目标uid+k1重算nonce→铸证→bind→cookie, 全程零设备! 4. 信封本身(TAF帧)可长期复用/补丁(uid/cert), 不依赖账号。 ### 资产更新 - tools/nonce_forge.py: 本地nonce生成器(XXTEA+MD5, 自检对设备实采) - scripts/probe_nonce_bind.py: 本地nonce bind验证脚本(同/跨账号) - evidence/nonce_k1.json: k1设备常量证据 --- ## 附录: 网页 cookie 登录态的服务端验证 (活动页 webActUI TAF, 2026-08-26 深夜新增) ### 目标 纯 Python 直连活动页 TAF 接口, 确认"注入/复刻的网页 cookie"在服务端是**已登录**状态, 而不只是浏览器页面自说自话(document.cookie/localStorage 可伪造)。 ### 两条通道与结论 | 通道 | 结果 | |---|---| | HTTP POST `cdnws.api.huya.com/?baseinfo=×tamp=` | ❌ **volc-dcdn CDN 封锁非浏览器 TLS**: requests/curl/curl_cffi(chrome131等) 全部超时或 502, 仅真实浏览器可过 | | WebSocket `wss://wsapi.huya.com/?baseinfo=` | ✅ **纯 Python(websocket-client) 可直连**, 本验证走此通道 | ### webActUI TAF 信封格式 (逐字节逆向, 与仓库 _Writer JCE 编码一致) ``` [4B len] 10 03 2c 3c 40 56 08 66 7d 00 01 08 00 01 06 04 tReq 1d 00 01 8c 98 0c a8 0c [2c 36 4c 5c 66 00] value = JCE: 0a 0a t0 uid(INT64大端) t1"" t2"" t3 UA t4 cookie(STR4) t5 ZERO t6"" 0b 0b L2 = len(value); L1 = L2 + 14 (两样本一致) baseinfo(URL参数,b64) = JCE: t0 uid t1"" t2 UA t3 "HUYA&ZH&2052" t4..t7 空 t8 cookie(STR4) t9 trace t10 空MAP 认证帧(WS, 裸JCE无信封) = t0 uid t1 UA t2 cookie(STR4) t3"" t4 1 t5 "HUYA&ZH&2052" t2..t6 空尾 WS帧包装 = 00 03 1d 00 01 [2B len][4B len] + 信封(去4B前缀), len = inner - (12+svc+func) ``` - getActUserTaskDetail 的 func 尾 = `11 62 2f` = JCE **t1/INT16 标签+actId**(0x622F=25135, 活动"精英宝典福利季Q3"的 actId, 与 getActInfo 响应一致)。 - HTTP 响应体 = **base64(TAF信封)**; WS 响应 = 包装头 + 信封。 ### 三个致命坑 (全部实测定位) 1. **身份一致性**: baseinfo / 认证帧 / task 帧的 uid+cookie 必须**全部同一账号**, 否则服务端回 `wup request biz execption:-1`(tars iErrorCode 5000, status 255)。 早先失败原因 = 预热帧是旧账号捕获, task 帧是新账号 → 身份冲突。 2. **actId 尾字段**: `11 62 2f` 是 3 字节(标签1字节+INT16值2字节), 只发 `62 2f` 会被服务端解析失败(-1)。 3. **最小序列**: 只需【认证帧 + getActUserTaskDetail 两帧】; launch/getConfig/等非必需。 无 cookie 对照 → 905(未登录), 带 cookie → 200 请求成功 + 6 任务项(服务端确认)。 ### 验证结果 (2026-08-26 02:31, 新账号 hy_300026595 / uid 1199666913640) ``` [self_check] 通过: baseinfo/body/auth 与捕获金样本逐字节一致 [A] 带 cookie: 249B status=200 msg=请求成功 任务项=6 => ✅ 服务端确认已登录 [B] 无 cookie: 96B status=905 => ❌ 未登录 (对照成立) ``` 结论: 纯Python产出的网页 cookie 在活动服务端确认为**已登录**, 无需浏览器。 ### 资产 - tools/huya_activity_verify.py: 纯Python WS 验证工具 (自检金样本 + A/B 对照) - evidence/activity_taf_golden.json: baseinfo/body/auth 金样本(逐字节) - evidence/ws_warmup_frames.json: 8.26 浏览器预热帧(旧会话, 参考) - evidence/ws_current_session_frames.json: 浏览器当前会话帧(新账号, 证明格式) - evidence/activity_verify_result.txt: 最终验证输出 - /tmp/cdp_now*.py: CDP 抓当前浏览器帧的脚本(浏览器 200 对照) --- ## 附录B: 多账号设备画像 —— 设备/指纹/风控数据归属与校验矩阵 (2026-08-26 新增) ### 需求 号很多, 不能所有账号共用同一台"设备身份", 需要: ①分清哪些字段是设备、 哪些是指纹、哪些是风控; ②找到设备/指纹"注册"类接口, 让每账号有独立设备态。 ### 整个 App 登录链路的数据归属 (appId=5008 / hypasswordLogin) | 环节 | 数据 | 归属 | 说明 | |---|---|---|---| | WUP 登录 t1.t0 | **hdid** 32hex | **设备(注册锚)** | libhydeviceid.so 生成, dfp 链注册; **换→APP_SIGN_NOT_MATCH 硬错** | | WUP 登录 t1.t1 | app_version | **设备(签名相关)** | 与 hdid 绑定校验; **换→同样硬错** | | WUP 登录 t2.t2 | **fingerprint 40hex** | **指纹** | SHA1 设备指纹; 换→safe_auth 滑块(**可自动过**) | | WUP 登录 t2.t8 | device_id 40hex | 指纹(session hash) | dfpReport t5 下发值; 换→**直接通过** | | WUP 登录 t1/t2 其余 | vendor/model/os/screen/宽高 | **声明型设备信息** | 自述, 换→直接通过 (需与 screen/UA 自洽) | | WUP 登录 t0.t5 | safedeviceid(b64) | 指纹令牌 | RSA action, 服务端不校验内容; 换→通过 | | WUP 登录 t0.t2 | metadata JSON (session/traceId/uid) | 会话 | 账号绑定 | | WUP 登录 t0.t8 | user_action JSON | **风控(行为)** | 轨迹采集; 金样本固定值, 可批量复用 | | 证书 P1 | fingerprint + nonce + cred | 指纹+账号 | fingerprint 设备绑定; 换 cred/nonce 即多账号 | | 绑定流 | sdid (df/collect) | 指纹 | **每次调用返回新 sdid**, 天然每账号不同 | | 前置链 | getDfpConfig/selectOperator/dfpReport/dckey | 设备指纹注册 | 见下"注册类接口" | ### 服务端校验矩阵 (全部对 wup.huya.com 实测, 2026-08-26) ``` 字段变更 | 结果 hdid 32hex | 652B APP_SIGN_NOT_MATCH (硬错, 无滑块) app_version | 652B APP_SIGN_NOT_MATCH (硬错, 无滑块) fingerprint 40hex | 2107B 含 pt_auth 滑块URL → solver自动过 → 放行 ✅ device_id 40hex | 1571B cred ✅ (直接通过) vendor/model/screen | 1571B cred ✅ (直接通过) safedeviceid 内容 | 1571B cred ✅ (直接通过) multi 全换(除hdid/版本)| 滑块(可过) ✅ ``` ### 设备/指纹"注册类"接口 (已定位) 1. **df.huya.com/web/df/token + df/collect** —— 指纹采集注册;每次调用从服务端 取新 sdid (工具: core/huya/device_fingerprint.get_huya_sdid; **已实测每次返回 新值**, 天然满足"每账号一个")。 2. **wsapi.huya.com huyaudbwebui: getDfpConfig → selectOperator → dfpReport → udbdf.huya.com/dckey/check** —— App 渠道完整设备注册链: 上报设备采集数据, 服务端下发稳定设备指纹(32hex)/session hash(40hex)/action(RSA加密)。 dfpReport 加密体未破解(待破解#2), 纯Python走注册链暂不可行。 3. **数美 libtscsdk.so (cfgc.zztfly.com)** —— 第三方设备指纹 SDK, 登录前初始化。 ### 多账号工程方案 (已实现并端到端验证) - 每账号一套画像: fingerprint/device_id/机型/vendor/screen 随机生成并持久化 (evidence/device_profiles.json); **hdid+app_version 复用金样本**(硬锚, 不泄 露单账号身份, 只是 App 签名设备锚点)。 - 首次用新画像登录 → 触发 safe_auth 滑块 → solver (YOLO+opencv, 282px级) 自动 过验 → 重发 → cred; 证书 P1 指纹也用该账号画像指纹, 全链设备一致。 - sdid 每次 df/collect 自动新取, 绑定流天然隔离。 - 实测 (hy_300026595, realme RMX3366 画像): cred 114B → bind rc 0 → biztoken 344B → cookie verify 200 → 全套 cookie ✅ ### 资产 - tools/huya_device_profile.py: 每账号设备画像生成/持久化 (get_profile 幂等) - tools/app_login_flow.py: login_cred 支持 device_info 参数 (滑块自动过) - tools/full_web_cookie.py: 全链接入画像 (--new-device 强制换新画像) - evidence/device_profiles.json: 账号→画像映射