Files
live-hub-py/scripts/forge_replay.py
T
2026-08-26 18:28:33 +08:00

156 lines
5.1 KiB
Python

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