Qual é a melhor maneira de impedir que as pessoas invadam a tabela de recordes baseada em PHP de um jogo em Flash?

StackOverflow https://stackoverflow.com/questions/73947

Pergunta

Estou falando de um jogo de ação sem limite máximo de pontuação e sem maneira de verificar a pontuação no servidor repetindo movimentos, etc.

O que eu realmente preciso é da criptografia mais forte possível em Flash/PHP e de uma maneira de evitar que as pessoas chamem a página PHP de outra forma que não seja através do meu arquivo Flash.Eu tentei alguns métodos simples no passado de fazer várias chamadas para uma única pontuação e completar uma sequência de soma de verificação/fibonacci, etc., e também ofuscar o SWF com Amayeta SWF Encrypt, mas todos eles foram hackeados eventualmente.

Graças às respostas do StackOverflow, encontrei mais algumas informações da Adobe - http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html e https://github.com/mikechambers/as3corelib - que acho que posso usar para criptografia.Não tenho certeza se isso vai me ajudar no CheatEngine.

Preciso saber as melhores soluções tanto para AS2 quanto para AS3, caso sejam diferentes.

Os principais problemas parecem ser coisas como cabeçalhos TamperData e LiveHTTP, mas entendo que também existem ferramentas de hacking mais avançadas - como CheatEngine (obrigado Mark Webster)

Foi útil?

Solução

Este é um problema clássico dos jogos e concursos da Internet.Seu código Flash trabalha com os usuários para decidir a pontuação de um jogo.Mas os usuários não são confiáveis ​​e o código Flash é executado no computador do usuário.Você é SOL.Não há nada que você possa fazer para impedir que um invasor obtenha pontuações altas:

  • Flash é ainda mais fácil de fazer engenharia reversa do que você imagina, já que os bytecodes são bem documentados e descrevem uma linguagem de alto nível (Actionscript) --- quando você publica um jogo em Flash, você está publicando seu código-fonte, seja você saiba ou não.

  • Os invasores controlam a memória de tempo de execução do interpretador Flash, de modo que qualquer pessoa que saiba usar um depurador programável possa alterar qualquer variável (incluindo a pontuação atual) a qualquer momento ou alterar o próprio programa.

O ataque mais simples possível contra o seu sistema é executar o tráfego HTTP do jogo por meio de um proxy, capturar o salvamento de pontuação mais alta e reproduzi-lo com uma pontuação mais alta.

Você pode tentar bloquear esse ataque vinculando cada pontuação mais alta salva a uma única instância do jogo, por exemplo, enviando um token criptografado ao cliente na inicialização do jogo, que pode ser semelhante a:

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

(Você também pode usar um cookie de sessão para o mesmo efeito).

O código do jogo ecoa esse token de volta ao servidor com o salvamento da pontuação mais alta.Mas um invasor ainda pode simplesmente iniciar o jogo novamente, obter um token e colá-lo imediatamente em um salvamento de pontuação alta reproduzido.

Em seguida, você alimenta não apenas um token ou cookie de sessão, mas também uma chave de sessão com criptografia de alta pontuação.Esta será uma chave AES de 128 bits, criptografada com uma chave codificada no jogo Flash:

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

Agora, antes de o jogo postar a pontuação mais alta, ele descriptografa a chave de sessão de criptografia de pontuação mais alta, o que pode ser feito porque você codificou a chave de descriptografia da chave de sessão de criptografia de pontuação mais alta no binário Flash.Você criptografa a pontuação mais alta com esta chave descriptografada, junto com o hash SHA1 da pontuação mais alta:

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

O código PHP no servidor verifica o token para garantir que a solicitação veio de uma instância de jogo válida e, em seguida, descriptografa a pontuação mais alta criptografada, verificando se a pontuação mais alta corresponde ao SHA1 da pontuação mais alta (se você pular esta etapa , a descriptografia simplesmente produzirá pontuações aleatórias, provavelmente muito altas).

Então agora o invasor descompila seu código Flash e encontra rapidamente o código AES, que se destaca como um polegar machucado, embora mesmo que não fosse, ele seria rastreado em 15 minutos com uma pesquisa de memória e um rastreador ("Eu sei minha pontuação para este jogo é 666, então vamos encontrar 666 na memória e, em seguida, capturar qualquer operação que toque nesse valor --- olhe, o código de criptografia da pontuação mais alta!").Com a chave de sessão, o invasor nem precisa executar o código Flash;ela pega um token de lançamento do jogo e uma chave de sessão e pode enviar de volta uma pontuação alta arbitrária.

