Unity 内存数据极限防护:一个 int 藏 8 份密文,修改器到底还能怎么改?

游戏里有些数值非常敏感:

金币、钻石、战斗属性、关键计数......

如果直接使用普通 int

bash 复制代码
public int mGold = 10000;

内存里就是一个非常明确的 10000

一些简单的内存修改器只需要搜索数值、修改数值、再次筛选,就可能定位到目标地址。

MyFramework 中的 SafeInt / SafeLong / SafeFloat 没有试图让客户端数据"绝对不可修改",而是做了一件更加现实的事情:

不让真正的数据以固定明文、固定位置、固定密钥长期存在,同时主动检测内存是否被异常修改。

项目地址:

github.com/ZHOURUIH/My...

一、一个 int,实际用了 8 个密文位置

SafeInt 内部不是:

bash 复制代码
private int mValue;

而是准备了 8 个密文槽:

bash 复制代码
private int sidhgsg;
private int isdhy34;
private int hibsd;
private int sgkn;
private int jgo2;
private int hgwikg;
private int gnwe;
private int gwijhge;

再用:

bash 复制代码
private int wghwe;

记录当前真正的数据到底藏在哪一个位置。

每次 set()

bash 复制代码
wghwe = randomInt(0, 7);

因此同一个变量连续写入:

bash 复制代码
100
100
100
100

真实密文位置都可能不同:

bash 复制代码
第1次 → Slot 6
第2次 → Slot 1
第3次 → Slot 7
第4次 → Slot 3

修改器就不能长期盯住某个固定字段。

二、真正存进去的也不是原始值

写入时先重新生成两个随机密钥:

bash 复制代码
gikoweg = randomInt(mValue0, mValue1);
woghjwe = randomInt(mValue2, mValue3);

真正存储的值经过多层运算:

bash 复制代码
int newValue =
	(value ^ (gikoweg - (gikoweg >> 2))) +
	(woghjwe ^ 0x1238) -
	(woghjwe ^ 0xFF123);

所以业务值:

bash 复制代码
10000

内存里并不会长期出现:

bash 复制代码
10000

而会随着每次 set() 的随机密钥发生变化。

也就是说,同样写入 10000 两次,最终看到的密文通常也完全不同。

三、连密钥自己都要做合法性校验

如果攻击者不改密文,直接尝试修改密钥呢?

MyFramework 又给两个密钥增加了一条约束:

bash 复制代码
gikoweg =
	(gikoweg + woghjwe) /
	mValue5 *
	mValue5 -
	woghjwe;

其中 SafeInt

bash 复制代码
mValue5 = 437;

最终必须满足:

bash 复制代码
(gikoweg + woghjwe) % 437 == 0

读取数据前先检查:

bash 复制代码
if (gikoweg < mValue0 ||
	gikoweg > mValue1 ||
	woghjwe < mValue2 ||
	woghjwe > mValue3 ||
	(woghjwe + gikoweg) % mValue5 != 0)
{
	mGameFrameworkHotFix.onMemoryModified(...);
}

所以密钥不仅要"看起来是个 int",还必须:

范围正确 + 两个密钥之间的数学关系正确。

四、解密以后,还要再做第二次校验

仅仅检查密钥还不够。

写入真实值时还保存了一份校验数据:

bash 复制代码
gikowjeg = value ^ woghjwe;

读取时先解密:

bash 复制代码
int curValue =
	(value -
		(woghjwe ^ 0x1238) +
		(woghjwe ^ 0xFF123))
	^
	(gikoweg - (gikoweg >> 2));

再检查:

bash 复制代码
if (curValue !=
	(gikowjeg ^ woghjwe))
{
	mGameFrameworkHotFix.onMemoryModified(...);
}

因此一次 get() 实际经历:

bash 复制代码
检查密钥范围
↓
检查密钥数学关系
↓
找到真正密文槽
↓
解密
↓
用另一份校验数据验证
↓
返回真实值

攻击者只改中间任意一个字段,都可能导致校验失败。

五、每写一次,整套密钥都会换掉

这一点很关键。

set() 不是使用固定 Key:

bash 复制代码
Value
↓
固定密钥
↓
Cipher

而是:

bash 复制代码
每次set()
↓
生成新Key0
↓
生成新Key1
↓
重新选择8个槽中的一个
↓
生成新的Cipher

因此即使游戏数值完全没变,只要重新赋值,内存特征也会变化。

这比简单的:

bash 复制代码
mValue = value ^ 12345678;

要难直接搜索得多。

