rsa_investigation.md 6.4 KB

RSA 探索全程记录 — 从 7 次搜索失败到 stub 定论

调查日期: 2026-09-23 调查对象: 996传奇盒子主二进制 (9.6MB arm64, stripped) 中 LuaBridgeCtl:RsaEncryptPub / RsaDecryptPri 最终结论: 两方法均为空壳 stub, 客户端二进制中不存在任何 RSA 密钥材料 配套文档: box_data_passing.md (密钥是否由盒子传入的否定性验证)


一、问题起源

mmoclient_interfaces.h 桥接组中 LuaBridgeCtl 有两个 RSA 方法:

RsaEncryptPub(...)   // tolua: LuaBridgeCtl:RsaEncryptPub
RsaDecryptPri(...)   // tolua: LuaBridgeCtl:RsaDecryptPri

有 Pub 又有 Pri, 意味着客户端可能内置完整密钥对(少见, 值得挖)。 rsa.py 据此留了接口骨架, 等待从二进制提取 PUB_KEY_N / PRI_KEY_D。


二、前 7 次搜索(全部失败, 保留作为方法论记录)

# 方法 结果
1 strings 搜 RsaEncryptPub / Public Key / BEGIN 只有 tolua 方法名字符串, 无 key 材料
2 ASN.1 头 30 82 扫 __DATA.__const (0x826350-0x891858, 439,560 B) 0 命中
3 RSA 模数特征 (256 B 高位起始 0x80-0xC0 + 非零密度 ≥98%) 0 命中
4 指数 01 00 01 00 (e=65537) 搜索 14+ 命中但全是 __const 段 16 B 数组的 +C 偏移常量, 非 key
5 OpenSSL 符号 (RSA_* / EVP_* / BN_*) 未找到 (二进制仅通用链接 Security.framework)
6 30 82 01 0a (2048-bit RSA 公钥 DER 头) 搜出的 4 个地址 0x100ad9e8f 等 false positive: 换算 file offset 后超出文件大小 (9,593,728 B), 落在无效段
7 手工编码 ADRP+ADD 字节模式找字符串 xref 失败 (方法错误, 见 §三)

失败原因当时的 4 种推测: Keychain 运行时加载 / 种子动态计算 / 加密存储 / 自研实现。 全都错了 —— 真相是对象根本不存在(见 §四)。


三、突破: r2 完整分析的正确用法

技术根因: 前期所有 xref 搜索都基于裸 r2 -c "/x 字节模式" 或未分析状态下的 axt。 r2 对 arm64 的字符串引用是 ADRP+ADD 指令对, 必须先 aaa 全量分析, axt 才能追溯。

# 正确姿势 (一条命令出结果):
r2 -q -c "aaa; axt @ 0x1006c7d5c" 996传奇盒子
# → sym.func.100049f94 0x10004a0bc [STRN] add x1, x1, str.RsaEncryptPub

方法论教训: stripped 二进制先 aaa 再 axt; 手工解码 ADRP 立即数极易出错。


四、完整反汇编证据链

4.1 tolua 注册函数 (0x100049f94)

aaa 后 pdf @ 0x100049f94 得到 LuaBridgeCtl 的方法注册表, 每项是 tolua_registr(L, "方法名", 函数指针) 三元组:

"SetXXTEAKeyGM"  (0x1006c7d4e) → 0x100049000+0x9e0 = 0x1000499E0
"RsaEncryptPub"  (0x1006c7d5c) → 0x100049000+0xb24 = 0x100049B24  ★
"RsaDecryptPri"  (0x1006c7d6a) → 0x100049000+0xd5c = 0x100049D5C  ★

4.2 RsaEncryptPub 调用链

