字节暗面

数据上报校验:服务端如何识别伪造的游戏数据


最近在分析客户端数据上报时,我按照常见的攻击者绕过方式全部梳理了一遍。

手法并不复杂,比如:抓到结算请求,把金币从500改成50000,再重放一次。接口签名、时间戳和nonce能拦住其中一部分。或者客户端正常打完一局,只是内存里的攻击力被改高了,变速齿轮拉快了游戏时间,或者无敌逻辑让角色没有掉血。请求是真的,战斗也确实发生过,字段甚至能够互相对上,单靠接口验签查不出来。

一、先改结算包

服务端校验通常从请求合法性开始:账号和角色是否匹配,battleId是否存在,序号有没有倒退,时间戳是否过期,同一局是否重复结算。

更习惯让服务器在开局时下发battleId和随机种子。客户端后续只报操作,最小事件可以保留这些字段:

battleId | seq | clientTick | action | targetId | checkpointHash

服务器另外记录recvTimeseq用于检查丢包和重放,clientTick与服务端心跳对时,checkpointHash只作为审计线索,不能拿它证明客户端可信。

这里有个容易被忽略的问题:固定在客户端里的签名密钥也可能被提取,原有签名函数还可能被外挂直接调用。签名能抬高改包成本,但它守不住结算结果。

奖励由服务器算,events从哪里来

把奖励计算放到服务器,方向没有问题:

foreach (var e in events.OrderBy(x => x.Seq))
    ApplyIntent(context, e.Action, e.TargetId);
Grant(playerId, CalculateReward(context.VerifiedResult));

真正麻烦的是events仍然来自客户端。如果客户端上报的是“造成10000点伤害”“敌人已死亡”,服务器只是把这些结果再加一遍,所谓重算没有意义。

我会把事件收窄到“操作意图”,例如释放哪个技能、选择哪个目标。命中、伤害、冷却和奖励仍由服务器根据角色属性、关卡配置、随机种子和战斗前快照推导。做不到完整服务端战斗的项目,也至少要把货币、稀有道具、首通奖励和排行榜积分留在服务器结算。

二、战斗是真的,过程被改了

攻击力修改、变速和无敌比直接改金币更麻烦。攻击者不需要伪造一整局,只要让客户端按错误规则跑完,再提交看似连贯的事件流。

这时要查的是过程约束。前面提到的8秒冷却技能,如果10秒内出现5次,可以直接按技能分组检查:

bool broken = events.GroupBy(e => e.SkillId).Any(g => {
    var t = g.OrderBy(e => e.ObservedMs).Select(e => e.ObservedMs).ToArray();
    var cd = GetEffectiveCooldown(g.Key, serverBuffs);
    return t.Length > 1 && t[^1] - t[0] + jitterMs < (t.Length - 1) * cd;
});

ObservedMs应使用服务端接收时间,或由心跳锚定后的客户端单调时钟。冷却值也要从服务端状态计算,角色身上的减CD Buff不能漏掉,否则误报会很多。

变速可以看clientTick与服务端时间的持续漂移;无敌则要核对敌方攻击、碰撞和角色血量变化。这里也有边界:如果战斗完全由客户端裁决,服务器既没有敌方行为,也没有可信的状态快照,仅靠结算上报无法证明玩家是否开过无敌。

全量重演之前,先算成本

完整上传每一帧通常没必要。做这类设计前,会先算关键事件的账。

battleId 8字节、seq 4字节、clientTick 4字节、action 2字节、targetId 8字节、checkpointHash 8字节计算,一条事件原始字段约34字节。每局记录80个关键事件,原始数据约2.7KB,算上协议和索引后再按实测调整。

CPU可以先设一个预算示例:所有对局跑序号、值域和状态约束;5%的普通对局做完整重演;风险对局100%重演;规则重演的P95先压在2ms/局以内。这个数字不是行业标准,只是容量评估的起点,最终要拿真实玩法压测。

风险分数要落到数值上。例如:冷却冲突+40,持续时钟漂移+25,关键状态不一致+60,宏相似度异常+15。重复结算和非法battleId直接拒绝,不参与累计。总分达到60时延迟发奖并完整重演,达到100时冻结本次结算并进入复核,权重需要根据误报率回调。

三、数据是真的,操作的人是假的

再往后是脚本和宏,这个就比较看你的游戏类型了,有些游戏不在意这个可忽略,有需求的往下看。一串数据中,技能释放合法,冷却正常,伤害也由服务器算,数据看上去没有问题,只是操作并非玩家本人完成。

这已经超出了数据正确性校验的范围。服务端只能继续观察多局行为,比如操作间隔是否长期过于稳定、路径是否高度重复、账号是否全天候运行,以及多个账号是否共用相似设备和行为模板。客户端环境检测也能补充脚本、注入和自动化工具的风险信号。

但服务端校验能判断一局战斗是否符合规则,真到了这一步,还是要接入行为检测和风控,而不是继续往结算接口里堆字段。

一份可以直接复用的检查清单

接口评审问题 最小落地项
请求是不是本局产生的 服务端下发battleId;校验nonceseq和幂等状态
客户端报的是操作还是结果 只接收操作意图;伤害、掉落和奖励由服务器计算
战斗过程能不能成立 校验冷却、能量、血量、库存和事件顺序
时间是否被加速 比较心跳锚定时间与服务端时间,给网络抖动留容差
每局都重演是否划算 全量跑轻规则,普通对局抽样重演,风险对局全量重演
数据正常是否等于真人操作 不等于;脚本和宏交给跨局行为检测与风险处置

 

← 返回列表