六、MostSafeInt:觉得一份还不够,再存两套

框架还有一个更加激进的:

bash 复制代码
public struct MostSafeInt
{
	private SafeInt erhgre;
	private SafeInt gwegihweg;
}

设置时两份一起写:

bash 复制代码
public void set(int value)
{
	gwegihweg.set(value);
	erhgre.set(value);
}

读取时两边分别解密:

bash 复制代码
int curValue = gwegihweg.get();
int checkValue = erhgre.get();

if (curValue != checkValue)
{
	mGameFrameworkHotFix.onMemoryModified(...);
}

于是攻击者不仅要让一个 SafeInt 内部所有校验成立,还得保证:

bash 复制代码
SafeInt A
==
SafeInt B

两套随机密钥、两套随机槽位最终必须解出同一个值。

七、Float 也不是直接把 IEEE 754 放在内存里

SafeFloat 会先把浮点数整数化:

bash 复制代码
int newValue =
	(round(value * 1000) ^
	(qwfb - (qwfb >> 2))) +
	(qgoqjg ^ 0x1238) -
	(qgoqjg ^ 0xFF123);

也就是保留约 3 位小数后,再进入和 SafeInt 类似的密文体系。

校验数据则使用:

bash 复制代码
asgihfasg =
	(int)(value * 10000) ^ qgoqjg;

读取以后再通过误差范围比较。

所以框架目前提供:

bash 复制代码
SafeInt
SafeLong
SafeFloat

MostSafeInt
MostSafeLong
MostSafeFloat

八、安全是有成本的,而且成本并不小

普通:

bash 复制代码
int = 4 Byte

而一个 SafeInt 内部有 12 个 int

bash 复制代码
8个密文
1个当前下标
2个密钥
1个校验值

理论数据量就是:

bash 复制代码
48 Byte

MostSafeInt 又包含两个 SafeInt

bash 复制代码
约96 Byte

所以这种类型绝对不应该无脑替换项目中所有 int

更合理的用法是只保护真正敏感的数据:

bash 复制代码
金币
付费货币
重要战斗属性
关键次数
客户端必须暂存的敏感值

而不是拿它存:

bash 复制代码
数组下标
UI页签
普通动画状态
临时循环变量

另外当前实现依赖随机数,并明确限制:

只能在主线程使用。

九、它不是"客户端绝对安全"

这一点必须说清楚。

加密算法最终仍然存在于客户端代码中,足够有能力的攻击者可以反编译、调试甚至绕过检测。

所以 SafeInt 的定位不是密码学意义上的安全存储,也不能取代服务器权威校验。

它真正解决的是:

bash 复制代码
明文搜索变困难
↓
固定地址修改变困难
↓
固定密文搜索变困难
↓
随意修改Key容易触发校验
↓
随意修改Cipher容易触发校验
↓
检测到异常后统一上报

最终异常都会进入:

bash 复制代码
mGameFrameworkHotFix.onMemoryModified(
	flag,
	param0,
	param1,
	param2,
	param3);

再由项目自己决定后续处理。

所以这套设计真正的核心不是:

"我把一个 int 加密了。"

而是:

我让真实值、密文位置和密钥不断变化,再用多层冗余信息检查这些数据有没有被人动过。

对于客户端防简单内存修改来说,这比一个永远躺在固定地址里的普通 int,已经完全是两回事了。

不过说真的,有点用,但不多

相关推荐
SmalBox5 小时前
02-01-原理篇-Unity原生AssetBundle原理深度解析
unity3d·游戏开发
fujisheng6619 小时前
FUI 验证实战:从 Prefab 节点改名到生成诊断与构建门禁
unity3d
_zhourui_h_1 天前
Unity AssetBundle 打包极限治理:依赖环、重复资源、包体膨胀,怎么在上线前全部揪出来?
unity3d
_zhourui_h_2 天前
Unity AssetBundle 极限管理:依赖、异步加载、引用计数、自动卸载到底怎么串起来?
unity3d
SmalBox2 天前
01-09-认知篇-对比-方案选型矩阵
unity3d·游戏开发
鑫鑫哥adam3 天前
用一个 struct 给游戏关键数值上锁:ProtectedInt 反作弊实践
unity3d
fujisheng6613 天前
FUI 导航实践:拆开 Layer、History、Coverage 与 Cache 的组合语义
c#·unity3d
SmalBox3 天前
01-08-认知篇-对比-其他资源管理方案
unity3d·游戏开发
SmalBox4 天前
01-07-认知篇-对比-原生AssetBundle工作流
unity3d·游戏开发