hyp_test.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. """Two-hypothesis test for the CS 2001 rejection:
  2. H-A (watermark): In proxy-port mode, param1 MUST come from the NetStamp
  3. sequence table (server validates it). Our random param1 gets rejected.
  4. -> Try harder to fetch the sequence: uppercase MD5, no-.txt, different
  5. serverID encodings, alternate hosts.
  6. H-B (32-byte header): The doc says '24-byte header becomes 32 when encrypted'.
  7. Maybe even with SData=0 the header is padded to 32 bytes on the wire.
  8. -> Send 24B header + 8 zero bytes + body (=110B total) and see if the
  9. server response differs.
  10. """
  11. import asyncio
  12. import base64
  13. import hashlib
  14. import json
  15. import random
  16. import struct
  17. import sys
  18. from pathlib import Path
  19. sys.path.insert(0, "src")
  20. from cq2_sdk.utils.encoding import encode6bit
  21. import httpx
  22. raw = Path(r"D:\ProgramFiles\996box\GameBoxDownload\1\env.json").read_bytes()
  23. text = raw.decode("utf-8", errors="replace")
  24. idx = text.find("eyJ")
  25. b64 = text[idx:].rstrip()
  26. padded = b64 + "=" * ((-len(b64)) % 4)
  27. env = json.loads(base64.urlsafe_b64decode(padded).decode("utf-8", errors="replace"))
  28. uid = str(env["uid"])
  29. pwd_enc = encode6bit(env["token"].encode()).decode("ascii")
  30. ch_enc = encode6bit(env.get("channel", "110585").encode()).decode("ascii")
  31. BODY = f"{uid}/{pwd_enc}/{ch_enc}/1/0"
  32. HOST_IP = "47.118.172.233"
  33. PROXY_PORT = 12000
  34. SID = 14414738
  35. SPORT = 8021
  36. NS_BASE = "https://engine-game.dhsf.996db.cn/list/"
  37. async def fetch_netstamp_variants() -> int | None:
  38. """Try every plausible md5/url variant for the sequence table."""
  39. print("=== H-A: NetStamp variants ===")
  40. cands: list[tuple[str, str]] = []
  41. for sid in [SID, 14414736, 14414739, 14414741, 14301060]:
  42. s = str(sid)
  43. md5_lower = hashlib.md5(s.encode()).hexdigest()
  44. md5_upper = md5_lower.upper()
  45. for tag, h in [("lower", md5_lower), ("upper", md5_upper)]:
  46. cands.append((f"sid{sid}-{tag}.txt", f"{NS_BASE}{h}.txt"))
  47. cands.append((f"sid{sid}-{tag}-noext", f"{NS_BASE}{h}"))
  48. # maybe it's md5 of the whole URL path or the port
  49. cands.append(("port8021", f"{NS_BASE}{hashlib.md5(b'8021').hexdigest()}.txt"))
  50. cands.append(("proxyport12000", f"{NS_BASE}{hashlib.md5(b'12000').hexdigest()}.txt"))
  51. # maybe list endpoint itself
  52. cands.append(("index", NS_BASE))
  53. cands.append(("index-no-slash", NS_BASE.rstrip("/")))
  54. async with httpx.AsyncClient(timeout=6, follow_redirects=True) as c:
  55. for label, url in cands:
  56. try:
  57. r = await c.get(url)
  58. if r.status_code == 200 and r.text.strip():
  59. txt = r.text.strip()
  60. print(f" *** HIT {label}: {url}")
  61. print(f" content[:200]: {txt[:200]}")
  62. try:
  63. data = json.loads(txt)
  64. # Lua: index = L8Bit(UnixTime)+1 ; param1 = Sequence[index]
  65. seq_list = data.get("Sequence") or []
  66. unix = data.get("UnixTime") or 0
  67. if seq_list:
  68. i8 = unix & 0xFF
  69. idx_seq = (i8 % len(seq_list)) if seq_list else 0
  70. val = int(seq_list[idx_seq])
  71. print(f" Sequence[{idx_seq}] = {val} (UnixTime low8={i8})")
  72. return val
  73. except Exception as e:
  74. print(f" parse err: {e}")
  75. else:
  76. print(f" {r.status_code} {label}")
  77. except Exception as e:
  78. print(f" ERR {label}: {type(e).__name__}")
  79. return None
  80. async def send_frame(frame: bytes, label: str) -> None:
  81. try:
  82. r, w = await asyncio.wait_for(asyncio.open_connection(HOST_IP, PROXY_PORT), timeout=5)
  83. except Exception as e:
  84. print(f" {label}: connect {type(e).__name__}")
  85. return
  86. try:
  87. w.write(frame)
  88. await w.drain()
  89. total = b""
  90. t0 = asyncio.get_event_loop().time()
  91. while asyncio.get_event_loop().time() - t0 < 4:
  92. try:
  93. chunk = await asyncio.wait_for(r.read(4096), timeout=1.2)
  94. if not chunk:
  95. break
  96. total += chunk
  97. if len(total) >= 6:
  98. break
  99. except asyncio.TimeoutError:
  100. if total:
  101. break
  102. if total:
  103. note = ""
  104. if total != b"\x00" * 6:
  105. note = " *** DIFFERENT FROM 6B-ZERO !!! ***"
  106. print(f" {label}: reply {len(total)}B {total.hex()}{note}")
  107. else:
  108. print(f" {label}: no reply")
  109. finally:
  110. w.close()
  111. try:
  112. await w.wait_closed()
  113. except Exception:
  114. pass
  115. async def main():
  116. # H-A: try to get a valid watermark
  117. seq_val = await fetch_netstamp_variants()
  118. print()
  119. # H-B: 32-byte header variant
  120. print("=== H-B: frame format variants ===")
  121. variants = []
  122. # v1: baseline (what we've been sending) — 24B header
  123. p1_rand = random.randint(100_000_000, 2_100_000_000) & 0xFFFF00FF
  124. variants.append((
  125. struct.pack("<6i", 2001, 900000, p1_rand, SID, SPORT, len(BODY.encode())) + BODY.encode(),
  126. "v1 24B-header random-p1 (baseline)",
  127. ))
  128. # v2: 24B header + 8 zero bytes padding (32B header hypothesis)
  129. variants.append((
  130. struct.pack("<6i", 2001, 900000, p1_rand, SID, SPORT, len(BODY.encode()))
  131. + b"\x00" * 8
  132. + BODY.encode(),
  133. "v2 32B-header (8 zero pad)",
  134. ))
  135. # v3: 24B header + 8 bytes = SData(0) repeated as int32 x2
  136. variants.append((
  137. struct.pack("<6i", 2001, 900000, p1_rand, SID, SPORT, len(BODY.encode()))
  138. + struct.pack("<ii", 0, 0)
  139. + BODY.encode(),
  140. "v3 32B-header (int32 0,0)",
  141. ))
  142. # v4: watermark = 0 exactly (not random)
  143. variants.append((
  144. struct.pack("<6i", 2001, 900000, 0, SID, SPORT, len(BODY.encode())) + BODY.encode(),
  145. "v4 24B-header p1=0",
  146. ))
  147. # v5: if we got a netstamp value, use it
  148. if seq_val is not None:
  149. variants.append((
  150. struct.pack("<6i", 2001, 900000, seq_val, SID, SPORT, len(BODY.encode())) + BODY.encode(),
  151. f"v5 24B-header p1=netstamp({seq_val})",
  152. ))
  153. for frame, label in variants:
  154. await send_frame(frame, label)
  155. await asyncio.sleep(0.5)
  156. print()
  157. print("Baseline reply is 6B zeros + FIN. Any DIFFERENT reply = breakthrough.")
  158. asyncio.run(main())