浅谈游戏安全

🗨️字数统计=1.5k字 ⏳阅读时长≈6分钟

网络游戏现状

市面上手机游戏数不胜数,在众多玩家玩的不亦乐乎的时候,总有那么一小撮拥有技术的人不安分,他们开发出了外挂修改器,甚至直接修改游戏安装包。对于部分玩家来说,外挂是超级作弊器,可以无条件获得任何想要的装备,对他们有益。但是对于游戏开发商来说,这无异于晴天霹雳。

什么是外挂

外挂,又叫开挂、开外挂、辅助、修改器,一般指通过修改游戏数据而为玩家谋取利益的作弊程序或软件,即利用电脑技术针对一个或多个软件进行非原设操作,篡改游戏原本正常的设定和规则,大幅增强游戏角色的技能和超越常规的能力,从而达到轻松获取胜利、奖励和快感的好处,通过改变软件的部分程序制作而成的作弊程序。

主要应用原理是在游戏中用封包和抓包工具对游戏本身或游戏服务器提交假参数从而改变游戏中的人物能力。 使用外挂具有一定风险,特别是在非单机游戏中使用破坏游戏公平性的外挂,可能还会被封禁账号。

—来自百度百科

外挂的弊端

一些外挂会破坏游戏的平衡,造成网络游戏的极度不公平,影响正常玩家的游戏体验,若长时间不控制,正常玩家会逐渐流失。

外挂的种类

1、外挂修改器,独立于手机之外的一个小软件,原理是修改内存,有时即使时特意通过第三方加固过的安装包也无法杜绝数据内存被修改。

2、外挂包,即安装包的包体被修改,替换了dll以及so库等。

应对方法

我经历的项目有幸被这两种外挂盯上,由于在开发阶段部分功能没有做好数据加密防护,在被外挂魔改之后,达成了一刀秒杀,满攻速,免疫所有伤害等,所幸咱也不怕,兵来将挡,水来土掩,开始与外挂斗智斗勇。

对于外挂修改器,对核心数据进行加密,修改一下存储的数据类型,就可以杜绝大部分外挂修改器的修改,其次,可以对数据进行基本的校验,比如存储时将数据分别*2和/2,三份数据均存在本地,取数据时再进行校验,如果数据不对就闪退。

对于包体被修改,可以在打包之后对包体内的文件进行md5校验,运行游戏的时候如果发现md5不对就闪退。也可以使用第三方加固包体的服务,使得包体无法再被修改。

另外,终极大招就是在服务器端对数据进行校验,这样外挂就无机可乘了。

相关代码

可以参考以下代码

DBTools.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace DBTools
{
public class Encryption
{
/// <summary>
/// 加密数据
/// </summary>
/// <param name="Text"></param>
/// <param name="sKey"></param>
/// <returns></returns>
public static string Encrypt(string Text, string sKey)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
byte[] inputByteArray;
inputByteArray = Encoding.Default.GetBytes(Text);
des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8));
des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
StringBuilder ret = new StringBuilder();
foreach (byte b in ms.ToArray())
{
ret.AppendFormat("{0:X2}", b);
}
return ret.ToString();
}
/// <summary>
/// 解密数据
/// </summary>
/// <param name="Text"></param>
/// <param name="sKey"></param>
/// <returns></returns>
public static string Decrypt(string Text, string sKey)
{
DESCryptoServiceProvider des = new DESCryptoServiceProvider();
int len;
len = Text.Length / 2;
byte[] inputByteArray = new byte[len];
int x, i;
for (x = 0; x < len; x++)
{
i = Convert.ToInt32(Text.Substring(x * 2, 2), 16);
inputByteArray[x] = (byte)i;
}
des.Key = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8));
des.IV = ASCIIEncoding.ASCII.GetBytes(System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(sKey, "md5").Substring(0, 8));
System.IO.MemoryStream ms = new System.IO.MemoryStream();
CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(inputByteArray, 0, inputByteArray.Length);
cs.FlushFinalBlock();
return Encoding.Default.GetString(ms.ToArray());
}
// MD5 32 位
public static String Encrypt32(String convertString)
{
MD5 md5 = new MD5CryptoServiceProvider();
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(convertString);
bytes = md5.ComputeHash(bytes);
md5.Clear();
string ret = "";
for (int i = 0; i < bytes.Length; i++)
{
ret += Convert.ToString(bytes[i], 16).PadLeft(2, '0');
}
return ret.PadLeft(32, '0').ToLower();
}
// MD5 16 位
public static string Encrypt16(string convertString)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
string t2 = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(convertString)), 4, 8);
t2 = t2.Replace("-", "");
return t2.ToLower();
}
}
}

编码时:

1
2
3
//LoginPwd--要加密的数据。也可以将二进制值传递给此函数。此参数区分大小写,即使是在不区分大小写的数据库中也是如此。
//Master--用来对 LoginPwd 进行加密的加密密钥。解密时必须使用同一密钥才能获得原始值。此参数区分大小写,即使是在不区分大小写的数据库中也是如此。
string Pwd = DBTools.Encryption.Encrypt(LoginPwd, "Master");

与大多数口令一样,最好选择无法被轻易猜到的密钥值。建议选择满足以下条件的密钥值:长度至少为 16 个字符,混合使用大小写并包含数字、字母和特殊字符。每次要对数据进行解密时,都需要使用此密钥。

解码时:

1
string Pwd = DBTools.Encryption.Decrypt(LoginPwd, "Master");

##总结

魔高一丈道高一尺,遇到外挂不要害怕,他们并不可怕,钻研外挂的原理,从源头上击垮他们。在项目立项之初,也要有数据加密的意识,一些重要的运算放在服务器端,客户端只做展示,不给外挂可乘之机。

分享到