数据保护

当「上服务端」不是答案时 — 没有后端如何守住游戏数据

「重要数值交给服务端判定」— 可要是没有服务端呢

关于游戏安全的讨论,结尾往往是同一句话:「货币、分数这类重要数值不要信任客户端,应当由服务端做最终判定。」

这话没错。我们自己也说过不止一次。

问题在于下一步。对没有后端的团队来说,这条建议实际上等于「把现在的结构重做一遍」。搭一套服务端,意味着服务器成本、账号体系、故障处理、个人信息合规,以及维持这一切的人。如果是以单机为主的游戏,这笔账很可能不划算。

所以本文换一个问题来谈:在已经决定不上服务端的前提下,能做到哪一步,又必须放弃什么。

先把话说清楚:没有服务端做不到的事

开始之前要先讲明白一点。只要最终判定权在客户端,就不存在决定性的防御。

如果游戏在设备内部判定「金币 5,000」,而没有外部基准可以核对这个判定,那么肯投入时间的攻击者最终总能把这个值变成他想要的样子。换存储格式、加签名、藏进内存,结果都一样——做校验的代码也在同一台设备上。

因此在无服务端的环境里,把目标定为「拦住」注定失败。现实的目标有三个:

  • 抬高成本 —— 从「用文本编辑器打开存档」抬到「必须分析二进制」
  • 留下痕迹 —— 让篡改发生过这件事,事后仍能被发现
  • 保留回退能力 —— 问题浮出水面时,有路径把状态恢复正常

仅这三点,就足以筛掉大多数随手的篡改。存档文件本身怎么保护,游戏存档篡改已经讲过。本文接着往下说。

没有服务端时特别好使的三种手法

1. 存档回滚 —— 撤销按钮变成免费的

把整个存档复制一份留着,结果不满意就还原回去。

  • 抽卡,结果不好就还原重抽
  • 用掉消耗品,还原让消耗不算数
  • 在难点处倒下,还原把惩罚抹掉

这种手法麻烦的地方在于,根本不需要篡改文件。它只是把正规存档正规地还原了一次,所以完整性校验和加密都筛不掉它。签名也能通过——那份存档是真的。

2. 设备时间篡改 —— 把等待时间抹掉

把设备时钟往前拨,一切消耗时间的设计都会垮掉。

  • 一天之内多次领取每日奖励
  • 让建造或养成计时器立刻结束
  • 重置冷却

System.DateTime.Now 读的是设备时钟。没有服务端,就没有基准去核对这个时钟对不对。

3. 云存档冲突 —— 在两台设备之间挑对自己有利的那份

用了云存档,两台设备就可能出现不同的进度。这时必须决定保留哪一份,而这条决定规则正是被利用的地方。下一节单独讲。

没有服务端也能用的四种结构

用单调递增计数器发现回滚

每次保存存档时,一并记录一个逐次加一的编号。然后把这个编号的最大值记在存档之外的地方。

读取时如果存档里的编号小于记录的最大值,说明被回滚过。至少「发生过回滚」这件事是能知道的。

当然,攻击者把两处一起回滚仍然能通过。所以这不是拦截手段,而是抬高成本的手段:原本复制一个文件就够,现在要找到两处存储位置并一起处理。

时间只用来发现「倒退」

把最后一次观测到的时刻存下来,下次启动时若读到比它更早的时间,就可以判断时钟被往回拨了。

反过来,往前拨则很难判别。究竟是真的过了一天,还是把时钟提前了一天,设备内部没有依据区分。这里常见的错误是一发现时间倒退就立刻处罚。跨时区出行、夏令时、时钟自动校正,这些正常原因同样会让时间发生跳变。

除非这款游戏完全不碰网络,偶尔取一次可信时间是最稳妥的做法。只确认时间而不是搭一整套后端,负担要小得多。

把奖励设计从依赖时间改为依赖进度

最根本的解法不在技术,而在设计。把「时间到了就能拿」的奖励换成「玩了才能拿」的奖励,时钟篡改就没有可图的东西了。

这不是每款游戏都能套用的办法。但至少要知道:在没有服务端的前提下守住时间型设计,本来就很难。

把存档绑定到设备

在存档中一并签入设备标识,就能筛掉直接拿别人存档来用的方式,对社区里流传「满级存档」的情况有效。

务必同时设计换机与备份恢复的流程,否则正常玩家换台设备就会丢掉进度。

云存档的陷阱 —— 解决冲突是你的活

这是无服务端游戏里最常被忽略的一点。

Google 的Play Games Services 已保存游戏文档对此说得很清楚:在多台设备上游玩、保存时连接不上服务、访客进度与账号进度相遇,都会产生冲突。而且该服务不会替你自动解决冲突,保留哪一份要由游戏来判断。