Agora você está no ponto em que a maioria dos desenvolvedores simplesmente desiste - mais ou menos alguns meses de mexer com os invasores:

  • Embaralhando as chaves AES com operações XOR

  • Substituindo matrizes de bytes de chave por funções que calculam a chave

  • Espalhar criptografias de chaves falsas e postagens de alta pontuação por todo o binário.

Tudo isso é principalmente uma perda de tempo.Nem é preciso dizer que o SSL também não irá ajudá-lo;O SSL não pode protegê-lo quando um dos dois pontos de extremidade SSL é mau.

Aqui estão algumas coisas que podem realmente reduzir a fraude de pontuação alta:

  • Exija um login para jogar, faça com que o login produza um cookie de sessão e não permita vários lançamentos de jogos pendentes na mesma sessão ou várias sessões simultâneas para o mesmo usuário.

  • Rejeite pontuações altas de sessões de jogos que durem menos do que os jogos reais mais curtos já jogados (para uma abordagem mais sofisticada, tente "colocar em quarentena" pontuações altas para sessões de jogos que durem menos de 2 desvios padrão abaixo da duração média do jogo).Certifique-se de monitorar as durações dos jogos no servidor.

  • Rejeite ou coloque em quarentena pontuações altas de logins que jogaram apenas uma ou duas vezes, para que os invasores tenham que produzir uma "trilha de papel" de jogo de aparência razoável para cada login que criarem.

  • Pontuações de "pulsação" durante o jogo, para que seu servidor veja o crescimento da pontuação ao longo da vida de um jogo.Rejeite pontuações altas que não sigam curvas de pontuação razoáveis ​​(por exemplo, saltar de 0 a 999.999).

  • Estado do jogo "instantâneo" durante o jogo (por exemplo, quantidade de munição, posição no nível, etc.), que você pode posteriormente reconciliar com pontuações provisórias registradas.Para começar, você nem precisa encontrar uma maneira de detectar anomalias nesses dados;você só precisa coletá-lo e então voltar e analisá-lo se as coisas parecerem suspeitas.

  • Desative a conta de qualquer usuário que falhe em uma de suas verificações de segurança (por exemplo, enviando uma pontuação alta criptografada que falhe na validação).

Lembre-se, porém, de que você está apenas impedindo fraudes de pontuação alta aqui.Há nada você pode fazer para evitar isso.Se houver dinheiro em jogo no seu jogo, alguém irá derrotar qualquer sistema que você criar.O objetivo não é parar este ataque;é tornar o ataque mais caro do que apenas ficar realmente bom no jogo e vencê-lo.

Outras dicas

Você pode estar fazendo a pergunta errada.Você parece focado nos métodos que as pessoas estão usando para subir na lista de pontuações mais altas, mas bloquear métodos específicos só vai até certo ponto.Não tenho experiência com TamperData, então não posso falar sobre isso.

A pergunta que você deveria fazer é:"Como posso verificar se as pontuações enviadas são válidas e autênticas?" A maneira específica de fazer isso depende do jogo.Para jogos de quebra-cabeça muito simples, você pode enviar a pontuação junto com o estado inicial específico e a sequência de movimentos que resultaram no estado final e, em seguida, executar novamente o jogo no servidor usando os mesmos movimentos.Confirme se a pontuação declarada é igual à pontuação calculada e só aceite a pontuação se forem iguais.

Uma maneira fácil de fazer isso seria fornecer um hash criptográfico do valor do seu recorde junto com a própria pontuação.Por exemplo, ao postar os resultados via HTTP GET:http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

Ao calcular esta soma de verificação, um segredo compartilhado deve ser utilizado;esse segredo nunca deve ser transmitido pela rede, mas deve ser codificado tanto no backend PHP quanto no frontend flash.A soma de verificação acima foi criada acrescentando a string "segredo"para a pontuação"500", e executá-lo através do md5sum.

