阻止人们入侵Flash游戏的基于PHP的高分表的最好方法是什么


212

我说的是一个动作游戏,没有最高分数限制,也没有办法通过重播动作等来验证服务器上的分数。

我真正需要的是Flash / PHP中最强大的加密,以及一种防止人们通过我的Flash文件以外的方式调用PHP页面的方法。过去,我尝试过一些简单的方法,可以对单个分数进行多次调用,并完成校验和/斐波那契序列等,还使用Amayeta SWF Encrypt对SWF进行混淆,但最终它们都被黑了。

多亏了StackOverflow的回应,我现在已经从Adobe找到了更多信息-http: //www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.htmlhttps://github.com/mikechambers/as3corelib-我想我可以用于加密。不确定这是否会让我了解CheatEngine。

如果它们不同,我需要知道AS2和AS3的最佳解决方案。

主要问题似乎是TamperData和LiveHTTP标头之类的,但是我知道还有更先进的黑客工具-CheatEngine(感谢Mark Webster)


您应该设置分数限制,没有理由为什么您不能按每个级别,总计等...
mpm 2012年

第一条链接不再可用
Mike

Answers:


415

这是互联网游戏和竞赛的经典问题。您的Flash代码可与用户一起确定游戏得分。但是用户不受信任,并且Flash代码在用户的计算机上运行。你是SOL。您无法采取任何措施来阻止攻击者伪造高分:

  • Flash甚至比您想象的要容易进行逆向工程,因为字节码已被很好地记录并描述了高级语言(动作脚本)---发布Flash游戏时,无论您是否在发布源代码,知道与否。

  • 攻击者控制Flash解释器的运行时内存,以便知道如何使用可编程调试器的任何人都可以随时更改任何变量(包括当前得分),或更改程序本身。

对您系统的最简单攻击是通过代理运行游戏的HTTP流量,捕获高分保存,然后以更高的分数重播。

您可以通过将每个高分存储区绑定到游戏的单个实例来尝试阻止此攻击,例如,在游戏启动时通过将加密令牌发送给客户端,如下所示:

hex-encoding( AES(secret-key-stored-only-on-server, timestamp, user-id, random-number))

(您也可以使用会话cookie来达到相同的效果)。

游戏代码以高分数保存将令牌返回给服务器。但是,攻击者仍然可以再次启动游戏,获得令牌,然后立即将该令牌粘贴到重播的高分保存中。

因此,接下来,您不仅要馈送令牌或会话cookie,还要馈送高分数加密的会话密钥。这将是一个128位AES密钥,它本身已使用硬编码到Flash游戏中的密钥进行了加密:

hex-encoding( AES(key-hardcoded-in-flash-game, random-128-bit-key))

现在,在游戏发布高分之前,它将解密高分加密会话密钥,这是可以完成的,因为您已将高分加密会话密钥解密密钥硬编码为Flash二进制文件。您可以使用此解密密钥以及高分的SHA1哈希来加密高分:

hex-encoding( AES(random-128-bit-key-from-above, high-score, SHA1(high-score)))

服务器上的PHP代码检查令牌以确保请求来自有效的游戏实例,然后解密加密的高分,检查以确保高分与高分的SHA1匹配(如果跳过此步骤) ,解密只会产生随机的,可能很高的分数)。

因此,现在,攻击者反编译您的Flash代码,并迅速找到AES代码,该代码像拇指酸痛地伸出来,尽管即使没有找到,也可以在15分钟内通过内存搜索和跟踪器对其进行跟踪(“我知道我在这款游戏中的得分是666,所以让我们在内存中找到666,然后进行任何涉及该值的操作-哦,高分加密代码!”)。有了会话密钥,攻击者甚至不必运行Flash代码。她获取了游戏启动令牌和会话密钥,并可以发回任意高分。

现在,大多数开发人员都放弃了---通过以下方式放弃或破坏攻击者几个月:

  • 使用XOR操作加扰AES密钥

  • 用计算密钥的函数替换密钥字节数组

  • 在整个二进制文件中散布伪造的密钥加密和高分发布。

这全都是浪费时间。不用说,SSL也不会帮助您。当两个SSL端点之一为恶意时,SSL无法保护您。

