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

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

我真正需要的是在Flash/PHP中最强的加密,以及一种防止人们调用PHP页面而不是通过我的Flash文件的方法。我在过去尝试了一些简单的方法,对一个分数进行多次调用,完成一个校验和/斐波那契序列等,也用Amayeta SWF加密混淆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)

30145 次浏览

你可能问错了问题。你似乎专注于人们通过游戏获得高分的方法,但阻止特定的方法也仅此而已。我没有TamperData的经验,所以我不能说。

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

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

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

所有像可以这样的事情都是逆向工程,所以考虑扔进一堆垃圾数据来甩掉人们的气味。

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

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

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

每当你的高分系统是基于这样一个事实,即Flash应用程序通过网络发送未加密/未签名的高分数据时,这些数据就会被拦截和操纵/重放。答案由此而来:加密(体面地!)或对高分数据进行加密签名。这至少使人们更难破解您的高分系统,因为他们需要从您的SWF文件中提取密钥。很多人可能会当场放弃。另一方面,只需要一个人就可以提取密钥并将其张贴在某个地方。

真正的解决方案包括在Flash应用程序和高分数据库之间进行更多的通信,以便后者能够验证给定的分数是否真实。这可能很复杂,这取决于你的游戏类型。

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

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

想要得到你想要的是不可能的。Flash应用程序的内部始终是部分可访问的,特别是当你知道如何使用CheatEngine之类的东西时,这意味着无论你的网站和browser<->服务器通信有多安全,它仍然是相对容易克服的。

一个简单的方法是提供你的高分值和它自己的分数的加密散列。例如,当通过HTTP GET发布结果时: http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095 < / >强

当计算这个校验和时,应该使用一个共享秘密;这个秘密永远不应该通过网络传输,而应该在PHP后端和flash前端中硬编码。上面的校验和是通过将字符串“秘密”前置到分数“500”,并通过md5sum运行来创建的。

虽然该系统将防止用户发布任意分数,但它不能防止“重放攻击”,即用户重新发布先前计算的分数和哈希组合。在上面的例子中,得分为500总是会产生相同的哈希字符串。通过在要散列的字符串中加入更多信息(如用户名、时间戳或IP地址),可以降低部分风险。虽然这不会阻止数据的重放,但它将确保一组数据在单一时间只对单一用户有效。

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

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

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

作为一种替代方法,可以通过强制客户端通过HTTPS与服务器通信,并确保客户端预先配置为只信任由您单独有权访问的特定证书颁发机构签署的证书来避免这种来回。

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

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

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

这是网络游戏和竞赛的一个经典问题。你的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匹配(如果跳过这一步,解密只会产生随机的,可能非常高的高分)。

所以现在攻击者反编译了你的Flash代码,并迅速找到了AES代码,这就像一个令人头疼的拇指,即使它没有,它也会在15分钟内被内存搜索和跟踪器追踪到(“我知道我在这款游戏中的分数是666,所以让我们在内存中找到666,然后捕捉任何触及该值的操作——哦,看,高分加密代码!”)。使用会话密钥,攻击者甚至不需要运行Flash代码;她可以获得一个游戏启动令牌和一个会话密钥,并可以返回任意的高分。

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

  • 用异或操作打乱AES键

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

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

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

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

  • 要求登录方能玩游戏,让登录产生会话cookie,不允许在同一会话中启动多个未完成的游戏,也不允许同一用户同时启动多个会话。

  • 拒绝那些持续时间少于真实游戏最短时间的游戏回合中的高分(更复杂的方法是,尝试着将高分“隔离”在少于平均游戏持续时间2个标准差的游戏回合中)。确保你在服务器端跟踪游戏持续时间。

  • 拒绝或隔离那些只玩过一两次游戏的登录用户的高分,这样攻击者就必须为他们创造的每个登录用户提供合理的游戏玩法的“书面记录”。

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

  • 游戏过程中的“快照”游戏状态(游戏邦注:例如,弹药数量,关卡中的位置等),你可以稍后将其与记录的中期分数相匹配。你甚至不需要有一种方法来检测这些数据中的异常;你只需要收集它,然后如果事情看起来可疑,你可以回去分析它。

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