Embora este sistema impeça que um usuário poste pontuações arbitrárias, ele não evita um "ataque de repetição", onde um usuário repassa uma pontuação previamente calculada e uma combinação de hash.No exemplo acima, uma pontuação de 500 sempre produziria a mesma string hash.Parte desse risco pode ser mitigado incorporando mais informações (como nome de usuário, carimbo de data/hora ou endereço IP) na string que será criptografada.Embora isso não impeça a reprodução de dados, garantirá que um conjunto de dados seja válido apenas para um único usuário por vez.

Prevenir qualquer ocorram ataques de repetição, algum tipo de sistema de resposta a desafios terá que ser criado, como o seguinte:

  1. O jogo flash ("o cliente") executa um HTTP GET de http://example.com/highscores.php sem parâmetros.Esta página retorna dois valores:um gerado aleatoriamente sal valor e um hash criptográfico desse valor salt combinado com o segredo compartilhado.Esse valor salt deve ser armazenado em um banco de dados local de consultas pendentes e deve ter um carimbo de data/hora associado a ele para que possa "expirar" após talvez um minuto.
  2. O jogo flash combina o valor salt com o segredo compartilhado e calcula um hash para verificar se corresponde ao fornecido pelo servidor.Esta etapa é necessária para evitar a adulteração dos valores salt pelos usuários, pois verifica se o valor salt foi realmente gerado pelo servidor.
  3. O jogo flash combina o valor salt com o segredo compartilhado, o valor da pontuação mais alta e qualquer outra informação relevante (apelido, ip, carimbo de data/hora) e calcula um hash.Em seguida, ele envia essas informações de volta ao back-end do PHP via HTTP GET ou POST, junto com o valor salt, pontuação mais alta e outras informações.
  4. O servidor combina as informações recebidas da mesma forma que no cliente e calcula um hash para verificar se corresponde ao fornecido pelo cliente.Em seguida, ele também verifica se o valor salt ainda é válido conforme listado na lista de consultas pendentes.Se ambas as condições forem verdadeiras, ele grava a pontuação mais alta na tabela de pontuações mais altas e retorna uma mensagem assinada de "sucesso" ao cliente.Ele também remove o valor salt da lista de consultas pendentes.

Tenha em mente que a segurança de qualquer uma das técnicas acima será comprometida se o segredo compartilhado estiver acessível ao usuário

Como alternativa, algumas dessas idas e vindas poderiam ser evitadas forçando o cliente a se comunicar com o servidor por HTTPS e garantindo que o cliente esteja pré-configurado para confiar apenas em certificados assinados por uma autoridade de certificação específica à qual somente você tem acesso. .

Gosto do que o tpqf disse, mas em vez de desativar uma conta quando uma trapaça é descoberta, implemente um honeypot para que, sempre que fizerem login, vejam suas pontuações hackeadas e nunca suspeitem que foram marcados como trolls.Procure no Google por "phpBB MOD Troll" e você verá uma abordagem engenhosa.

Na resposta aceita, tqbf menciona que você pode simplesmente fazer uma busca na memória pela variável score ("Minha pontuação é 666, então procuro o número 666 na memória").

Existe uma maneira de contornar isso.Eu tenho uma aula aqui: http://divilysausages.com/blog/safenumber_and_safeint

Basicamente, você tem um objeto para armazenar sua pontuação.No setter ele multiplica o valor que você passa por um número aleatório (+ e -), e no getter você divide o valor salvo pelo multiplicador aleatório para obter o original de volta.É simples, mas ajuda a interromper a busca na memória.

Além disso, confira o vídeo de alguns dos caras por trás do mecanismo PushButton que falam sobre algumas das diferentes maneiras de combater o hacking: http://zaa.tv/2010/12/the-art-of-hacking-flash-games/.Eles foram a inspiração por trás da aula.

Eu fiz uma espécie de solução alternativa ...Eu tive uma situação em que as pontuações aumentaram (você sempre obtém +1 pontuação).Primeiro, comecei a contar a partir de números aleatórios (digamos 14) e quando exibi as pontuações, apenas mostrei as pontuações var menos 14.Assim, se os crackers estiverem procurando por exemplo 20, não encontrarão (serão 34 na memória).Em segundo lugar, já que sei qual deveria ser o próximo ponto...Usei a biblioteca de criptografia da Adobe, para criar o hash do próximo ponto deveria estar.Quando tenho que incrementar as pontuações, verifico se o hash das pontuações incrementadas é igual ao hash que deveria ser.Se o cracker alterou os pontos na memória, os hashes não são iguais.Eu realizo algumas verificações no servidor e quando recebo pontos diferentes do jogo e do PHP, sei que houve trapaça.Aqui está um trecho do meu código (estou usando a classe Adobe Crypto libraty MD5 e criptografia aleatória salt.callPhp() é minha validação do lado do servidor)

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");
            }
        }

