ChatGPT解决这个技术问题 Extra ChatGPT

阻止人们破解基于 PHP 的 Flash 游戏高分表的最佳方法是什么

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

我真正需要的是 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)

你应该设置分数限制,没有理由你不能这样做,每个级别,等等......
第一个链接不再可用

t
tqbf

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

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 代码;她抓住一个游戏启动令牌和一个会话密钥,并可以发回任意高分。

您现在正处于大多数开发人员刚刚放弃的地步——通过以下方式与攻击者相处几个月:

使用 XOR 操作加扰 AES 密钥

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

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

这主要是浪费时间。不用说,SSL 也帮不了你。当两个 SSL 端点之一是邪恶的时,SSL 无法保护您。

以下是一些实际上可以减少高分欺诈的事情:

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

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

拒绝或隔离只玩过一次或两次游戏的登录名的高分,这样攻击者就必须为他们创建的每个登录名生成看起来合理的游戏玩法的“书面记录”。

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

游戏过程中的“快照”游戏状态(例如,弹药数量、关卡中的位置等),您可以稍后将其与记录的临时分数进行核对。您甚至不必从一开始就检测此数据中的异常情况;你只需要收集它,然后如果事情看起来可疑,你可以回去分析它。

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

请记住,您只是在这里阻止高分欺诈。没有什么可以阻止如果。如果你的游戏中有钱,那么有人会打败你想出的任何系统。目标不是阻止这种攻击;这是为了让攻击变得更昂贵,而不是仅仅在游戏中表现出色并击败它。


我创建了一个在线游戏社区,我们设计了整个奖励系统来奖励前 10 个百分位数的分数,而不仅仅是最高得分者,而且效果很好,所以尝试回避这个问题也有帮助!
好的!简而言之,除非游戏在服务器上运行(或至少在服务器上运行健全性检查),否则您无法防止滥用。多人射击游戏也有同样的问题:如果修改客户端以产生更好的瞄准效果,您无法在网络协议中阻止这种情况。
我想还可以记录所有用户操作并在服务器上重放它们以验证/确定高分
S
Stephen Deken

你可能问错了问题。您似乎专注于人们用来在高分榜上博弈的方法,但阻止特定方法仅止于此。我没有使用 TamperData 的经验,所以我无话可说。

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


这是我过去用于投票系统的一种技术。您无法阻止人们作弊,但是您可以做的是记录他们的行为,以便检查是否存在作弊行为。对于我们的投票系统,我们曾经存储时间戳和 IP 地址。 [续]
这样我们至少可以看到作弊行为的模式,并在必要时取消人们的资格。我们已经成功使用了很多次。
s
stormlash

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

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

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

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

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

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

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


D
DGM

我喜欢 tpqf 所说的,但与其在发现作弊时禁用帐户,不如实施一个蜜罐,这样每当他们登录时,他们就会看到他们被黑的分数,并且永远不会怀疑他们被标记为巨魔。谷歌搜索“phpBB MOD Troll”,您会看到一种巧妙的方法。


这种方法的问题是这些作弊者在留言板等上互相吹嘘,所以它不会真正阻止他们。
我不同意。这听起来是一个非常聪明的方法。
d
divillysausages

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

有办法解决这个问题。我在这里有一堂课:http://divillysausages.com/blog/safenumber_and_safeint

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

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


C
Chris Panayotoff

我做了一种解决方法......我给了分数增加的地方(你总是得到+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 游戏来说,这是很多工作,但在涉及奖品的情况下,有些人会变得贪婪。如果您需要任何帮助,请给我留言。

干杯,伊科


O
Oli

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

但是您可以包含诸如游戏长度(再次加密)和点击次数之类的变量。

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

编辑:在一些 PHP 会话中也可能值得一试。当他们点击开始游戏并(正如这篇文章的评论所说)记录时间时开始会话。当他们提交分数时,您可以检查他们实际上有一个开放的游戏,并且他们提交的分数不会太快或太大。

可能值得计算一个标量,看看每秒/分钟的最高得分是多少。

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


遵循此方法的任何人还必须包含时间,并验证时间,否则您很容易受到重放攻击。
S
Scott Reynen

根据我的经验,最好将其视为社会工程问题而不是编程问题。与其专注于让它不可能作弊,不如专注于通过消除作弊的动机来让它变得无聊。例如,如果主要动机是公开可见的高分,则只需延迟显示高分的时间就可以通过消除作弊者的积极反馈循环来显着减少作弊。


但这也消除了对实际玩家的即时高分的积极反馈,因此您正在降低游戏质量。
z
zeller

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

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


J
Jan Krüger

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

真正的解决方案涉及 Flash 应用程序和高分数据库之间的更多通信,以便后者可以验证给定分数是否有点现实。这可能很复杂,具体取决于您拥有的游戏类型。


D
David Arno

没有办法让它完全无法破解,因为它很容易反编译 SWF,然后熟练的开发者黑客可以追踪您的代码并找出如何绕过您可能使用的任何加密系统。

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


P
Peter Bailey

您正在谈论所谓的“客户信任”问题。因为客户端(在这个现金中,一个在浏览器中运行的 SWF)正在做一些它被设计做的事情。保存高分。

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

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


L
Lucas Meijer

我通常在高分条目中包含游戏会话的“幽灵数据”。因此,如果我正在制作赛车游戏,我会包含重播数据。您通常已经拥有重播功能或幽灵赛车功能的重播数据(与您的最后一场比赛比赛,或与排行榜上第 14 号的鬼魂比赛)。

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

如果目标是将高分列表保持在网上直到时间结束,而无需任何人查看,那么这不会给您带来太多好处。


V
Vaughn

一个新的流行街机模组的做法是将数据从 Flash 发送到 php,然后返回到 Flash(或重新加载),然后返回到 php。这使您可以做任何您想做的事情来比较数据以及绕过发布数据/解密黑客等。这样做的一种方法是将 2 个随机值从 php 分配到闪存中(即使运行实时闪存数据采集器,您也无法抓取或查看),使用数学公式将分数与随机值相加,然后使用检查它相同的公式来反转它以查看分数是否匹配它,当它最终到达 php 时。这些随机值永远不可见,并且它还会计算交易发生的时间,如果超过几秒钟,那么它也会将其标记为作弊,因为它假定您已停止发送以尝试找出随机值或运行这些数字通过某种类型的密码返回可能的随机值以与分数值进行比较。

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


线鲨。 Wireshark 始终是漏洞。
是的,我试过这个确切的方法......好吧......我不知何故让合法用户被记录为作弊者(该功能一直在我的电脑上工作,但它在其他一些电脑上不起作用)......所以现在我只是允许作弊...我记录没有通过上述检查的人,我记录分数非常高的人,然后手动滥用他们...通过使他们的分数* -1 :P 他们通常会很好地开玩笑。
“这样做的一种方法是将 2 个随机值从 php 分配到闪存中(即使运行实时闪存数据采集器,您也无法抓取或查看)” - 请启发我,有没有办法将值从 php 传递到在没有被萤火虫或任何其他工具监控的情况下闪光?
m
mathieu landry

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


h
hurturk

只有将所有游戏逻辑保留在服务器端,服务器端也可以在用户不知情的情况下在内部存储分数。出于经济和科学的原因,人类不能将此理论应用于除回合制以外的所有游戏类型。例如,将物理保存在服务器端的计算成本很高,并且很难像手速一样做出响应。甚至有可能,在下棋时,任何人都可以将 AI 国际象棋游戏与对手相匹配。因此,更好的多人游戏也应该包含点播创意。


M
Mark Webster

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


m
m1gu3l

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