代码之家  ›  专栏  ›  技术社区  ›  Iain

阻止人们入侵Flash游戏中基于PHP的highscore表的最佳方法是什么

  •  212
  • Iain  · 技术社区  · 16 年前

    我说的是一款动作游戏,没有分数上限,也无法通过重放动作等方式在服务器上验证分数。

    多亏了StackOverflow的回复,我现在从Adobe找到了更多的信息- http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html https://github.com/mikechambers/as3corelib -我想我可以用它来加密。但我不确定这是否能让我绕过引擎。

    主要的问题似乎是篡改数据和LiveHTTP头之类的东西,但我知道还有更高级的黑客工具,比如作弊引擎(感谢马克·韦伯斯特)

    18 回复  |  直到 13 年前
        1
  •  416
  •   tqbf    16 年前

    这是网络游戏和竞赛的经典问题。您的Flash代码与用户一起决定游戏的分数。但是用户是不可信的,Flash代码在用户的计算机上运行。你是索尔。您无法阻止攻击者伪造高分:

    • Flash甚至比你想象的更容易进行反向工程,因为字节码有很好的文档记录,并且描述了一种高级语言(Actionscript)——当你发布Flash游戏时,你发布的是你的源代码,不管你是否知道。

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

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

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

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

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

    所以接下来,您不仅要提供令牌或会话cookie,还要提供一个高分加密会话密钥。这将是一个128位AES密钥,本身使用闪存游戏中硬编码的密钥进行加密:

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

    现在在游戏发布高分之前,它会解密高分加密会话密钥,这是因为您将高分加密会话密钥解密密钥硬编码到闪存二进制文件中。使用此解密密钥以及高分的SHA1散列加密高分:

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

    因此,现在攻击者反编译你的Flash代码,并迅速找到AES代码,它像一个酸痛的拇指一样突出,尽管即使没有,它也会在15分钟内通过内存搜索和跟踪程序被追踪到(“我知道我在这个游戏中的分数是666,所以让我们在内存中找到666,然后捕获任何触及该值的操作(哦,看,高分加密代码!”)。有了会话密钥,攻击者甚至不必运行闪存代码;她抓取游戏启动令牌和会话密钥,可以发回任意高分。

    你现在已经到了大多数开发人员都会放弃的地步——通过以下方式与攻击者进行一两个月的混战:

    • 用异或运算置乱AES密钥

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

    • 在二进制文件中散布假密钥加密和高分帖子。

    这几乎都是浪费时间。不用说,SSL也不会帮助您;当两个SSL端点中的一个是邪恶的时,SSL无法保护您。

    以下是一些可以实际减少高分欺诈的方法:

    • 需要登录才能玩游戏,让登录名生成会话cookie,并且不允许在同一会话上启动多个未完成的游戏,或为同一用户同时启动多个会话。

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

    • “心跳”在游戏过程中得分,这样服务器就可以在一次游戏的生命周期中看到分数的增长。拒绝不遵循合理分数曲线的高分(例如,从0跳到999999)。

    • 游戏过程中的“快照”游戏状态(例如,弹药量、关卡位置等),您可以稍后根据记录的临时分数进行核对。你甚至不需要有一种方法来检测这些数据中的异常;你只需要收集它,然后你可以回去分析,如果事情看起来可疑。

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

    没有什么 你可以做些什么来预防如果。如果你的游戏里有钱,有人会打败你想出的任何系统。我们的目标不是 停止 这次袭击;这是为了让进攻比真正擅长比赛并击败对手更昂贵。

        2
  •  25
  •   Stephen Deken    16 年前

    你可能问错了问题。你似乎专注于人们在高分排行榜上所使用的方法,但阻止特定的方法只会走这么远。我没有篡改数据的经验,所以我不能这么说。

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

        3
  •  19
  •   stormlash    16 年前

    一个简单的方法是提供highscore值的加密散列以及分数本身。例如,通过HTTP GET发布结果时: http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

    计算此校验和时,应使用共享密钥;这个秘密永远不应该通过网络传输,而是应该在PHP后端和flash前端中进行硬编码。上面的校验和是通过在字符串前加前缀创建的” “成功” 500 ,并通过md5sum运行它。

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

    1. http://example.com/highscores.php 没有参数。此页面返回两个值:随机生成的 值,以及该salt值与共享密钥组合的加密哈希。这个salt值应该存储在挂起查询的本地数据库中,并且应该有一个与之相关联的时间戳,以便它可能在一分钟后“过期”。
    2. flash游戏将salt值与共享秘密、高分值和任何其他相关信息(昵称、ip、时间戳)结合起来,并计算哈希值。然后,它通过HTTPGET或POST将此信息以及salt值、高分和其他信息发送回PHP后端。
    3. 服务器以与客户端相同的方式组合接收到的信息,并计算一个哈希值,以验证该哈希值是否与客户端提供的哈希值匹配。然后,它还会验证salt值是否仍然有效,如挂起的查询列表中所列。如果这两个条件都为真,它会将高分写入高分表,并向客户端返回一条签名的“成功”消息。它还从挂起的查询列表中删除salt值。

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

        4
  •  11
  •   DGM    16 年前

    我喜欢tpqf所说的,但与其在发现作弊时禁用帐户,不如实现一个蜜罐,这样无论何时登录,他们都可以看到自己被黑客攻击的分数,并且永远不会怀疑自己被标记为巨魔。谷歌搜索“phpBB MOD Troll”,你会看到一个巧妙的方法。

        5
  •  4
  •   divillysausages    13 年前

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

    这是有办法的。我在这里上课: http://divillysausages.com/blog/safenumber_and_safeint

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

    此外,请查看按钮引擎背后的一些人的视频,他们讲述了一些打击黑客的不同方法: http://zaa.tv/2010/12/the-art-of-hacking-flash-games/ . 他们是这门课背后的灵感来源。

        6
  •  4
  •   Chris Panayotoff    11 年前

    我做了一些变通。。。我得到了一个分数递增的分数(你总是得到+1分)。首先,我开始从random num(比如说14)开始计数,当我显示分数时,只显示var减14的分数。这是这样的,如果黑客正在寻找例如20,他们不会找到它(它将是34在内存中)。第二,既然我知道下一点应该是什么。。。我使用adobe crypto库,创建下一点的哈希值 应该是 . 当我必须增加分数时,我检查增加分数的哈希值是否等于哈希值。如果破解程序更改了内存中的点,则哈希值不相等。我执行一些服务器端验证,当我从游戏和PHP中得到不同的分数时,我知道其中涉及到作弊。下面是我的代码片段(我使用的是Adobe Crypto Library MD5类和随机加密salt.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游戏来说,这是一个很大的工作,但是在涉及奖品的地方,有些人会变得贪婪。如果你需要帮助,请给我写一封邮件。

        7
  •  3
  •   Oli    16 年前

    使用已知(私有)可逆密钥加密是最简单的方法。我不完全了解,所以我不确定有什么样的加密提供商。

    诸如此类 可以

    编辑:可能也值得加入一些PHP会话。当他们点击“开始游戏”时开始游戏,并记录时间。当他们提交分数时,你可以检查他们是否真的有一场公开赛,他们提交分数不是太快或太大。

    这两种东西都不是不可循环的,但是在人们看不到的地方有一些逻辑会有所帮助。

        8
  •  3
  •   Scott Reynen    16 年前

    根据我的经验,最好将其作为一个社会工程问题来处理,而不是编程问题。与其专注于让作弊变得不可能,不如专注于消除作弊的动机,让作弊变得无聊。例如,如果主要激励因素是公开可见的高分,那么只要在高分出现时延迟,就可以通过消除作弊者的正反馈循环,显著减少作弊行为。

        9
  •  3
  •   zeller    13 年前

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

    可能会定期将数据发送回服务器并进行一些验证。不要把重点放在客户端代码上,即使那是你的应用程序所在的地方。

        10
  •  2
  •   Jan Krüger    16 年前

    当您的highscore系统基于Flash应用程序通过网络发送未加密/未签名的highscore数据这一事实时,这些数据可以被截取和处理/重放。答案如下:加密(正派!)或加密签名高分数据。这至少让人们更难破解你的高分系统,因为他们需要从你的SWF文件中提取密钥。很多人可能会马上放弃。另一方面,只需要一个人提取钥匙并将其张贴到某个地方。

    真正的解决方案涉及Flash应用程序和highscore数据库之间的更多通信,以便后者可以验证给定的分数是否真实。这可能很复杂,取决于你玩的是哪种游戏。

        11
  •  2
  •   David Arno    16 年前

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

    http://code.google.com/p/as3crypto/ 在将高分传递回PHP代码之前对其进行加密。然后在服务器端对其进行解密,然后将其存储到数据库中。

        12
  •  2
  •   Peter Bailey    16 年前

    你说的是所谓的“客户信任”问题。因为客户端(在这个cash中,是一个在浏览器中运行的SWF)正在做它设计用来做的事情。保存高分。

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

        13
  •  2
  •   Lucas Meijer    16 年前

    我通常会把游戏中的“鬼数据”和高分输入一起包含进去。因此,如果我正在制作一个赛车游戏,我会包含回放数据。您通常已经有了重播功能或幽灵赛车功能的重播数据(与上一场比赛对抗,或与排行榜上的幽灵dude#14对抗)。

    检查这些是非常体力劳动,但如果目标是验证竞赛中前10名参赛者是否合法,这可能是对其他人已经指出的安全措施库的有益补充。

    如果目标是让高分列表一直在线到时间结束,而不需要任何人查看,这不会给你带来多少好处。

        14
  •  2
  •   Vaughn    15 年前

    一个新的流行的arcade mod的方式是,它将数据从flash发送到php,再返回到flash(或重新加载),然后返回到php。这允许你做任何你想比较数据的事情,也可以绕过后期数据/解密黑客等等。实现这一点的一种方法是将php中的2个随机值分配到flash中(即使运行实时flash数据采集程序,也无法获取或查看这些值),使用数学公式将分数与随机值相加,然后使用相同的公式对其进行检查,以反转分数,查看分数是否与之匹配,最后将分数转到php。这些随机值永远都不可见,它也会计算事务发生的时间,如果超过几秒钟,它也会将其标记为欺骗,因为它假定您已停止发送以尝试找出随机值,或通过某种类型的密码运行数字以返回可能的随机值进行比较使用分数值。

    如果你问我,这似乎是一个很好的解决方案,有人认为使用这种方法有什么问题吗?或者可能的解决方法?

        15
  •  2
  •   mathieu landry    13 年前

        16
  •  2
  •   hurturk    13 年前

    只有保持 服务器端的所有游戏逻辑 它还可以在用户不知情的情况下在内部存储分数。出于经济和科学原因,人类无法将这一理论应用于除回合制以外的所有游戏类型。例如,将物理保持在服务器端在计算上是昂贵的,而且很难像手的速度那样响应。甚至有可能,在下棋的时候,任何人都可以和对手的AI象棋比赛。因此,更好 多人游戏

        17
  •  1
  •   Wayne    16 年前

    CheatEngine ,这意味着无论您的网站和浏览器多么安全<-&燃气轮机;服务器通信是,它仍然将是相对简单的克服。

        18
  •  1
  •   m1gu3l    11 年前

    通过与后端通信可能是一个好主意 AMFPHP