box_data_passing.md 9.2 KB

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)
        → 落盘 <writablePath>/mod_launcher/env.json
      → GameEnvManager:RestartGame()  ★ 重启 Lua 虚拟机, 切换到 mod_fgcq

1.2 WriteEnv 落盘细节 (launcher :3119-3134)

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)

{
  "state": 1,               // 1 成功 / 0 失败
  "userName": "...",        // 账号名
  "sessionId": "...",       // 会话票(赋给 _password)
  "sdata": "...",           // ★ 游戏服务器票据 → GameEnvManager:SetSData()
  ...其余字段逐个 SetLoginDataByKey(k, v)
}

三、数据最终去向: CS20 登录握手串

fgcq 的 LoginProxy.lua:1056-1087 把两通道数据拼成 Mir 风格握手串:

-- // *#账号/角色名字/角色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, 无则回退硬编码:

-- 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 签名消费