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 段抓取样本
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user