# 996盒子 → 传奇游戏 数据传递机制 > 调查日期: 2026-09-23 > 调查起因: RSA 逆向定论(见 `rsa_investigation.md`)后, 追问"密钥是否由盒子传入游戏" > 方法: env.json 落盘文件实测 + Lua 解密源码双向验证 (launcher + fgcq 两模块) > 结论: **传递机制与 RSA stub 无关**, 但完整还原了盒→游的两条数据通道 --- ## 〇、一句话总览 996盒子启动传奇游戏时, 通过 **env.json 文件注入**(选服进游戏时一次性落盘)和 **GN_/NG_ 运行时桥**(回调式)两条通道, 把账号凭证/签名密钥/渠道信息传递给游戏模块; 游戏侧凭 `token`+`sdata` 构造 CS20 登录握手串进服。**全程无 RSA 参与**, 敏感字段靠"沙盒隔离 + HMAC-SHA1 请求签名"保护(信任壳层架构)。 --- ## 一、传递通道 ①: env.json 文件注入(静态, 一次性) ### 1.1 触发时机 用户在启动器选服点"进入游戏"后: ``` 壳层(ObjC) 发 GameList 选择 → Lua: NativeBridgeManager:NG_SelectGameAndRestart(sender, envString) (NativeBridgeManager_launcher.lua:370) → ModuleChooseLayer:WriteEnvAndRestart(envStr) (ModuleChooseLayer_launcher.lua:3157) → WriteEnv(envStr) (:3119) → 落盘 /mod_launcher/env.json → GameEnvManager:RestartGame() ★ 重启 Lua 虚拟机, 切换到 mod_fgcq ``` ### 1.2 WriteEnv 落盘细节 (launcher :3119-3134) ```lua envPath = writePath .. getNewFoldername("mod_launcher") .. "/env.json" if fileutil:isFileExist(envPath) then fileutil:removeFile(envPath) -- 先删旧 -- 然后写入新 envString ``` 跨模块传递的介质就是这份**明文 JSON 文件**; fgcq 游戏模块启动后从同路径读回。 ### 1.3 真机落盘实测字段 (05-Documents镜像/mod_launcher/env.json) | 字段 | 实测值(脱敏) | 用途 | |---|---|---| | `uid` | 2001849239 | 盒子账号 ID | | `token` | 166a68a8...(42 位 hex) | **游戏登录票据**(SDK 登录凭证) | | `new_boxtoken` | eyJ0eXAiOiJKV1...(JWT, HS256) | 盒子新版令牌 | | `gameAccount` | z1098210167 | 游戏账号名 | | `signkey` | 47f67f30...(32 位 hex) | **SDK 请求 HMAC-SHA1 签名密钥** | | `u_key` | QuM2001849239itC | 用户密钥(账号绑定) | | `channel` | 110585 | 渠道号(握手串用) | | `gameid` / `gameId` | 206923 | 游戏 ID | | `modlist` | https://list-box.dhsf.xqhuyu.com/modlist/modlist_110585.txt?time=...&sign=... | 模块清单 URL(带 sign 防篡改) | | `gameListUrl` | https://h5qfdt.996box.com/ | 游戏列表 H5 | | `sdkBaseUrl` | https://api.tj.996sdk.com | SDK 后端 | | `cacheName` | cache_206923 | 缓存目录名 | | `boxVersion` | 5.1.1.9007 | 盒子版本 | | `sdkChannel` | 996box_mac | SDK 渠道标识 | | `platform` | mac | 平台 | | `isBoxLogin` | 1 | 盒子登录态 | | `isPortrait` / `resolution` | 0 / (空) | 屏幕方向/分辨率 | | `loadingAuthor` | 推广商:玖扬网络 | 加载页署名 | | `scene` | 0 | 场景标记 | | `boxConfig` | 1 | 盒子配置开关 | | `macAddress` | FCE92589-...(UUID) | 设备指纹 | | `gm` | 0 | GM 标志 | | `signToken` / `serverId` / `resolution` | (空) | 预留 | > 对照: .app 包内自带的 `env.json`(首次启动模板)还含 `sdkAppid`/`sdkAppkey`、 > `is_adult`/`is_realname`、JWT 格式 token —— 结构同源, 字段随渠道配置增减。 ### 1.4 与 .app 包内 env.json 的差异 | | .app 包内(模板) | Documents 落盘(真机) | |---|---|---| | token | JWT (eyJ0eXAi...) | 42 位 hex 票据 | | 渠道 | 37504 | 110585 | | 特有字段 | sdkAppid/sdkAppkey, is_adult, is_realname | u_key, gameAccount, signkey, sdkBaseUrl | | 生成方 | 打包时固化 | 壳层登录+选服后动态写入 | --- ## 二、传递通道 ②: GN_/NG_ 运行时桥(动态, 回调式) ### 2.1 桥机制回顾 ``` Lua → 壳: NativeBridgeCtl:Inst():sendMessage2Native("GN_方法名", jsonString) 壳 → Lua: nativeBridgeCtl:addNativeSelector("组名", "NG_方法", handler) ``` ### 2.2 注册表全量 (NativeBridgeManager_launcher.lua:393-432) | 组名 | NG_ 回调 | 传递的数据 | |---|---|---| | recvDataForInit | NG_recvData | 壳层数据下发(onRecvDataCB) | | SDKLoginResponse | NG_SDKLoginCB | **登录凭证**(见 2.3) | | AccountOperator | NG_Logout | 登出通知 | | OverlayInstallation | NG_OverlayInstallation | 覆盖安装 | | WifiMacRecord | NG_WifiMacRecv | MAC 地址 | | NativeStatus | NG_NativeStatusChanged | 壳层状态 | | DeviceRotation | NG_DeviceRotationChanged | 旋转 | | AllowLaunchGame | NG_AllowLaunchGame | `{isAllowLaunch}` 启动许可 | | RealNameInfo | NG_RealNameInfo | `{RealNameStatus}` 实名状态 | | SuspectInfo | NG_SuspectInfo | 可疑行为(防沉迷) | | HeartInfo | NG_HeartInfo / NG_AppInfo | 心跳 / App 信息 | | LastServerInfo | NG_LastServerInfo | 上次选服 | | SDKHasBuff | NG_HasBuff | SDK buff | | GameList | NG_SelectGameAndRestart | **envString → env.json 落盘**(即通道①的触发源) | ### 2.3 NG_SDKLoginCB 字段明细 (launcher :218-243) ```json { "state": 1, // 1 成功 / 0 失败 "userName": "...", // 账号名 "sessionId": "...", // 会话票(赋给 _password) "sdata": "...", // ★ 游戏服务器票据 → GameEnvManager:SetSData() ...其余字段逐个 SetLoginDataByKey(k, v) } ``` --- ## 三、数据最终去向: CS20 登录握手串 fgcq 的 `LoginProxy.lua:1056-1087` 把两通道数据拼成 Mir 风格握手串: ```lua -- // *#账号/角色名字/角色ID/parma4/920080512/channelid/whitelist sendStr = string.format("*#%s/%s/%s/%d/%s/0/%s/%s/%s/%s/%s/%s", account, -- ← env.gameAccount self:GetSelectedRoleName(), self:GetSelectedRoleID(), netClient:GetNetMessageTypeSData(), -- ← NG_SDKLoginCB 的 sdata 票据序号 platform, -- android:0 / ios:1 / 其他:2 (mac=2) envProxy:GetChannelID(), -- ← env.channel (110585) whitelist, cert, adult, -- ← NG_RealNameInfo / NG_SuspectInfo isboxlogin, -- ← env.isBoxLogin isBatchMsg) -- ← LuaBridgeCtl:GetModulesSwitch ``` 与《996传奇客户端框架结构与调用链.md》§13.1 的三段连接模型吻合: 此握手串是**第三段(游戏服)连接的首包**。 --- ## 四、signkey 的消费点(游戏内 SDK 请求签名) `AuthProxy.lua` (fgcq) 优先用 env 注入的 signkey, 无则回退硬编码: ```lua -- AuthProxy.lua:14 (默认值, 仅兜底) local signkey = "634eff98723b31da43ed35f0dd4edf36" -- AuthProxy.lua:269-274 (优先 env.json 注入值) local tmp = global.L_GameEnvManager:GetSignkey() if tmp and string.len(tmp) > 0 then signkey = tmp else releasePrint("can't find signkey by env.json, use default") end -- AuthProxy.lua:142 (消费: HMAC-SHA1 请求签名) local sign = sha1.hmac(signkey, origin) ``` 其他消费点: `GetCustomDataByKey("sdkPay"/"XZPay")` 决定支付通道; `GetEnvDataByKey("juhe_*"/"original_user_id")` 注入聚合支付参数。 顺带发现的相关硬编码(同一文件, 支付/聚合渠道): - 聚合密钥 `juhe_secret_key = "ptONCD6UxNbQRxYrvdVpFACgL1Ir53rjfUO0lOM2Did0ZxGEg2WB0WfhtsqTT2ee"` - 支付域名 `pay-sdkv2.dhsf.xqhuyu.com` / `pcjuhe.dhsf.xqhuyu.com` / `xdwqq.dhsf.xqhuyu.com` --- ## 五、与 RSA stub 的关系(本次调查的原点) | 问题 | 答案 | |---|---| | RSA 密钥是不是盒子传进来的? | **不是**。`RsaEncryptPub/RsaDecryptPri` 是引擎通用桥接接口, 客户端实现为空壳(返回空串), 证据链见 `rsa_investigation.md` | | 那 token/signkey 怎么保护? | **信任壳层架构**: 盒子 App 与游戏主二进制同进程同沙盒, env.json 落盘沙盒内; 出沙盒的 HTTP 请求靠 `HMAC-SHA1(signkey)` 签名防重放/防篡改 (AuthProxy.lua:142) | | 端侧为何无加密? | 传奇类"信任客户端壳、防服务器外敌"的经典设计; 破解者拿走 env.json 也只能顶替该账号(有设备指纹 `macAddress` + JWT 时效约束), 无需端侧加密 | --- ## 六、对 protocol_bot 的验证价值 本机制与 `protocol_bot/login_996pc.py` 的纯 HTTP 登录互为印证: | 环节 | 真机客户端 | protocol_bot | |---|---|---| | 账号登录 | 壳层 SDK (极验滑块) | 极验滑块自动破解 ✓ | | 拿 token | env.json 注入 | HTTP 响应直取 ✓ | | 选服 | NG_LastServerInfo/GameList | serverlist txt 直读 ✓ | | 进游戏 | env.json 重启 + CS20 握手串 | CS20 握手串直发 ✓ | | 请求签名 | HMAC-SHA1(signkey) | 同算法(如需调 SDK 接口) | 即: bot 已复刻通道①②的全部有效字段, 未依赖任何未还原的壳层能力。 --- ## 七、文件索引 | 文件(源码相对 `lua脚本解密源码/mod_launcher/stab/scripts/`) | 作用 | |---|---| | `logic/NativeBridgeManager_launcher.lua:370` | NG_SelectGameAndRestart 入口 | | `logic/NativeBridgeManager_launcher.lua:393-432` | NG_ 桥注册表(13 组) | | `logic/NativeBridgeManager_launcher.lua:218-243` | NG_SDKLoginCB 字段解析 | | `logic/ModuleChooseLayer_launcher.lua:3119` | WriteEnv 落盘 env.json | | `logic/ModuleChooseLayer_launcher.lua:3157` | WriteEnvAndRestart | | `../mod_fgcq/stab/scripts/game/proxy/remote/LoginProxy.lua:1056` | CS20 握手串构造 | | `../mod_fgcq/stab/scripts/game/proxy/remote/AuthProxy.lua:142,269` | signkey 签名消费 |