以下是一些可以真正减少高分欺诈的事情:

  • 需要登录才能玩游戏,让登录产生会话cookie,并且不允许在同一会话上多次启动游戏,也不允许同一用户进行多个并发会话。

  • 拒绝持续时间少于有史以来最短的真实游戏的游戏会话的高分(对于更复杂的方法,对于持续时间少于平均游戏时间2个标准偏差的游戏会话,尝试“隔离”高分)。确保您正在服务器端跟踪游戏时间。

  • 从仅玩过一次或两次游戏的登录中拒绝或隔离高分,因此攻击者必须为创建的每个登录产生合理外观的“纸迹”。

  • 在游戏过程中,“ Heartbeat”得分很高,因此您的服务器可以看到一个游戏寿命中得分的增长。拒绝不遵循合理分数曲线的高分(例如,从0跳到999999)。

  • 游戏过程中的“快照”游戏状态(例如,弹药量,关卡中的位置等),您可以稍后将其与记录的临时得分进行核对。您甚至不必以某种方式来检测此数据中的异常。您只需要收集它,然后可以回头再分析一下是否看起来很腥。

  • 禁用任何未通过您的安全检查的用户的帐户(例如,通过提交未通过验证的加密高分)。

请记住,虽然您只是在阻止高分欺诈。有没有什么可以做,以防止误。如果您的游戏中有钱,那么有人会击败您想出的任何系统。目的不是阻止这种攻击;这是使攻击变得更加昂贵,而不仅仅是真正擅长于击败游戏。


23
我建立了一个在线游戏社区,我们设计了整个奖励系统,以奖励排名前10位的得分,而不仅仅是得分最高的得分,而且效果很好,因此,回避问题也有帮助!
grapefrukt

1
真好!简而言之,除非游戏在服务器上运行(或至少在服务器上运行健全性检查),否则您无法防止滥用行为。多人射击游戏也存在同样的问题:如果修改客户端以产生更好的瞄准效果,则无法在网络协议中阻止这一点。
Kerrek SB 2011年

3
我猜一个人也可以记录所有用户操作并在服务器上重播它们以验证/确定高分
I3ck

25

您可能会问错问题。您似乎专注于人们用来打高分列表的方法,但是到目前为止,阻止特定方法仍然有效。我没有使用TamperData的经验,所以我无法对此讲话。

您应该问的问题是:“我如何验证提交的分数是有效和真实的?” 具体实现方式取决于游戏。对于非常简单的益智游戏,您可以将比分以及特定的开始状态和导致结束状态的移动顺序一起发送,然后使用相同的移动在服务器端重新运行游戏。确认指定的分数与计算出的分数相同,并且仅在匹配时接受分数。


2
这是我过去在投票系统中使用的一种技术。您无法阻止他人作弊,但是您可以做的是记录他们的行为,以便检查是否有作弊行为。对于我们的投票系统,我们曾经存储时间戳和IP地址。[续]
路加福音

1
这样,我们至少可以看到作弊行为的模式,并在必要时取消人们的资格。我们已经成功使用了多次。
路加福音

19

一种简单的方法是提供您的高分值的加密哈希值以及它自己的分数。例如,通过HTTP GET发布结果时:http : //example.com/highscores.php? score= 500& checksum= 0a16df3dc0301a36a34f9065c3ff8095

计算此校验和时,应使用共享机密。此机密绝不应通过网络传输,而应在PHP后端和Flash前端中进行硬编码。上面的校验和是通过在字符串“ secret ”之前加上分数“ 500 ”并通过md5sum运行而创建的。

尽管此系统将阻止用户发布任意分数,但是它不能阻止“重播攻击”,即用户重新发布先前计算的分数和哈希组合。在上面的示例中,分数500始终会产生相同的哈希字符串。通过将更多信息(例如用户名,时间戳或IP地址)并入要散列的字符串中,可以减轻某些风险。尽管这不会阻止数据的重放,但可以确保一组数据仅一次对单个用户有效。

