Files
live-hub-py/tools/dfp_gen.py
T
yml2213 36b5d78050 docs(huya): 记录 dfpReport 零设备生成突破与生成器工具
- 破解进度: 服务端不校验 cw 内容, 随机 4146B cw 即可 200 + 签发新 t2/t5
- 生成流程: 零设备注册链 getDfpConfig->selectOperator->dfpReport 打法
- tools/dfp_gen.py: cw/body 构建器 (TAF 4226B 结构)
- tools/dfp_cli.py: 注册链 CLI
- evidence: cw JSON 段/collection 段抓取样本
2026-08-27 17:57:38 +08:00

193 lines
6.6 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
dfp_report 零设备自生成器 (python)
====================================
基于实证研究 (见 docs/dfpReport破解进度.md 第29-31章):
★ 决定性发现 (round7):
- 服务端不校验 cw 内容: 整个 cw 用 os.urandom 填充仍返回 200 OK
- 必须保持: taf 总长 4B、cw 长度=4146 (2B字段)、尾部10B固定 3600400c0b8c980ca80c
- wup 协议尾 0b8c980ca80c 同时出现在 GetDfpConfig 请求/响应
★ body 结构 (4226B):
[0:4] 4B 大端总长 (0x1082=4226)
[4:10] 1003 2c3c4c56 (wup魔数)
[10:23] 0c "huyaudbwebui" (servant, len-prefixed)
[23:36] 09 "dfpReport" (func)
[36:45] 7d 00 01 10 56 08 00 01 06 04
[45:49] "tReq"
[49:58] 1d 00 01 10 48 0a 06 00 16
[58:65] 07 "android"
[65:70] 2d 00 01 (字段头)
[70:72] 10 32 ★cw 长度 2B (4146=0x1032)
[72:82] 571882cf664bb39401ee (MAGIC 10B)
[82:82+4146] cw: [2B前缀]+[586B JSON段]+[3548B collection段]+[10B固定尾]
cw 中的 JSON 段含三元组 (hdid/deviceId/appkey) 明文模板, 但服务端不校验内容
"""
import os
import struct
import ssl
import socket
import hashlib
import json
import time
import re
# ---- 常量 (来自真实 wire 证据) ----
TAF_HEAD = bytes.fromhex(
"10032c3c4c56" # wup 魔数
"0c687579617564627765627569" # len=12 "huyaudbwebui"
"66" # ★servant/func 间标记 (实证存在)
"096466705265706f7274" # len=9 "dfpReport"
"7d00011056"
"0800010604"
"74526571" # "tReq"
"1d00011048"
"0a060016"
"07616e64726f6964" # len=7 "android"
"2d0001"
"1032" # cw 长度 0x1032=4146
)
MAGIC = bytes.fromhex("571882cf664bb39401ee")
CW_TAIL = bytes.fromhex("3600400c0b8c980ca80c") # 10B 固定尾
CW_JSON_LEN = 586 # JSON^ks 段长度
CW_COLL_LEN = 3548 # collection 段长度
CW_PREFIX_LEN = 2
CW_LEN = CW_PREFIX_LEN + CW_JSON_LEN + CW_COLL_LEN + len(CW_TAIL) # 4146
JSON_TEMPLATE = (
'{"appId":"5008","appVer":"13.4.22","appkey":"{APPKEY}",'
'"channel":"xiaomi","deviceId":"{DEVICE_ID}",'
'"deviceName":"M2102J2SC","hdid":"{HDID}",'
'"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"}'
)
def gen_triple():
"""生成随机三元组 (格式与真机一致)"""
hdid = hashlib.sha256(os.urandom(32) + b"hdid").hexdigest()
device_id = hashlib.sha256(os.urandom(32) + b"devid").hexdigest()
appkey = hashlib.sha256(os.urandom(32) + b"appkey").hexdigest()
return hdid, device_id, appkey
def make_triple_json(device_name="M2102J2SC", system_ver="M2102J2SC,30,11",
channel="xiaomi", width=1080, height=2120):
hdid, device_id, appkey = gen_triple()
# 用 replace 而非 format (模板含 JSON 花括号)
body = JSON_TEMPLATE.replace("{APPKEY}", appkey) \
.replace("{DEVICE_ID}", device_id) \
.replace("{HDID}", hdid)
return hdid, device_id, appkey, body.encode("utf-8")
def build_cw(json_plain: bytes, random_json: bool = True) -> bytes:
"""
构建 cw (4146B):
[2B前缀][586B JSON段][3548B collection段][10B尾]
实证: 服务端不校验内容, 全部用随机数即可 (200 OK)
若 random_json=False 则 JSON 段 = json_plain ^ 随机ks (也是随机)
实际上整段随机已验证可行, 保留结构即可。
"""
prefix = os.urandom(2)
if random_json:
json_sec = os.urandom(CW_JSON_LEN)
else:
ks = os.urandom(CW_JSON_LEN)
js = json_plain[:CW_JSON_LEN].ljust(CW_JSON_LEN, b"\x00")
json_sec = bytes(a ^ b for a, b in zip(js, ks))
coll_sec = os.urandom(CW_COLL_LEN)
return prefix + json_sec + coll_sec + CW_TAIL
def build_body(cw: bytes, total_len=None) -> bytes:
"""组装完整 body (4226B), 重算 4B 总长与 2B cw 长度"""
assert len(cw) == CW_LEN, f"cw len={len(cw)} != {CW_LEN}"
body = TAF_HEAD + MAGIC + cw
# taf 总长 = 4B 自身 + 后续所有字节
total = len(body) + 4
full = struct.pack(">I", total) + body
assert len(full) == total
return full
def send_dfp(body: bytes, host="wsapi.huya.com", timeout=8) -> bytes:
"""HTTPS POST 到 wsapi.huya.com (真实通路, 已验证)"""
req = (
b"POST / HTTP/1.1\r\n"
b"i-ver: 1\r\n"
b"Content-Type: application/octet-stream\r\n"
+ b"Content-Length: " + str(len(body)).encode() + b"\r\n"
+ b"Host: " + host.encode() + b"\r\n"
b"Connection: close\r\n"
b"Accept-Encoding: gzip\r\n"
b"User-Agent: okhttp/3.14.9\r\n\r\n"
)
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
s = socket.create_connection((host, 443), timeout=timeout)
ss = ctx.wrap_socket(s, server_hostname=host)
ss.settimeout(timeout)
ss.sendall(req + body)
data = b""
try:
while True:
ch = ss.recv(4096)
if not ch:
break
data += ch
if len(data) > 10000:
break
except socket.timeout:
pass
except Exception:
pass
ss.close()
return data
def parse_resp(resp: bytes) -> dict:
"""解析 dfpReport 响应, 提取 actionV / base64 token"""
res = {"http_ok": b"HTTP/1.1 200" in resp[:100], "actionV": None, "token": None}
m = re.search(rb"actionV\(([0-9a-f]{40})", resp)
if m:
res["actionV"] = m.group(1).decode()
m = re.search(rb"\x26\xb4([A-Za-z0-9+/=]{30,})", resp)
if m:
res["token"] = m.group(1).decode()
return res
def main():
print("=== 零设备 dfpReport 自生成器 ===")
# 1) 随机三元组
hdid, did, appkey, json_plain = make_triple_json()
print(f"三元组: hdid={hdid}")
print(f" deviceId={did}")
print(f" appkey={appkey}")
# 2) 构建 cw (全随机段, 服务端不校验内容 - 已验证)
cw = build_cw(json_plain)
print(f"cw: {len(cw)}B, 尾: {cw[-10:].hex()}")
# 3) 构建 body
body = build_body(cw)
print(f"body: {len(body)}B")
# 4) 发送
resp = send_dfp(body)
print(f"响应: {len(resp)}B, HTTP200={b'HTTP/1.1 200' in resp[:100]}")
info = parse_resp(resp)
print(f"actionV={info['actionV']}")
print(f"token={info['token'][:40] if info['token'] else None}...")
if __name__ == "__main__":
main()