find_key_218620.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. """Find game-218620's Lua XXTEA key.
  2. Layout hint from cq2.exe: sign b'4meJnPyl\\0\\0\\0\\0' + key b'34UFXMgtkz\\0\\0'
  3. — sign and key stored adjacently. Game 218620's cache uses sign 'XdvFTgd5',
  4. so its key is likely stored next to that string in ITS module binary
  5. (or in cq2.exe itself if multiple keys are embedded).
  6. Probe:
  7. 1. Search cq2.exe for 'XdvFTgd5' — read next 16 bytes as key candidate
  8. 2. Search game-218620's own module dir binaries
  9. 3. Try decrypting one of its .lua with each candidate
  10. """
  11. import struct
  12. import sys
  13. from pathlib import Path
  14. sys.path.insert(0, str(Path(__file__).parent))
  15. from decrypt_client_lua import SIGN, DELTA, bytes_to_u32, u32_to_bytes
  16. BASE = Path(r"D:\ProgramFiles\996box\GameBoxDownload\1\cq")
  17. SIGN2 = b"XdvFTgd5"
  18. def xxtea_decrypt(v, key):
  19. n = len(v)
  20. if n < 2:
  21. return v
  22. rounds = 6 + 52 // n
  23. total = (rounds * DELTA) & 0xFFFFFFFF
  24. y = v[0]
  25. while total != 0:
  26. e = (total >> 2) & 3
  27. for p in range(n - 1, 0, -1):
  28. z = v[p - 1]
  29. mx = (((z >> 5) ^ ((y << 2) & 0xFFFFFFFF)) + ((y >> 3) ^ ((z << 4) & 0xFFFFFFFF))) ^ ((total ^ y) + (key[(p & 3) ^ e] ^ z))
  30. mx &= 0xFFFFFFFF
  31. y = v[p] = (v[p] - mx) & 0xFFFFFFFF
  32. z = v[n - 1]
  33. mx = (((z >> 5) ^ ((y << 2) & 0xFFFFFFFF)) + ((y >> 3) ^ ((z << 4) & 0xFFFFFFFF))) ^ ((total ^ y) + (key[0 ^ e] ^ z))
  34. mx &= 0xFFFFFFFF
  35. y = v[0] = (v[0] - mx) & 0xFFFFFFFF
  36. total = (total - DELTA) & 0xFFFFFFFF
  37. return v
  38. def try_decrypt(raw: bytes, key16: bytes) -> bytes | None:
  39. try:
  40. v = bytes_to_u32(raw[8:], include_length=False)
  41. k = bytes_to_u32(key16, include_length=False)
  42. v = xxtea_decrypt(v, k)
  43. real_len = v[-1]
  44. if real_len <= 0 or real_len > (len(v) - 1) * 4:
  45. return None
  46. plain = u32_to_bytes(v[:-1])[:real_len]
  47. # validate: looks like Lua text
  48. head = plain[:60].lstrip()
  49. if head.startswith((b"--", b"local", b"function", b"return", b"require")):
  50. return plain
  51. return None
  52. except Exception:
  53. return None
  54. def find_key_candidates() -> list[bytes]:
  55. cands: list[bytes] = []
  56. targets = [
  57. BASE / "cq2.exe",
  58. BASE / "libcocos2d.dll",
  59. ]
  60. # also 218620's own module dir
  61. for extra in (BASE / "cache_218620").rglob("*.exe"):
  62. targets.append(extra)
  63. for extra in (BASE / "cache_218620").rglob("*.dll"):
  64. targets.append(extra)
  65. for t in targets:
  66. if not t.exists() or t.stat().st_size > 100_000_000:
  67. continue
  68. data = t.read_bytes()
  69. i = data.find(SIGN2)
  70. while i >= 0:
  71. # key likely follows the sign after NUL padding: read next 24 bytes
  72. after = data[i + 8 : i + 8 + 24]
  73. # extract printable run(s)
  74. import re
  75. for m in re.finditer(rb"[!-~]{6,20}", after):
  76. cand = m.group()
  77. cands.append(cand)
  78. # also the 16 raw bytes right after (padded)
  79. cands.append(after[:16].rstrip(b"\0").ljust(16, b"\0"))
  80. i = data.find(SIGN2, i + 1)
  81. return cands
  82. def main():
  83. sample = None
  84. for f in (BASE / "cache_218620").rglob("*.lua"):
  85. raw = f.read_bytes()
  86. if raw[:8] == SIGN2:
  87. sample = (f, raw)
  88. break
  89. if not sample:
  90. print("no sample with sign XdvFTgd5 found")
  91. return
  92. f, raw = sample
  93. print(f"sample: {f.name} ({len(raw)}B) sign={raw[:8]!r}")
  94. cands = find_key_candidates()
  95. print(f"key candidates from binaries: {len(cands)}")
  96. for c in set(cands):
  97. if len(c) < 6:
  98. continue
  99. key16 = c.ljust(16, b"\0")[:16]
  100. plain = try_decrypt(raw, key16)
  101. status = "HIT!" if plain else "-"
  102. print(f" {status} key={c!r}")
  103. if plain:
  104. print(f" first 80 chars: {plain[:80]!r}")
  105. out = Path("lua-dec2/cache_218620_test.lua")
  106. out.write_bytes(plain)
  107. print(f" saved -> {out}")
  108. return
  109. print("no candidate worked")
  110. main()