为了防止发生任何重播攻击,必须创建某种类型的质询-响应系统,例如:

  1. Flash游戏(“客户端”)执行不带参数的HTTP GET http://example.com/highscores.php。该页面返回两个值:一个随机生成的值,以及与共享机密结合的该盐值的加密哈希。此盐值应存储在待处理查询的本地数据库中,并应具有与其相关的时间戳记,以便它可能在一分钟后“过期”。
  2. Flash游戏将盐值与共享机密结合在一起,并计算哈希值以验证其是否与服务器提供的值匹配。必须执行此步骤,以防止用户篡改盐值,因为它会验证盐值实际上是由服务器生成的。
  3. Flash游戏将盐值与共享的机密,高分值和任何其他相关信息(昵称,ip,时间戳)结合在一起,并计算哈希值。然后,它通过HTTP GET或POST将该信息以及盐值,高分和其他信息发送回PHP后端。
  4. 服务器以与客户端相同的方式组合接收到的信息,并计算散列以验证其是否与客户端提供的散列相匹配。然后,它还验证盐值仍然有效,如未决查询列表中所列。如果这两个条件都成立,则将高分写入高分表,并向客户端返回签名的“成功”消息。它还将从挂起的查询列表中删除盐值。

请记住,如果用户可以访问共享机密,则上述任何一种技术的安全性都会受到损害。

或者,可以通过强制客户端通过HTTPS与服务器进行通信,并确保将客户端预先配置为仅信任由特定证书颁发机构签名的证书,而您自己可以访问这些证书,来回避免某些往返事务。


11

我喜欢tpqf所说的话,但是与其在发现作弊时禁用帐户,不如实施蜜罐,这样每当他们登录时,他们都可以看到自己的黑名单,并且从不怀疑自己已被标记为巨魔。Google搜索“ phpBB MOD Troll”,您将看到一个巧妙的方法。


1
这种方法的问题在于,这些作弊行为在留言板等上相互炫耀,因此并不能真正阻止它们。
伊恩

7
我不同意。这听起来很聪明。
bzlm

4

在接受的答案中,tqbf提到您可以对分数变量进行内存搜索(“我的分数是666,所以我在内存中寻找数字666”)。

有一种解决方法。我在这里上课:http : //divillysausages.com/blog/safenumber_and_safeint

基本上,您有一个对象来存储您的分数。在setter中,它将您传递给它的值与一个随机数(+和-)相乘,在getter中,您将保存的值除以随机乘数以得到原始值。这很简单,但是有助于停止内存搜索。

另外,请查看来自PushButton引擎背后的一些人的视频,他们讨论了一些可与黑客作斗争的不同方法:http: //zaa.tv/2010/12/the-art-of-hacking-flash-游戏/。他们是课堂背后的灵感。


4

我做了种变通办法...我给了分数增加的分数(您总会得到+1分数)。首先,我开始从随机数开始计数(比如说14),当我显示分数时,只显示分数var减14。这就是这样的情况,如果饼干正在寻找20,他们将找不到它(在内存中将为34)。其次,由于我知道下一点应该是什么...我使用了Adobe加密库,创建了下一点应该是什么的哈希。当我必须增加分数时,我检查增加的分数的哈希值是否等于该哈希值。如果饼干更改了内存中的点,则哈希值不相等。我执行一些服务器端验证,当我从游戏和PHP中获得不同的观点时,我知道其中涉及作弊。这是我的代码段(我使用的是Adobe Crypto libraty MD5类和随机加密盐。callPhp()是我的服务器端验证)

private function addPoint(event:Event = null):void{
            trace("expectedHash: " + expectedHash + "  || new hash: " + MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt) );
            if(expectedHash == MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt)){
                SCORES +=POINT;
                callPhp();
                expectedHash = MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt);
            } else {
                //trace("cheat engine usage");
            }
        }

使用这种技术+ SWF模糊处理,我能够阻止饼干。另外,当我将分数发送到服务器端时,我使用了自己的小型加密/解密功能。这样的事情(不包括服务器端代码,但是您可以看到算法并用PHP编写):

