rsa.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #!/usr/bin/env python3
  2. """
  3. RSA 加解密 还原接口 (客户端本地密钥对)
  4. 来源: 996传奇客户端 LuaBridgeCtl @0x100042xxx 区段, tolua 注册:
  5. - RsaEncryptPub(data) → 服务器用私钥解 (客户端发包加密)
  6. - RsaDecryptPri(data) → 客户端用私钥解 (服务器发包加密)
  7. 注: 与 protocol_bot/login_996pc.py 里的 996 平台登录公钥**不同**,
  8. 那个用于 HTTPS 接口的 user-api/pay-sdk, 跟游戏协议无关。
  9. 当前状态: TODO — 客户端本地 RSA 密钥对尚未从二进制 @0x100042xxx 提取
  10. - 需要从 __DATA 段定位 RSA modulus (256/512 字节) 和 exponent (3/65537)
  11. - 模逆元(私钥 d)需要二次精读 RsaDecryptPri 的 CRT 优化代码反推
  12. - 提取后填到 PUB_KEY_N / PUB_KEY_E / PRI_KEY_D 即可
  13. 接口约定 (与二进制 LuaBridgeCtl 行为一致):
  14. - encrypt_pub(plain): 用客户端公钥加密, 传给服务器
  15. - decrypt_pri(cipher): 用客户端私钥解, 来自服务器
  16. - 客户端不需要"客户端用私钥加密, 服务器用公钥解"——那是签名场景
  17. """
  18. import base64
  19. from typing import Optional
  20. # ============== TODO: 从二进制提取的密钥对 (占位) ==============
  21. # 客户端公钥模数 (待从 __DATA 段提取, 通常 256 字节 = 2048 bit)
  22. PUB_KEY_N: Optional[int] = None
  23. # 客户端公钥指数 (通常是 3 或 65537 = 0x10001)
  24. PUB_KEY_E: int = 65537
  25. # 客户端私钥指数 d = e^(-1) mod φ(n) (待二次精读反推)
  26. PRI_KEY_D: Optional[int] = None
  27. # 客户端私钥 CRT 组件 (p, q, dp, dq, qinv) — 用于加速解密, 待提取
  28. PRI_CRT_P: Optional[int] = None
  29. PRI_CRT_Q: Optional[int] = None
  30. PRI_CRT_DP: Optional[int] = None
  31. PRI_CRT_DQ: Optional[int] = None
  32. PRI_CRT_QINV: Optional[int] = None
  33. # ============== 高层 API (待密钥填入后即可用) ==============
  34. def encrypt_pub(plain: bytes) -> bytes:
  35. """
  36. RSA 公钥加密 (客户端 → 服务器方向)
  37. :param plain: 明文, 长度须 < KEY_SIZE / 8 - 11 (PKCS#1 v1.5 padding)
  38. :return: 密文, 长度 = KEY_SIZE / 8
  39. :raises NotImplementedError: 密钥未提取
  40. """
  41. if PUB_KEY_N is None:
  42. raise NotImplementedError(
  43. "客户端 RSA 公钥模数 N 未从二进制 @0x100042xxx 提取。"
  44. "请用 r2 'pf N @ 0x1008xxxx' (匹配 .long double) 寻找 256/512 字节的"
  45. "0x03 0x01 0x00 0x01 0x00 ASN.1 SEQUENCE 头, 提取后填 PUB_KEY_N。"
  46. )
  47. # 实际实现走 pycryptodome:
  48. # from Crypto.PublicKey import RSA
  49. # from Crypto.Cipher import PKCS1_v1_5
  50. # key = RSA.construct((PUB_KEY_N, PUB_KEY_E))
  51. # cipher = PKCS1_v1_5.new(key)
  52. # return cipher.encrypt(plain)
  53. raise NotImplementedError
  54. def decrypt_pri(cipher: bytes) -> bytes:
  55. """
  56. RSA 私钥解密 (服务器 → 客户端方向)
  57. :param cipher: 密文, 长度 = KEY_SIZE / 8
  58. :return: 明文
  59. :raises NotImplementedError: 密钥未提取
  60. """
  61. if PRI_KEY_D is None or PUB_KEY_N is None:
  62. raise NotImplementedError(
  63. "客户端 RSA 私钥指数 d 尚未反推。"
  64. "需要从 RsaDecryptPri 函数的反汇编中提取 p, q 并计算 d = e^(-1) mod (p-1)(q-1)。"
  65. )
  66. # 实际实现走 pycryptodome (CRT 加速版):
  67. # from Crypto.PublicKey import RSA
  68. # from Crypto.Cipher import PKCS1_v1_5
  69. # if PRI_CRT_P and PRI_CRT_Q and PRI_CRT_DP and PRI_CRT_DQ and PRI_CRT_QINV:
  70. # key = RSA.construct((PUB_KEY_N, PUB_KEY_E, PRI_KEY_D,
  71. # PRI_CRT_P, PRI_CRT_Q, PRI_CRT_DP, PRI_CRT_DQ, PRI_CRT_QINV))
  72. # else:
  73. # key = RSA.construct((PUB_KEY_N, PUB_KEY_E, PRI_KEY_D))
  74. # cipher = PKCS1_v1_5.new(key)
  75. # return cipher.decrypt(cipher, None) # 失败返回 None
  76. raise NotImplementedError
  77. # ============== 测试辅助: 生成临时密钥对自验 (与 996 无关, 仅验接口形态) ==============
  78. def _self_test_with_temp_key():
  79. """用临时生成的 2048-bit RSA 密钥对验证 encrypt/decrypt 接口形态正确。
  80. 注意: 这与游戏客户端实际密钥无关, 仅 smoke test。"""
  81. try:
  82. from Crypto.PublicKey import RSA
  83. from Crypto.Cipher import PKCS1_v1_5
  84. except ImportError:
  85. print("[skip] pycryptodome 未安装, 跳过 rsa.py self-test")
  86. return
  87. # 临时密钥
  88. key = RSA.generate(2048)
  89. PUB_TMP = (key.n, key.e)
  90. PRI_TMP = key
  91. plain = b"hello RSA from rsa.py self-test" * 4 # 约 100 字节
  92. cipher = PKCS1_v1_5.new(RSA.construct(PUB_TMP)).encrypt(plain)
  93. dec = PKCS1_v1_5.new(PRI_TMP).decrypt(cipher, None)
  94. assert dec == plain, "self-test fail"
  95. print(f"✓ RSA 2048 self-test pass: {len(plain)}B → {len(cipher)}B → {len(dec)}B")
  96. print(f" (注意: 临时密钥对, 与游戏客户端密钥无关)")
  97. # ============== 占位符填入流程 (TODO 提示) ==============
  98. INSTALL_HINT = """
  99. ╔══════════════════════════════════════════════════════════════════╗
  100. ║ 最终结论 (2026-09-23, r2 aaa 完整分析后定稿): ║
  101. ║ 客户端 RsaEncryptPub / RsaDecryptPri 均为【空壳 stub】, ║
  102. ║ RSA 密钥根本不存在于二进制中 —— 搜索失败不是方法问题, 是无米之炊。 ║
  103. ╚══════════════════════════════════════════════════════════════════╝
  104. 完整调查记录见同目录 rsa_investigation.md (含方法论与否定性验证)。
  105. ✅ 完整反汇编证据链 (r2 aaa 后 axt 找到 xref):
  106. LuaBridgeCtl tolua 注册函数 sym.func.100049f94:
  107. "RsaEncryptPub" (0x1006c7d5c) → binding 0x100049b24
  108. "RsaDecryptPri" (0x1006c7d6a) → binding 0x100049d5c
  109. RsaEncryptPub 链:
  110. binding 0x100049b24 (tolua 栈检查 + 2 参校验)
  111. → sym.func.100044f30 (std::string 薄封装)
  112. → sym.func.10004bd94 ★ = mov x0,x8; adrp x1,0x1006c0000;
  113. add x1,x1,0x2c0; b 10013ad30
  114. = 用 __cstring 段偏移 0x2c0 处的字符串构造返回值
  115. = 0x1006c02c0 内容是 \x00 (空字符串!)
  116. RsaDecryptPri 链:
  117. binding 0x100049d5c
  118. → sym.func.100044fc0 (与 EncryptPub 的封装完全同构)
  119. → sym.func.10004bda4 ★ = 与 10004bd94 仅差一条指令, 同样返回空串
  120. 两个 binding 的其余代码全是 tolua 样板: 栈参数校验 (mov w1,2/w1,3)、
  121. "%s has wrong number of arguments" 报错路径、operator delete 清理。
  122. 🔍 之前 7 种搜索失败的根本原因:
  123. 1-6: 二进制中确实没有任何 RSA key material (搜索没错, 是对象不存在)
  124. 7 : 之前用字节模式手搜 ADRP 失败; 用 r2 "aaa" 完整分析后 axt 立即找到 xref
  125. —— 教训: stripped 二进制先跑 aaa 再找 xref, 不要手编 ADRP 编码
  126. 💡 对协议实现的含义:
  127. - Lua 层 LuaBridgeCtl:RsaEncryptPub(...) 调用只会拿到空串
  128. - 真实的登录加密在 protocol_bot/login_996pc.py 里走的是 996 平台 HTTPS
  129. (RSA_PUB_FALLBACK), 与游戏内这两个 stub 无关
  130. - 本模块的 encrypt_pub/decrypt_pri 保持 NotImplementedError 是正确终态:
  131. 若未来服务器启用该通道, 需从服务器侧或新版客户端提取 key
  132. """
  133. if __name__ == "__main__":
  134. print("rsa.py — 客户端本地 RSA 密钥对还原接口")
  135. print("=" * 60)
  136. print("当前状态: 密钥对未提取, encrypt/decrypt 抛 NotImplementedError")
  137. print()
  138. print("已知信息:")
  139. print(f" - 公钥指数 e = {PUB_KEY_E} (标准 65537)")
  140. print(f" - 公钥模数 n = {'未设置' if PUB_KEY_N is None else f'0x{PUB_KEY_N:x}'}")
  141. print(f" - 私钥指数 d = {'未设置' if PRI_KEY_D is None else f'0x{PRI_KEY_D:x}'}")
  142. print(f" - CRT 组件 p/q/dp/dq/qinv = {'已设置' if all([PRI_CRT_P, PRI_CRT_Q, PRI_CRT_DP, PRI_CRT_DQ, PRI_CRT_QINV]) else '未设置'}")
  143. print()
  144. print(INSTALL_HINT)
  145. print()
  146. # 临时密钥对自测 (smoke test)
  147. _self_test_with_temp_key()
  148. print("\nAll rsa.py smoke tests passed (or skipped).")