Unity3D应用防外挂与防破解

摘要:
在我们的游戏开发过程中,我陆陆续续做了一些防外挂与防破解工作,这里记录总结一下。##随机数校验 为了让玩家的操作体验更好,游戏开发之初,我决定把战斗计算放在前端实现,战斗结束之后,后端校验前端发来的数据。最简单直接的校验方法就是:按照前端的实现方法,后端实现战斗计算,逐条验证前端发来的战报。这种方法简单、直接,非常可靠,缺点一是维护前后端两份代码,比较麻烦,我不想做重复性工作;二是消耗服务器CPU资源。有什么方法不用这么麻烦呢?对于战斗,玩家可以操作的数据分为可变和固定数据。决定战斗结果关键因素

在我们的游戏开发过程中,我陆陆续续做了一些防外挂与防破解工作,这里记录总结一下。

##随机数校验
为了让玩家的操作体验更好,游戏开发之初,我决定把战斗计算放在前端实现,战斗结束之后,后端校验前端发来的数据。最简单直接的校验方法就是:按照前端的实现方法,后端实现战斗计算,逐条验证前端发来的战报。这种方法简单、直接,非常可靠,缺点一是维护前后端两份代码,比较麻烦,我不想做重复性工作;二是消耗服务器CPU资源。有什么方法不用这么麻烦呢?

对于战斗,玩家可以操作的数据分为可变固定数据。决定战斗结果关键因素:卡牌上场顺序、技能释放概率和部分伤害值,是随机的,也就是可变数据。要校验这块只需要校验随机数。

为此,我用线性冗余法,自己实现了随机数生成算法,前后端只要随机数种子相同,生成的随机数序列就是一模一样的。前端记录战斗过程中生成的随机数,后端逐个校验,非常简单,解决了可变数据被修改的问题。

##内存加密/校验
后来,我们的合作运营提醒我说,他们运营的上一款游戏,战斗也是前端计算的,出现过玩家用外挂修改攻击值/血量值的情况,找来玩家用的工具试了一下,效果非常犀利。可以直接把对方英雄血量改为1,然后一下击毙。这就是修改基础固定数据,防范方法很简单,内存加密或内存校验就可以了。

内存加密的简单方法是把关键数据加密,比如攻击值atk可以这样加密存取:

private int curATK;
private int curAtkKey;
public int CurATK {
	get {
		return curATK ^ curAtkKey;
	}
	set {
		curAtkKey = Random.Range(0, 0xffff);
		curATK = value^curAtkKey;
	}
}1234567891011

这样内存修改工具就无法根据数值来找到修改了。或者也可以做内存校验:

private int curATK;
private int curRealAtk;
private int curAtkKey;
public int CurATK {
	get {
		if (curRealAtk != (curATK ^ curAtkKey))
			// cheat!!! report to server...
		return curATK ^ curAtkKey;
	}
	set {
		curRealAtk = value;
		curAtkKey = Random.Range(0, 0xffff);
		curATK = value^curAtkKey;
	}
}123456789101112131415

##防破解
防破解主要是C#脚本加密。Unity3D生成的应用,逻辑脚本都编译到了Assembly-CSharp.dll中。打包时把它按byte加密,然后在应用启动时解密就可以了。

####加密方法
这个有很多种,根据自己的理解选一种就可以了。比如MD5、AES、xtea(cocos2d-x用的这个)、RSA等,都有开源实现。最简单是用mono自带的实现,也就是.net的安全类库实现,在System.Security.Cryptography中。

####解密
mono加载dll是在/mono/metadata/image.c中的mono_image_open_from_data_with_name。

MonoImage *
mono_image_open_from_data_with_name (char *data, guint32 data_len, gboolean need_copy, MonoImageOpenStatus *status, gboolean refonly, const char *name)
{
	MonoCLIImageInfo *iinfo;
	MonoImage *image;
	char *datac;
	
	if (!data || !data_len) {
		if (status)
			*status = MONO_IMAGE_IMAGE_INVALID;
		return NULL;
	}
	
	// 我们在这里解密data
	
	// load image from data ...
	
	if (buffer != NULL)
		g_free(buffer);
	return register_image (image);
}123456789101112131415161718192021

####编译mono
1. 下载资源

  • 注意unity mono与mono的不同,unity mono源码在https://github.com/Unity-Technologies/mono。
    我是在linux64位机器编译的,用的是Unity4.6,对应的ndk是r9,下载地址:
    https://github.com/Unity-Technologies/mono/tree/unity-4.6
    https://github.com/Unity-Technologies/krait-signal-handler
    http://dl.google.com/android/ndk/android-ndk-r9-linux-x86_64.tar.bz2

  • 下载后解压
    /home/night/mono-unity-4.6
    /home/night/android-ndk-r9

  • 将krait_signal_handler复制到mono-unity-4.6/external/目录下,并重命名为android_krait_signal_handler。