package  {

    import bassta.utils.Hash;

    public class ScoresEncoder {

        private static var ranChars:Array;
        private static var charsTable:Hash;

        public function ScoresEncoder() {

        }

        public static function init():void{

            ranChars = String("qwertyuiopasdfghjklzxcvbnm").split("")

            charsTable = new Hash({
                "0": "x",
                "1": "f",
                "2": "q",
                "3": "z",
                "4": "a",
                "5": "o",
                "6": "n",
                "7": "p",
                "8": "w",
                "9": "y"

            });

        }

        public static function encodeScore(_s:Number):String{

            var _fin:String = "";

            var scores:String = addLeadingZeros(_s);
            for(var i:uint = 0; i< scores.length; i++){
                //trace( scores.charAt(i) + " - > " + charsTable[ scores.charAt(i) ] );
                _fin += charsTable[ scores.charAt(i) ];
            }

            return _fin;

        }

        public static function decodeScore(_s:String):String{

            var _fin:String = "";

            var decoded:String = _s;

            for(var i:uint = 0; i< decoded.length; i++){
                //trace( decoded.charAt(i) + " - > "  + charsTable.getKey( decoded.charAt(i) ) );
                _fin += charsTable.getKey( decoded.charAt(i) );
            }

            return _fin;

        }

        public static function encodeScoreRand(_s:Number):String{
            var _fin:String = "";

            _fin += generateRandomChars(10) + encodeScore(_s) + generateRandomChars(3)

            return _fin;
        }

        public static function decodeScoreRand(_s:String):Number{

            var decodedString:String = _s;
            var decoded:Number;

            decodedString = decodedString.substring(10,13);         
            decodedString = decodeScore(decodedString);

            decoded = Number(decodedString);

            return decoded;
        }

        public static function generateRandomChars(_length:Number):String{

            var newRandChars:String = "";

            for(var i:uint = 0; i< _length; i++){
                newRandChars+= ranChars[ Math.ceil( Math.random()*ranChars.length-1 )];
            }

            return newRandChars;
        }

        private static function addLeadingZeros(_s:Number):String{

            var _fin:String;

            if(_s < 10 ){
                 _fin = "00" + _s.toString();
            }

            if(_s >= 10 && _s < 99 ) {
                 _fin = "0" + _s.toString();
            }

            if(_s >= 100 ) {
                _fin = _s.toString();
            }           

            return _fin;
        }


    }//end
}

然后,我将变量与其他虚假变量一起发送,这只是在过程中迷路了……对于小型Flash游戏来说这是很多工作,但是在涉及奖品的情况下,有些人会变得贪婪。如果您需要任何帮助,请给我写一封PM。

干杯,伊科


3

使用已知的(私有)可逆密钥加密将是最简单的方法。我还没有完全了解AS,所以不确定是否有哪种加密提供程序。

但是您可以包含一些变量,例如游戏时长(再次加密)和点击计数。

诸如此类的所有事情都可以进行逆向工程,因此请考虑放入大量垃圾数据以使人们摆脱气味。

编辑:也许值得在一些PHP会话中使用。当他们单击开始游戏并(如该帖子的评论所述)记录时间时,开始会话。当他们提交分数时,您可以检查他们是否确实有一个开放游戏,并且他们提交的分数没有太快或太大。

也许应该计算出一个标量,看看每秒钟/分钟的最高得分是多少。

这些事情都不是不可回避的,但是在人们可以看到的Flash中没有某些逻辑会有所帮助。


遵循此方法的任何人还必须包括一个时间,并验证时间,否则您很容易受到重放攻击。
Tom Ritter

3

以我的经验,这最好是作为社会工程问题而不是编程问题来解决。与其专注于使其无法作弊,不如着眼于通过消除作弊动机使其变得无聊。例如,如果主要诱因是公开可见的高分,则仅需延迟显示高分的时间,即可通过删除作弊者的积极反馈回路来大大减少作弊。


12
但这也消除了实际玩家即时获得高分的积极反馈,因此您在降低游戏质量。
克里斯·加勒特

3

您不能信任客户端返回的任何数据。验证需要在服务器端执行。我不是游戏开发商,但我确实制作商业软件。在这两种情况下,都会涉及金钱,并且人们会破坏客户端的混淆技术。

也许定期将数据发送回服务器并进行一些验证。即使您的应用程序存在,也不要专注于客户端代码。


2

每当您的高分系统基于Flash应用程序通过网络发送未经加密/未签名的高分数据的事实时,就可以对其进行拦截和处理/重放。答案是:加密(体面!)或用密码签名高分数据。至少,这使人们更难破解您的高分系统,因为他们需要从您的SWF文件中提取密钥。许多人可能会在那里放弃。另一方面,只需一个人就可以提取密钥并将其发布到某个地方。

实际的解决方案需要Flash应用程序与高分数据库之间进行更多的通信,以便高分数据库可以验证给定的分数在一定程度上是现实的。这可能很复杂,具体取决于您拥有哪种游戏。


2