Usando esta técnica + ofuscação SWF, consegui parar os crackers.Além disso, quando envio as pontuações para o lado do servidor, uso minha própria pequena função de criptografia/descriptografia.Algo assim (código do lado do servidor não incluído, mas você pode ver o algoritmo e escrevê-lo em 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
}

Aí eu mando a variável junto com outras fake-vars e ela simplesmente se perde no meio do caminho...É muito trabalhoso apenas para pequenos jogos em flash, mas quando há prêmios envolvidos, algumas pessoas ficam gananciosas.Se precisar de ajuda, escreva-me uma PM.

Obrigada, Ico

Criptografar usando uma chave reversível conhecida (privada) seria o método mais simples.Não estou totalmente familiarizado com AS, então não tenho certeza de quais tipos de provedores de criptografia existem.

Mas você pode incluir variáveis ​​como duração do jogo (criptografado, novamente) e contagem de cliques.

Todas as coisas assim pode ser submetido a engenharia reversa, então considere lançar um monte de dados inúteis para despistar as pessoas.

Editar:Pode valer a pena participar de algumas sessões de PHP também.Inicie a sessão quando eles clicarem em iniciar jogo e (como diz o comentário desta postagem) registre o tempo.Quando eles enviam a pontuação, você pode verificar se eles realmente têm um jogo aberto e se não estão enviando uma pontuação muito cedo ou muito grande.

Pode valer a pena calcular um escalar para ver qual é a pontuação máxima por segundo/minuto de jogo.

Nenhuma dessas coisas é incontornável, mas ajudará ter alguma lógica fora do Flash, onde as pessoas possam vê-la.

Na minha experiência, é melhor abordar isso como um problema de engenharia social, e não como um problema de programação.Em vez de se concentrar em tornar impossível trapacear, concentre-se em torná-lo chato, removendo os incentivos para trapacear.Por exemplo, se o principal incentivo são pontuações altas visíveis publicamente, simplesmente atrasar o momento em que as pontuações altas são mostradas pode reduzir significativamente a trapaça, removendo o ciclo de feedback positivo para os trapaceiros.

Você não pode confiar em nenhum dado retornado pelo cliente.A validação precisa ser realizada no lado do servidor.Não sou desenvolvedor de jogos, mas faço software empresarial.Em ambos os casos, pode haver dinheiro envolvido e as pessoas quebrarão as técnicas de ofuscação do lado do cliente.

Talvez envie dados de volta ao servidor periodicamente e faça alguma validação.Não se concentre no código do cliente, mesmo que seja aí que reside o seu aplicativo.

Sempre que o seu sistema de pontuação for baseado no fato de que o aplicativo Flash envia dados de pontuação não criptografados/não assinados através da rede, que podem ser interceptados e manipulados/reproduzidos.A resposta decorre disso:criptografar (decentemente!) ou assinar criptograficamente dados de pontuação máxima.Isso, pelo menos, torna mais difícil para as pessoas quebrarem seu sistema de pontuação porque precisarão extrair a chave secreta do seu arquivo SWF.Muitas pessoas provavelmente desistirão ali mesmo.Por outro lado, basta uma única pessoa extrair a chave e colocá-la em algum lugar.

Soluções reais envolvem mais comunicação entre o aplicativo Flash e o banco de dados de recordes para que este possa verificar se uma determinada pontuação é realista.Isso provavelmente é complicado dependendo do tipo de jogo que você possui.

Não há como torná-lo completamente impossível de hackear, pois é fácil descompilar SWFs, e um hacker desenvolvedor qualificado poderia então rastrear seu código e descobrir como contornar qualquer sistema criptografado que você possa empregar.

Se você simplesmente deseja impedir que as crianças trapaceiem por meio do uso de ferramentas simples como o TamperData, você pode gerar uma chave de criptografia que você passa para o SWF na inicialização.Então use algo como http://code.google.com/p/as3crypto/ para criptografar a pontuação mais alta antes de devolvê-la ao código PHP.Em seguida, descriptografe-o no servidor antes de armazená-lo no banco de dados.