binding sym.func.100049b24 (444 B)
  ├─ tolua 栈操作: 1005218c4 (取 self), 10052c5e4 ×2 (取 2 个 string 参数)
  ├─ 参数个数校验: cmp w8,2 → 报错 "has wrong number of arguments"
  └─ C++ 实现 sym.func.100044f30 (116 B, std::string 薄封装)
       └─ 核心调用 sym.func.10004bd94 (16 B) ★
            0x10004bd94  mov  x0, x8              ; sret 返回对象
            0x10004bd98  adrp x1, 0x1006c0000
            0x10004bd9c  add  x1, x1, 0x2c0      ; → 0x1006c02c0
            0x10004bda0  b    sym.func.10013ad30 ; std::string(const char*)

          0x1006c02c0 处的字节 = 00 73 74 61 74 65 00 ...
          ↑ 首字节 \x00 → 构造出来的是【空字符串】

4.3 RsaDecryptPri 调用链

binding sym.func.100049d5c
  └─ C++ 实现 sym.func.100044fc0 (与 EncryptPub 的封装逐条同构)
       └─ sym.func.10004bda4 (16 B)
            与 10004bd94 仅差一条 branch 编码, 同样返回空串

4.4 binding 剩余代码

全是 tolua 样板, 无任何业务逻辑:

  • 栈参数个数校验 ("%s has wrong number of arguments: %d, was expecting %d")
  • 失败路径置 w19=0 返回
  • operator delete 清理临时 string

4.5 结论

Lua 调 LuaBridgeCtl:RsaEncryptPub(任意输入) → 恒返回 ""
Lua 调 LuaBridgeCtl:RsaDecryptPri(任意输入) → 恒返回 ""

两方法是接口占位(空壳 stub): 引擎模板预留了 RSA 能力, 但当前服务器版本未启用, 也没有任何密钥材料编译进二进制。rsa.py 的 encrypt_pub/decrypt_pri 保持 NotImplementedError 是正确终态。


五、否定性验证: 密钥是否由盒子运行时传入?

为排除"密钥由壳层动态注入"的可能, 专项调查了盒子→游戏的数据传递 (完整结论见 box_data_passing.md):

通道 传递内容 含 RSA key?
env.json 文件注入 uid/token/signkey/u_key/channel/gameid/... 无 (signkey 是 HMAC-SHA1 签名密钥, 非 RSA)
GN/NG 运行时桥 (13 组) SDKLoginCB(userName/sessionId/sdata)/实名/心跳/... 无

且两个 RSA binding 的反汇编中没有任何读取 env/桥数据的代码路径 (不调 gameEnviroment/NativeBridgeCtl, 只碰 Lua 栈和空串常量), 注入假设不成立。

真实保护设计: 信任壳层架构 —— 同进程同沙盒 + HMAC-SHA1 请求签名 (AuthProxy.lua:142: sha1.hmac(signkey, origin)), 不需要端侧非对称加密。


六、对协议实现的影响

场景 影响
protocol_bot 现有登录链 零影响 — bot 走 996 平台 HTTPS (login_996pc.py 的 RSA_PUB_FALLBACK 是平台层 key, 与游戏内 stub 无关), 游戏内握手串不含 RSA 加密字段
未来服务器启用该通道 需从服务器侧或新版客户端提取 key; 届时 rsa.py 填 PUB_KEY_N 即可启用
抓包分析 登录/游戏 TCP 流可确定: 不存在 RSA 加密层, 头部即 Encode6BitBuf 编码 (见 hdr_codec.py)

七、时间线与产出物

2026-09-23 上午   第 1-2 轮搜索 (strings / ASN.1) 失败, rsa.py 留 INSTALL_HINT
2026-09-23 下午   第 3-7 轮搜索 (模数特征/0x00010001/OpenSSL 符号/误报地址) 失败
2026-09-23 傍晚   r2 aaa 完整分析 → axt 立即定位注册函数 → 逐层反汇编 → stub 定论
同日              box_data_passing.md 否定"盒子注入"假设, 案件闭环

产出:

  • 本文档 (调查记录 + 方法论)
  • rsa.py INSTALL_HINT 定论版 (含证据链地址)
  • box_data_passing.md (盒→游数据传递机制)