文档提供的判断材料是 ID、名称、描述、最后修改时间、游玩时长和封面图。这里就出现了一个安全层面的问题。

游玩时长和描述是由开发者、也就是客户端填入的值。 如果解决冲突的规则是「保留游玩时长更长的那份」,那就等于把选择权交给了能修改这个值的攻击者。

实务上要定下来的是这些:

  • 明确定义冲突解决规则。 不定义,就会有一条偶然形成的规则在起作用,而攻击者会利用它。
  • 不要只凭客户端填入的单一数值判断。 结合多项依据,或者交给玩家选择,会更稳妥。
  • 交给玩家选择时,要让他看到会失去什么。 文档同样建议在访客进度冲突时告知玩家并让其选择。
  • 不能让正常玩家的数据丢失。 这一条优先级最高。为了阻止篡改而抹掉真实进度,损失反而更大。

具体的 API 与最新行为请务必以各平台官方文档为准——平台策略的变化比本文快得多。

什么时候服务端不再是可选项

一直不上服务端并非永远合理。以下任意一条成立,时候就到了。

  • 存在排行榜或竞争要素,一个人的篡改会损害其他人的体验。
  • 内购获得的货币会影响游戏进度。
  • 存在玩家之间的交易、赠送、公会等数据在人与人之间流动的功能。
  • 确认了篡改案例,却没有办法判断影响扩散到了什么程度。
  • 面对「请帮我恢复进度」的正常玩家诉求,没有依据可以回应。

实务中起决定作用的是最后两条。没有服务端,就不知道发生过什么,也无法把东西还回去。比起篡改本身,这份无力感更让运营难受。

也不必一步到位做完整后端。可信时间、存档完整性记录、异常迹象收集,都可以作为很窄的起点。

检查清单

存档结构

  • 存档带有单调递增的编号,且最大值记录在其他位置。
  • 有存档完整性校验,并绑定了设备标识。
  • 换机与备份恢复的流程已纳入设计。

时间

  • 能够发现时钟倒退。
  • 发现后不会直接处罚,会考虑正常原因。
  • 若存在时间型奖励,有获取可信时间的途径。

云存档

  • 冲突解决规则已明确定义。
  • 该规则不依赖客户端填入的单一数值。
  • 冲突发生时正常玩家的数据得以保留。

运营

  • 留有能在事后核查篡改迹象的记录。
  • 会定期重新对照上面「什么时候服务端不再是可选项」的清单。

OZero Security 负责的范围与不负责的范围

不负责的范围

  • 我们不是服务端的替代品。 客户端握有最终判定权的结构,我们无法替你改变。需要服务端判定的数值,终究还是需要服务端。
  • 云存档的冲突解决规则、奖励设计、数据恢复策略,属于游戏设计与运营的职责。

负责的范围

  • 判断读写这些存档的客户端本身是否被篡改。识别可执行文件的二进制篡改、调试器附加与挂钩,以及模拟器、Root 等非可信运行环境,提高绕过难度。
  • 在原生层通过交叉校验检测内存篡改与游戏速度操控的尝试。速度操控正是攻击时间型设计的典型手段。
  • 提供存档文件与 PlayerPrefs 防篡改保护,以及防止从其他设备复制存档的设备绑定,并为正常更换设备提供由你控制的重置回调。可选的网络时间校验会将设备时钟与公共时间源比对,帮助发现时钟篡改。
  • 接入 Pro Add-on 后,收集可疑客户端的遥测数据,支持在后台按运营策略处置。对没有服务端的团队来说,这有时是「知道发生了什么」的第一条途径。

把校验逻辑放在 C# 之外的 Native C++ 层,关掉校验本身就变成了另一件独立的工作——这正对应无服务端环境下「抬高成本」的目标。

结论:知道拦不住,然后据此设计

无服务端游戏的数据保护,不是要造出完整的防御。那在结构上就做不到。

不如换个思路:筛掉随手的篡改,抬高认真攻击的成本,保证事后能知道发生了什么,保证正常玩家能被恢复。 这就是没有服务端时可及的范围。

而一旦越过这个范围——出现排行榜、付费货币影响进度、数据开始在玩家之间流动——服务端就从选项变成了必需。提前知道这条线在哪里,真到那一天判断会快得多。

下一步

  1. 确认防护范围 —— 查看各平台的支持规格,以及各方案(Standard / Pro)的防护范围。
  2. 快速开始接入 —— 导入与配置步骤见集成手册。
  3. 技术咨询 —— 欢迎咨询在没有后端的配置下能做到什么。

没有服务端,客户端完整性依然值得校验 — OZero Security。

相关阅读