Você está falando sobre o que é chamado de problema de "confiança do cliente".Porque o cliente (neste caso, um SWF rodando em um navegador) está fazendo algo para o qual foi projetado.Salve uma pontuação alta.

O problema é que você deseja ter certeza de que as solicitações de "salvar pontuação" vêm do seu filme em flash, e não de alguma solicitação HTTP arbitrária.Uma possível solução para isso é codificar um token gerado pelo servidor no SWF no momento da solicitação (usando chama) que deve acompanhar a solicitação para salvar uma pontuação alta.Depois que o servidor salva essa pontuação, o token expira e não pode mais ser usado para solicitações.

A desvantagem disso é que um usuário só poderá enviar uma pontuação alta por carregamento do filme em flash - você terá que forçá-lo a atualizar/recarregar o SWF antes que ele possa reproduzir novamente para obter uma nova pontuação.

Normalmente incluo "dados fantasmas" da sessão de jogo com a entrada do recorde.Então, se estou fazendo um jogo de corrida, incluo os dados de replay.Muitas vezes você já tem os dados de replay para funcionalidade de replay ou funcionalidade de corrida fantasma (jogando contra sua última corrida ou jogando contra o fantasma do cara nº 14 na tabela de classificação).

Verificá-los é um trabalho muito manual, mas se o objetivo é verificar se as 10 melhores inscrições em um concurso são legítimas, isso pode ser uma adição útil ao arsenal de medidas de segurança que outros já apontaram.

Se o objetivo é manter a lista de recordes online até o fim dos tempos, sem que ninguém precise olhar para eles, isso não trará muito.

A maneira como um novo mod de arcade popular faz isso é enviar dados do flash para o php, de volta para o flash (ou recarregá-lo) e depois de volta para o php.Isso permite que você faça o que quiser para comparar os dados, bem como ignorar hacks de pós-dados/descriptografia e similares.Uma maneira de fazer isso é atribuindo 2 valores aleatórios do php ao flash (que você não pode capturar ou ver mesmo se estiver executando um captador de dados flash em tempo real), usando uma fórmula matemática para adicionar a pontuação com os valores aleatórios e depois verificando usando a mesma fórmula para reverter para ver se a pontuação corresponde quando finalmente vai para o php no final.Esses valores aleatórios nunca são visíveis, além de cronometrar a transação que está ocorrendo e, se demorar mais do que alguns segundos, também sinaliza como trapaça, porque presume que você interrompeu o envio para tentar descobrir os valores aleatórios ou executar os números por meio de algum tipo de cifra para retornar possíveis valores aleatórios para comparar com o valor da pontuação.

Esta parece ser uma solução muito boa, se você me perguntar, alguém vê algum problema ao usar esse método?Ou possíveis maneiras de contornar isso?

Acho que a maneira mais simples seria fazer chamadas para uma função como RegisterScore(score) cada vez que o jogo registra uma pontuação a ser adicionada e depois codificá-la, empacotá-la e enviá-la para um script php como uma string.O script php saberia como decodificá-lo corretamente.Isso interromperia qualquer chamada direta para o script php, pois qualquer tentativa de forçar uma pontuação resultaria em um erro de descompactação.

Só é possível mantendo o toda a lógica do jogo no lado do servidor que também armazena a pontuação internamente sem o conhecimento do usuário.Por razões económicas e científicas, a humanidade não pode aplicar esta teoria a todos os tipos de jogos, excluindo os baseados em turnos.Por exemplomanter a física no lado do servidor é computacionalmente caro e difícil de responder conforme a velocidade de mão.Mesmo possível, ao jogar xadrez, qualquer um pode combinar a jogabilidade de xadrez da IA ​​com o oponente.Portanto, melhor jogos multijogador também deve conter criatividade sob demanda.

Não é realmente possível conseguir o que você deseja.O interior do aplicativo Flash está sempre parcialmente acessível, especialmente quando você sabe como usar coisas como CheatEngine, o que significa que não importa o quão seguras sejam as comunicações do seu site e navegador<->servidor, ainda será relativamente simples de superar.

Pode ser uma boa ideia comunicar-se com o back-end via AMFPHP.Isso deve desencorajar pelo menos os preguiçosos de tentar enviar os resultados por meio do console do navegador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top