记住,你只是在阻止高分欺诈。有没有什么你可以做来防止if。如果你的游戏中存在金钱风险,那么总有人会打败你所想出的任何系统。目标不是停止此攻击;这是为了让攻击变得更加昂贵,而不仅仅是精通游戏并击败它。

我喜欢tpqf所说的,但是当作弊被发现时,与其禁用一个帐户,不如实施一个蜜罐,这样每当他们登录时,他们都会看到他们被黑客攻击的分数,并且永远不会怀疑他们已经被标记为喷子。谷歌为“phpBB MOD Troll”,你会看到一个巧妙的方法。

我通常会在高分条目中加入游戏回合的“幽灵数据”。所以如果我在制作一款赛车游戏,我就会包含回放数据。你通常已经拥有关于重玩功能或幽灵赛车功能的重玩数据(游戏邦注:与你的上一场比赛进行比赛,或与排行榜上排名14的玩家的幽灵进行比赛)。

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

如果你的目标是将高分列表保持在网上,直到时间结束,而没有人需要查看它们,这不会给你带来太多好处。

根据我的经验,这最好是一个社会工程问题,而不是一个编程问题。与其专注于让作弊变得不可能,不如专注于通过消除作弊动机来让它变得无聊。例如,如果主要动机是公开可见的高分,那么简单地延迟高分的显示时间就可以通过消除作弊者的正反馈循环来显著减少作弊。

一个新的流行街机mod的方式是,它将数据从flash发送到php,返回到flash(或重新加载它),然后返回到php。这允许你做任何你想要比较数据以及绕过后数据/解密黑客之类的事情。一种方法是通过从php中分配2个随机值到flash中(即使运行实时flash数据抓取器也无法抓取或看到),使用数学公式将随机值与分数相加,然后使用相同的公式检查它,以查看当它最终进入php时分数是否匹配。这些随机值永远都不可见,而且它还会计时发生的事务,如果超过几秒钟,那么它也会标记为作弊,因为它假设你已经停止发送,试图找出随机值或通过某种类型的密码运行数字,以返回可能的随机值与分数值进行比较。

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

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

这是有办法的。这里有一个类:http://divillysausages.com/blog/safenumber_and_safeint

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

另外,看看PushButton引擎背后的一些人的视频,他们谈论了一些不同的对抗黑客的方法:http://zaa.tv/2010/12/the-art-of-hacking-flash-games/。他们是这门课背后的灵感。

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

这只能通过保留所有游戏逻辑都在服务器端来实现,它也在内部存储分数,而用户不知道。出于经济和科学原因,人类无法将这一理论应用于除回合制游戏外的所有游戏类型。例如,保持物理在服务器端的计算是昂贵的,很难得到响应的速度。甚至有可能,当玩国际象棋时,任何人都可以将AI象棋玩法与对手匹配。因此,更好的多人游戏也应该包含随需应变的创造力。

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

可能会定期将数据发送回服务器并进行一些验证。不要把注意力集中在客户端代码上,即使您的应用程序就在客户端代码上。

我想了个变通办法…我有一个分数递增的地方(你总是得到+1分)。首先,我从随机num开始计数(假设是14),当我显示分数时,只显示分数var - 14。这是为了让破解者找不到20(在内存中是34)。其次,既然我知道下一个要点是什么……我使用adobe加密库,来创建下一个点应该是的哈希值。当我要增加分数时,我检查增加的分数的哈希值是否等于应该得到的哈希值。如果cracker改变了内存中的点,哈希值就不相等。我执行了一些服务器端验证,当我从游戏和PHP中得到不同的分数时,我知道作弊涉及其中。下面是我的代码片段(我使用Adobe Crypto库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游戏来说,这是一项艰巨的任务,但当涉及到奖品时,有些人就会变得贪婪。如果你需要任何帮助,给我写个便条。

欢呼,图标

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