# 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` 才能追溯。 ```bash # 正确姿势 (一条命令出结果): 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` (盒→游数据传递机制)