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

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

感谢StackOverflow的响应,我现在从Adobe找到了更多的信息- http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html和https://github.com/mikechambers/as3corelib -我认为我可以使用加密。但我不确定这是否能让我绕过CheatEngine。

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

主要的问题似乎是TamperData和LiveHTTP报头,但我知道还有更高级的黑客工具,比如CheatEngine(感谢Mark Webster)


当前回答

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

Flash甚至比你想象的更容易进行反向工程,因为字节码有良好的文档记录,并描述了一种高级语言(Actionscript)——当你发布一款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匹配(如果跳过这一步,解密只会产生随机的,可能非常高的高分)。

So now the attacker decompiles your Flash code and quickly finds the AES code, which sticks out like a sore thumb, although even if it didn't it'd be tracked down in 15 minutes with a memory search and a tracer ("I know my score for this game is 666, so let's find 666 in memory, then catch any operation that touches that value --- oh look, the high score encryption code!"). With the session key, the attacker doesn't even have to run the Flash code; she grabs a game launch token and a session key and can send back an arbitrary high score.

你现在已经到了大多数开发者放弃的时候了——在与攻击者纠缠了几个月之后:

用异或操作打乱AES键 用计算键的函数替换键字节数组 在二进制文件中散布假密钥加密和高分帖子。

这基本上是在浪费时间。不用说,SSL也帮不了你;当两个SSL端点中的一个是邪恶的时,SSL无法保护您。

以下是一些能够有效减少高分作弊的方法:

Require a login to play the game, have the login produce a session cookie, and don't allow multiple outstanding game launches on the same session, or multiple concurrent sessions for the same user. Reject high scores from game sessions that last less than the shortest real games ever played (for a more sophisticated approach, try "quarantining" high scores for game sessions that last less than 2 standard deviations below the mean game duration). Make sure you're tracking game durations serverside. Reject or quarantine high scores from logins that have only played the game once or twice, so that attackers have to produce a "paper trail" of reasonable looking game play for each login they create. "Heartbeat" scores during game play, so that your server sees the score growth over the lifetime of one game play. Reject high scores that don't follow reasonable score curves (for instance, jumping from 0 to 999999). "Snapshot" game state during game play (for instance, amount of ammunition, position in the level, etc), which you can later reconcile against recorded interim scores. You don't even have to have a way to detect anomalies in this data to start with; you just have to collect it, and then you can go back and analyze it if things look fishy. Disable the account of any user who fails one of your security checks (for instance, by ever submitting an encrypted high score that fails validation).

记住,你只是在阻止高分欺诈。你无法阻止它的发生。如果你的游戏中存在金钱风险,那么总有人会打败你所想出的任何系统。我们的目标不是阻止这次袭击;这是为了让攻击变得更加昂贵,而不仅仅是精通游戏并击败它。

其他回答

使用已知(私有)可逆密钥进行加密是最简单的方法。我不完全使用AS,所以我不确定有哪些类型的加密提供商。

但你也可以加入游戏长度(同样是加密的)和点击次数等变量。

所有这类事情都可以逆向工程,所以可以考虑扔进一堆垃圾数据来迷惑人们。

编辑:可能也值得加入一些PHP会话。当玩家点击“开始游戏”时便开始游戏,并记录时间。当他们提交分数时,你可以检查他们是否有一个开放的游戏,他们没有过早或过大地提交分数。

也许有必要计算出一个标量,比如每秒钟/分钟游戏的最大分数。

这两件事都不是不可避免的,但这将有助于在Flash之外的地方设置一些逻辑,让人们可以看到它。

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

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

I made kind of workaround... I had a gave where scores incremented ( you always get +1 score ). First, I started to count from random num (let's say 14 ) and when I display the scores, just showed the scores var minus 14. This was so if the crackers are looking for example for 20, they won't find it ( it will be 34 in the memory ). Second, since I know what the next point should be... I used adobe crypto library, to create the hash of what the next point should be. When I have to increment the scores, I check if the hash of the incremented scores is equal to the hash is should be. If the cracker have changed the points in the memory, the hashes are not equal. I perform some server-side verification and when I got different points from game and from the PHP, I know that cheating were involved. Here is snippet ot my code ( I'm using Adobe Crypto libraty MD5 class and random cryptography salt. callPhp() is my server side validation )

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游戏来说,这是一项艰巨的任务,但当涉及到奖品时,有些人就会变得贪婪。如果你需要任何帮助,给我写个便条。

欢呼,图标

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

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

这样做的缺点是,用户每次加载flash电影只能提交一个高分——你不得不强迫他们刷新/重新加载SWF,然后他们才能再次播放新的分数。