2. 安装依赖

  • 安装编译工具:gcc、make、automake等

  • 安装依赖包:bison、gettext、libffi-dev、zlib、libtool等
    yum -y install xxx

    3. 修改设置
  • 进入目录/home/night/mono-unity-4.6

  • 设置环境变量 export ANDROID_NDK_ROOT=/home/night/android-ndk-r9

  • 编辑./external/buildscripts/build_runtime_android.sh

  1. 找到这一行,perl ${BUILDSCRIPTSDIR}/PrepareAndroidSDK.pl,确保设置了ndk版本-ndk=r9;我编译的时候提示找不到ndk目录(设置了环境变量),可以在文件开头,export ANDROID_PLATFORM=android-9之后设置export ANDROID_NDK_ROOT=/home/night/android-ndk-r9。

  2. 在64位机器上,找到HOST_ENV=linux-x86,改为HOST_ENV=linux-x86_64

  3. 找到这一行-fpic -g -funwind-tables,去掉-g(编release版本)。

  4. 注释掉这两行,我们不需要ARMv5/v6的so

#clean_build “$CCFLAGS_ARMv5_CPU” “$LDFLAGS_ARMv5″ “$OUTDIR/armv5″
#clean_build “$CCFLAGS_ARMv6_VFP” “$LDFLAGS_ARMv5″ “$OUTDIR/armv6_vfp”12
  • 编辑./external/android_krait_signal_handler/build.pl,将#!/usr/bin/env perl –w改为#!/usr/bin/perl –w,因为有设备兼容问题

  • 编辑mono-unity-4.6/external/android_krait_signal_handler/PrepareAndroidSDK.pm
    拉到最后,找到sub PrepareNDK,在判断ndk版本号是否相等之前加入移除" (64-bit)"的代码

sub PrepareNDK
	...
	
	# remove the possible '(64-bit)' from the end
	my @curr_arr = split(' ', $current);
	$current = $curr_arr[0];

	if ($ndk eq $current)
	...123456789
  • 执行./external/buildscripts/build_runtime_android.sh
    如果出现以下提示,说明编译成功,生成的so在./builds/embedruntimes/android/armv7a/目录下。
    Build SUCCESS!
    Build failed? Android STATIC/SHARED library cannot be found… Found 4 libs under builds/embedruntimes/ android
    如果报错,可以看config.log文件,里面记录了错误的详细原因。

PS. 如果想编译Windows平台的mono.dll,需要用Visual Studio Command Prompt,打开,然后进入./msvc目录,执行msbuild.exe mono.sln /p:Configuration=Release_eglib

转载请注明出处: http://blog.csdn.net/ynnmnm/article/details/48784335。作者:夜风。

免责声明:文章转载自《Unity3D应用防外挂与防破解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇cephadm搭建ceph集群l2tp连接不上,修复下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

双向认证

证书携带公钥信息,用于验证服务器端,加密/解密数据,并作为OSI五类服务的认证(认证)服务和保密服务。这种双重认证是在线银行系统安全的关键!有关单向身份验证,请参阅Java加密技术(X)。双向身份验证要求CA授权机构颁发此类客户端和服务器端证书。首先,CA机构需要构建根证书。我们在linux下直接使用openssl来完成CA。我们需要修改openssl.cn...

GERBER文件

GERBER文件GERBER文件是一种国际标准的光绘格式文件,它包含RS-274-D和RS-274-X两种格式,其中RS-274-D称为基本GERBER格式,并要同时附带D码文件才能完整描述一张图形;RS-274-X称为扩展GERBER格式,它本身包含有D码信息。或GERBER描述是防焊层,并且描述之图形主要是防焊部分。若您自己将PCB文件转换成GERBER...

Windows Server 2019 Active Directory (AD域)时间不同步的解决方法

2.启用NTPServerHKEY_LOCAL_MACHINESYSTEMCurrentControlSetServicesW32TimeTimeProviderNtpServer,将键Enabled的值修改为十进制的1。快速将所有注册表导入WindowsRegistryEditorVersion5.00[HHKEY_LOCAL_MACHINESOFTWAR...

el-table设置默认选中

//初始设置选中toggleSelection(rows){if(rows){rows.forEach(row=˃{this.$refs.table.toggleRowSelection(row);});}}rows传入选中项的集合...

笔记本中LVDS屏与eDP屏的比较

EDP,EmbeddedDisplayPort它是一个基于DisplayPort架构和协议的内部数字接口。目前,笔记本电脑液晶屏的信号传输方式主要可分为LVDS和eDP接口。eDP是一种相对较新的技术,它比传统的LVDS接口具有更多优势。此外,自Haswell平台以来,Intel芯片组取消了LVDS接口的支持,仅保留CPU直接输出的eDP信号。根据技术规范,...

etcd集群日常维护

配置文件和启动参数说明命令行|配置文件|说明data-dir|ETCD_DATA_DIR|指定节点的数据存储目录,包括节点ID,集群ID,集群初始化配置,Snapshot文件,若未指定—wal-dir,还会存储WAL文件;wal-dir|ETCD_WAL_DIR|指定节点的was文件的存储目录,若指定了该参数,wal文件会和其他数据文件分开存储。增加一个新的...