调查日期: 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。
| # | 方法 | 结果 |
|---|---|---|
| 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 运行时加载 / 种子动态计算 / 加密存储 / 自研实现。 全都错了 —— 真相是对象根本不存在(见 §四)。
技术根因: 前期所有 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 立即数极易出错。
aaa 后 pdf @ 0x100049f94 得到 LuaBridgeCtl 的方法注册表, 每项是
tolua_registr(L, "方法名", 函数指针) 三元组:
"SetXXTEAKeyGM" (0x1006c7d4e) → 0x100049000+0x9e0 = 0x1000499E0
"RsaEncryptPub" (0x1006c7d5c) → 0x100049000+0xb24 = 0x100049B24 ★
"RsaDecryptPri" (0x1006c7d6a) → 0x100049000+0xd5c = 0x100049D5C ★
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 → 构造出来的是【空字符串】
binding sym.func.100049d5c
└─ C++ 实现 sym.func.100044fc0 (与 EncryptPub 的封装逐条同构)
└─ sym.func.10004bda4 (16 B)
与 10004bd94 仅差一条 branch 编码, 同样返回空串
全是 tolua 样板, 无任何业务逻辑:
"%s has wrong number of arguments: %d, was expecting %d")w19=0 返回operator delete 清理临时 stringLua 调 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 (盒→游数据传递机制)