一个单机 / 弱联网项目做客户端安全验收,排查到存档这块问题不小。把过程记一下:现象、怎么定位、为什么能改、每层怎么堵、上线前过哪些检查项。
现象
抽查本地存档,金币、等级、背包全明文落盘,字段和值一眼能对上,改完塞回去客户端照读。先复现确认:用存档编辑器改文件、数值直接生效,问题坐实。
排查
第一步,看存档落哪、是不是明文。
Unity 项目先翻 PlayerPrefs。Android 上就是一个明文 XML:
/data/data/<pkg>/shared_prefs/<pkg>.v2.playerprefs.xml
金币一行 <int name="gold" value="99999" />,背包、任务是 JSON 和 flag。Unity 官方文档自己都写着 PlayerPrefs 不加密、别存敏感数据。
第二步,验证是不是只有文件这一条路。
把存档加密后再测,数值还能改。挂 GameGuardian 观察:它根本不碰存档文件,直接搜内存:搜当前金币、花掉一点再搜、几轮锁定地址就改了。说明文件层之外,还有内存层这条独立的路。
第三步,看程序能不能发现“被改过”。
存档没有任何完整性标记,改完读回时,程序无从判断它还是不是原始版本。篡改和正常存档在程序眼里没区别。
分析
三条线对应三个独立问题:
- 文件明文:存档要被游戏反复读写、写回成自己能认的格式,对拿着设备的人天生就是敞开的,不加密等于裸奔。
- 内存可改:文件加密只堵文件这条路,游戏一加载金币在内存里就是明文
int,GG 搜内存跟加没加密无关。 - 无完整性:没有校验,程序发现不了篡改。
再深一层:加密和校验的密钥都在客户端,SO / DLL 拖进 IDA / dnSpy 能把密钥扒出来,造一份“合法”存档。判定只要在客户端做,就有天花板。
方案
按层堵,成本从低到高。
文件层:AES 加密 + 完整性校验。给存档算 HMAC,或一个数存两份(值 + 反码)交叉校验,改文件当场露。
string mac = HmacSha256(saveKey, saveBytes); // 存成 {data, mac}
// 加载重算比对,对不上 = 被篡改
内存层:值不落明文,ObscuredInt 那套,内存里躺 gold ^ key,用时才还原。
int _key = Rand();
int _enc = gold ^ _key; // 内存里是异或后的值
int Gold => _enc ^ _key;
这只挡裸搜。GG 有针对 XOR 的加密搜索(a;b)和模糊搜索,还得叠交叉校验 + 反调试。
存放与密钥:存档放应用私有目录、别写公共目录;防拷贝就一机一把密钥,安装时随机生成、存进 Keystore / Keychain,账号 ID / 槽位 / 版本号当认证加密的 AAD。别拿 IMEI / Android ID 当密钥(会变、受权限、连累换机云恢复)。
服务端(带经济 / 交易 / 榜单):权威数据放服务端,客户端只当缓存;且服务端别把上传的存档原样入库,得做值域、字段自洽、必要时重演。
上线前检查项
- 存档不落明文,键名别直白叫 gold
- 有完整性校验(HMAC / 交叉校验),改文件能发现
- 内存值做了混淆 + 交叉校验,GG 裸搜搜不到
- 密钥没写死在代码,能换,尽量下沉 native
- 存档在应用私有目录;设备绑定用安装期随机密钥,不用 IMEI
- 带经济的:权威数据在服务端,且不原样入库
排到最后,结论很实在:存档在玩家手里、能读能写这条改不了,能做的是把成本抬到高过收益,把用编辑器和 GG 的大多数挡在外面。值钱的那份(能卖存档、能上榜交易的),最终算不算数别让客户端说了算。
(按文件 / 内存 / 服务端分层的完整做法和代码,搜「字节暗面 存档安全」有完整版。)

370

被折叠的 条评论
为什么被折叠?