因为它很容易反编译SWF,所以没有办法使其完全不可破解,然后,熟练的开发人员黑客可以跟踪您的代码,并弄清楚如何绕过您可能采用的任何加密系统。

但是,如果您只是想通过使用简单的工具(例如TamperData)来阻止孩子作弊,则可以生成一个加密密钥,然后在启动时将其传递给SWF。然后使用类似http://code.google.com/p/as3crypto/的方法对高分进行加密,然后再将其传递回PHP代码。然后在服务器端将其解密,然后再将其存储在数据库中。


2

您正在谈论所谓的“客户端信任”问题。因为客户(用现金,即在浏览器中运行的SWF)正在做它打算做的事情。保存高分。

问题是您要确保“保存分数”请求来自您的Flash电影,而不是某些任意的HTTP请求。一个可能的解决方案是在请求时(使用flasm)将服务器生成的令牌编码到SWF中,该令牌必须随请求一起保存才能获得高分。服务器保存该分数后,令牌将过期,不能再用于请求。

这样做的缺点是,每次加载Flash电影,用户只能提交一个高分-您必须强迫他们刷新/重新加载SWF,然后他们才能再次播放新分。


2

我通常在游戏会话的“幽灵数据”中包含高分条目。因此,如果我要制作赛车游戏,则会包含重播数据。您通常已经具有用于重播功能或重影竞速功能的重播数据(在上一场比赛中对战,或在排行榜上与14号花花公子对战)。

检查这些是非常费力的工作,但是如果目标是验证比赛中排名前10位的参赛作品是否合法,这可能是对其他人已经指出的安全措施的有用补充。

如果目标是让高分列表在线保留到最后,而无需任何人查看,则不会带来太多收益。


2

新的流行街机游戏mod的执行方式是将数据从闪存发送到php,然后再发送到闪存(或重新加载),然后再发送到php。这使您可以做任何您想比较数据的事情,并且绕过发布数据/解密技巧等。一种实现方法是,将php中的2个随机值分配给Flash(即使运行实时Flash数据采集器也无法捕获或看到),使用数学公式将分数与随机值相加,然后使用用相同的公式将其反转以查看分数最终与php匹配时是否与之匹配。这些随机值从不可见,并且还会对交易发生的时间以及是否

如果您问我,这似乎是一个很好的解决方案,有人使用此方法是否会遇到任何问题?还是可能的解决方法?


Wireshark。Wireshark始终是漏洞。
aehiilrs

是的,香港专业教育学院尝试了这种确切的方法...好吧...我以某种方式使合法用户被记录为作弊者(该功能一直在我的计算机上运行,​​但在其他一些计算机上却不起作用)...所以现在我只是允许作弊...我记录未通过上述检查的人&我记录分数很高的人,然后手动使他们受辱...通过使他们的分数* -1:P他们通常会开玩笑。
Sigtran

1
“执行此操作的一种方法是,将php中的2个随机值分配给Flash(即使运行实时Flash数据获取器,也无法捕获或看到)”-请启发我,有没有一种方法可以将php中的值传递给Flash不受萤火虫或其他任何工具监控而闪烁?
mkto 2011年

2

我认为最简单的方法是,每次游戏注册要添加的乐谱时,都调用诸如RegisterScore(score)之类的函数,然后对其进行编码,打包并将其作为字符串发送到php脚本。php脚本将知道如何正确解码。这将停止任何直接调用php脚本的调用,因为任何试图强制得分的尝试都会导致解压缩错误。


2

只有通过将所有游戏逻辑都保留在服务器端,这也可能在用户不知情的情况下在内部存储得分。由于经济和科学原因,人类无法将此理论应用于除回合制以外的所有游戏类型。例如,在服务器端保持物理状态在计算上是昂贵的,并且难以像手速一样做出响应。甚至有可能,在下国际象棋时,任何人都可以将AI国际象棋的游戏玩法与对手匹敌。因此,更好的多人游戏也应包含按需创意。


1

真正不可能实现您想要的。Flash应用程序的内部组件始终可以部分访问,尤其是当您知道如何使用CheatEngine之类的东西时,这意味着无论您的网站和浏览器服务器的通信多么安全,克服它仍然相对容易。


1

通过AMFPHP与后端通信可能是一个好主意。至少应阻止那些懒惰的人尝试通过浏览器控制台推送结果。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.