#!/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()