¿Cuál es la mejor manera de evitar que la gente piratee la tabla de puntuación más alta basada en PHP de un juego Flash?

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

Pregunta

Estoy hablando de un juego de acción sin límite de puntuación superior y sin forma de verificar la puntuación en el servidor repitiendo movimientos, etc.

Lo que realmente necesito es el cifrado más fuerte posible en Flash/PHP y una forma de evitar que las personas llamen a la página PHP a través de otro método que no sea mi archivo Flash.He probado algunos métodos simples en el pasado para realizar múltiples llamadas para una sola puntuación y completar una suma de verificación/secuencia de Fibonacci, etc., y también ofuscar el SWF con Amayeta SWF Encrypt, pero finalmente todos fueron pirateados.

Gracias a las respuestas de StackOverflow, ahora encontré más información de Adobe: http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html y https://github.com/mikechambers/as3corelib - que creo que puedo usar para el cifrado.Sin embargo, no estoy seguro de que esto me ayude a utilizar CheatEngine.

Necesito conocer las mejores soluciones tanto para AS2 como para AS3, si son diferentes.

Los principales problemas parecen ser cosas como TamperData y encabezados LiveHTTP, pero entiendo que también existen herramientas de piratería más avanzadas, como CheatEngine (gracias Mark Webster).

¿Fue útil?

Solución

Este es un problema clásico de los juegos y concursos de Internet.Su código Flash trabaja con los usuarios para decidir la puntuación de un juego.Pero no se confía en los usuarios y el código Flash se ejecuta en la computadora del usuario.Eres SOL.No hay nada que puedas hacer para evitar que un atacante falsifique puntuaciones altas:

  • Flash es aún más fácil de aplicar ingeniería inversa de lo que piensas, ya que los códigos de bytes están bien documentados y describen un lenguaje de alto nivel (Actionscript). Cuando publicas un juego Flash, estás publicando tu código fuente, ya sea que saberlo o no.

  • Los atacantes controlan la memoria de ejecución del intérprete Flash, de modo que cualquiera que sepa cómo utilizar un depurador programable puede alterar cualquier variable (incluida la puntuación actual) en cualquier momento, o alterar el programa mismo.

El ataque más simple posible contra su sistema es ejecutar el tráfico HTTP del juego a través de un proxy, capturar la partida guardada con la puntuación más alta y reproducirla con una puntuación más alta.

Puedes intentar bloquear este ataque vinculando cada guardado de puntuación alta a una única instancia del juego, por ejemplo, enviando un token cifrado al cliente al iniciar el juego, que podría verse así:

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

(También puede utilizar una cookie de sesión con el mismo efecto).

El código del juego envía este token al servidor con la puntuación más alta guardada.Pero un atacante aún puede simplemente iniciar el juego nuevamente, obtener una ficha y luego pegarla inmediatamente en una partida guardada de puntuación alta repetida.

Entonces, a continuación, no solo alimenta un token o una cookie de sesión, sino también una clave de sesión de cifrado de puntuación alta.Esta será una clave AES de 128 bits, encriptada con una clave codificada en el juego Flash:

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

Ahora, antes de que el juego publique el puntaje más alto, descifra la clave de sesión de cifrado de puntaje alto, lo cual puede hacer porque usted codificó la clave de descifrado de la clave de sesión de cifrado de puntaje alto en el binario Flash.Cifras la puntuación más alta con esta clave descifrada, junto con el hash SHA1 de la puntuación más alta:

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

El código PHP en el servidor verifica el token para asegurarse de que la solicitud provenga de una instancia de juego válida, luego descifra la puntuación alta cifrada y verifica que la puntuación alta coincida con el SHA1 de la puntuación alta (si omite este paso). , el descifrado simplemente producirá puntuaciones aleatorias, probablemente muy altas).

Así que ahora el atacante descompila su código Flash y encuentra rápidamente el código AES, que sobresale como un pulgar dolorido, aunque incluso si no lo hiciera sería localizado en 15 minutos con una búsqueda de memoria y un rastreador ("Lo sé mi puntaje para este juego es 666, así que busquemos 666 en la memoria, luego capturemos cualquier operación que toque ese valor --- ¡oh, mira, el código de cifrado de puntaje más alto!").Con la clave de sesión, el atacante ni siquiera tiene que ejecutar el código Flash;toma un token de inicio del juego y una clave de sesión y puede enviar una puntuación alta arbitraria.

Ahora estás en el punto en el que la mayoría de los desarrolladores simplemente se dan por vencidos, más o menos un par de meses de jugar con los atacantes al:

  • Codificar las claves AES con operaciones XOR

  • Reemplazo de matrices de bytes clave con funciones que calculan la clave

  • Distribuir cifrados de claves falsos y publicaciones de puntuaciones altas por todo el binario.

Todo esto es mayoritariamente una pérdida de tiempo.No hace falta decir que SSL tampoco le ayudará;SSL no puede protegerlo cuando uno de los dos puntos finales SSL es incorrecto.

Aquí hay algunas cosas que realmente pueden reducir el fraude con puntajes altos:

  • Exija un inicio de sesión para jugar, haga que el inicio de sesión genere una cookie de sesión y no permita múltiples lanzamientos de juegos destacados en la misma sesión, ni múltiples sesiones simultáneas para el mismo usuario.

  • Rechace las puntuaciones altas de las sesiones de juego que duren menos que los juegos reales más cortos jamás jugados (para un enfoque más sofisticado, intente "poner en cuarentena" las puntuaciones altas de las sesiones de juego que duren menos de 2 desviaciones estándar por debajo de la duración media del juego).Asegúrate de realizar un seguimiento de la duración de los juegos en el servidor.

  • Rechaza o pone en cuarentena las puntuaciones altas de los inicios de sesión que sólo han jugado el juego una o dos veces, de modo que los atacantes tengan que producir un "rastro documental" de un juego de aspecto razonable para cada inicio de sesión que creen.

  • Puntuaciones "Heartbeat" durante el juego, de modo que su servidor vea el aumento de la puntuación a lo largo de la vida de un juego.Rechace puntuaciones altas que no sigan curvas de puntuación razonables (por ejemplo, saltar de 0 a 999999).

  • "Instantánea" del estado del juego durante el juego (por ejemplo, cantidad de munición, posición en el nivel, etc.), que luego puedes conciliar con las puntuaciones provisionales registradas.Para empezar, ni siquiera es necesario tener una forma de detectar anomalías en estos datos;sólo tienes que recopilarlo y luego puedes volver atrás y analizarlo si las cosas parecen sospechosas.

  • Deshabilite la cuenta de cualquier usuario que no pase uno de sus controles de seguridad (por ejemplo, al enviar una puntuación alta cifrada que no pase la validación).

Sin embargo, recuerde que aquí solo está disuadiendo el fraude con puntajes altos.hay nada puedes hacer para prevenir si.Si hay dinero en juego en tu juego, alguien derrotará cualquier sistema que se te ocurra.El objetivo no es detener este ataque;es hacer que el ataque sea más costoso que simplemente volverse realmente bueno en el juego y vencerlo.

Otros consejos

Quizás esté haciendo la pregunta equivocada.Pareces centrado en los métodos que la gente utiliza para ascender en la lista de puntuaciones más altas, pero bloquear métodos específicos sólo llega hasta cierto punto.No tengo experiencia con TamperData, así que no puedo hablar de eso.

La pregunta que deberías hacerte es:"¿Cómo puedo verificar que los puntajes enviados son válidos y auténticos?" La forma específica de hacerlo depende del juego.Para juegos de rompecabezas muy simples, puedes enviar la puntuación junto con el estado inicial específico y la secuencia de movimientos que dieron como resultado el estado final, y luego volver a ejecutar el juego en el lado del servidor usando los mismos movimientos.Confirme que la puntuación indicada sea la misma que la puntuación calculada y solo acepte la puntuación si coinciden.

Una manera fácil de hacer esto sería proporcionar un hash criptográfico de su valor de puntuación más alta junto con la puntuación misma.Por ejemplo, al publicar los resultados a través de HTTP GET:http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

Al calcular esta suma de verificación, se debe utilizar un secreto compartido;Este secreto nunca debe transmitirse a través de la red, sino que debe estar codificado tanto en el backend de PHP como en el frontend flash.La suma de verificación anterior se creó anteponiendo la cadena "secreto" al marcador "500", y ejecutándolo a través de md5sum.

Aunque este sistema evitará que un usuario publique puntuaciones arbitrarias, no evita un "ataque de repetición", en el que un usuario vuelve a publicar una puntuación previamente calculada y una combinación de hash.En el ejemplo anterior, una puntuación de 500 siempre produciría la misma cadena hash.Parte de este riesgo se puede mitigar incorporando más información (como un nombre de usuario, marca de tiempo o dirección IP) en la cadena que se va a aplicar hash.Aunque esto no impedirá la reproducción de datos, garantizará que un conjunto de datos solo sea válido para un único usuario a la vez.

Para prevenir cualquier Para que no se produzcan ataques de repetición, será necesario crear algún tipo de sistema de desafío-respuesta, como el siguiente:

  1. El juego flash ("el cliente") realiza un HTTP GET de http://ejemplo.com/highscores.php sin parámetros.Esta página devuelve dos valores:un generado aleatoriamente sal valor y un hash criptográfico de ese valor de sal combinado con el secreto compartido.Este valor de sal debe almacenarse en una base de datos local de consultas pendientes y debe tener una marca de tiempo asociada para que pueda "caducar" después de quizás un minuto.
  2. El juego flash combina el valor de la sal con el secreto compartido y calcula un hash para verificar que coincida con el proporcionado por el servidor.Este paso es necesario para evitar la manipulación de los valores de sal por parte de los usuarios, ya que verifica que el valor de sal realmente fue generado por el servidor.
  3. El juego flash combina el valor de sal con el secreto compartido, el valor de puntuación más alta y cualquier otra información relevante (apodo, IP, marca de tiempo) y calcula un hash.Luego envía esta información al backend de PHP a través de HTTP GET o POST, junto con el valor de sal, la puntuación más alta y otra información.
  4. El servidor combina la información recibida de la misma forma que en el cliente y calcula un hash para verificar que coincide con el proporcionado por el cliente.Luego también verifica que el valor de sal siga siendo válido tal como figura en la lista de consultas pendientes.Si ambas condiciones son verdaderas, escribe la puntuación más alta en la tabla de puntuación más alta y devuelve un mensaje de "éxito" firmado al cliente.También elimina el valor de sal de la lista de consultas pendientes.

Tenga en cuenta que la seguridad de cualquiera de las técnicas anteriores se ve comprometida si el usuario alguna vez puede acceder al secreto compartido.

Como alternativa, parte de este intercambio podría evitarse obligando al cliente a comunicarse con el servidor a través de HTTPS y asegurándose de que el cliente esté preconfigurado para confiar solo en certificados firmados por una autoridad certificadora específica a la que solo usted tiene acceso. .

Me gusta lo que dijo tpqf, pero en lugar de deshabilitar una cuenta cuando se descubre una trampa, implemente un honeypot para que cada vez que inicien sesión vean sus puntuaciones pirateadas y nunca sospechen que han sido marcados como troll.Busque en Google "phpBB MOD Troll" y verá un enfoque ingenioso.

En la respuesta aceptada, tqbf menciona que simplemente puede hacer una búsqueda en la memoria de la variable de puntuación ("Mi puntuación es 666, así que busco el número 666 en la memoria").

Hay una manera de evitar esto.Tengo una clase aquí: http://divillysausages.com/blog/safenumber_and_safeint

Básicamente, tienes un objeto para almacenar tu puntuación.En el setter multiplica el valor que le pasas con un número aleatorio (+ y -), y en el getter divides el valor guardado por el multiplicador aleatorio para recuperar el original.Es simple, pero ayuda a detener la búsqueda de memoria.

Además, mira el vídeo de algunos de los chicos detrás del motor PushButton que hablan sobre algunas de las diferentes formas en que puedes combatir la piratería: http://zaa.tv/2010/12/el-arte-de-hacking-flash-games/.Ellos fueron la inspiración detrás de la clase.

Hice una especie de solución...Tenía un dado donde las puntuaciones aumentaban (siempre obtienes +1 puntuación).Primero, comencé a contar desde un número aleatorio (digamos 14) y cuando muestro las puntuaciones, solo mostré las puntuaciones var menos 14.Esto fue así que si los crackers buscan por ejemplo 20, no lo encontrarán (será 34 en la memoria).En segundo lugar, ya que sé cuál debería ser el siguiente punto...Utilicé la biblioteca criptográfica de Adobe para crear el hash del siguiente punto. debiera ser.Cuando tengo que incrementar las puntuaciones, verifico si el hash de las puntuaciones incrementadas es igual al hash que debería ser.Si el cracker ha cambiado los puntos en la memoria, los hashes no son iguales.Realizo una verificación del lado del servidor y cuando obtuve diferentes puntos del juego y del PHP, sé que hubo trampas involucradas.Aquí hay un fragmento de mi código (estoy usando la clase MD5 de Adobe Crypto libraty y sal de criptografía aleatoria).callPhp() es mi validación del lado del 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 + ofuscación SWF, pude detener las galletas.Además, cuando envío las partituras al lado del servidor, uso mi propia pequeña función de cifrado/descifrado.Algo como esto (el código del lado del servidor no está incluido, pero puedes ver el algoritmo y escribirlo en 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
}

Luego envío la variable junto con otras variables falsas y simplemente se pierde en el camino...Es mucho trabajo para un pequeño juego flash, pero cuando hay premios involucrados, algunas personas simplemente se vuelven codiciosas.Si necesitas ayuda, escríbeme un MP.

Saludos, ico

Cifrar utilizando una clave reversible conocida (privada) sería el método más sencillo.No estoy totalmente de acuerdo con AS, por lo que no estoy seguro de qué tipos de proveedores de cifrado existen.

Pero podrías incluir variables como la duración del juego (encriptada, nuevamente) y el recuento de clics.

Todas las cosas como esta poder ser objeto de ingeniería inversa, así que considere agregar un montón de datos basura para despistar a la gente.

Editar:También podría valer la pena realizar algunas sesiones de PHP.Inicie la sesión cuando hagan clic en iniciar juego y (como dice el comentario de esta publicación) registre el tiempo.Cuando envían la puntuación, puedes comprobar que realmente tienen un juego abierto y que no envían una puntuación demasiado pronto o demasiado grande.

Podría valer la pena calcular un escalar para ver cuál es la puntuación máxima por segundo/minuto de juego.

Ninguna de estas cosas es ineludible, pero será útil tener algo de lógica que no esté en Flash, donde la gente puede verla.

En mi experiencia, esto se aborda mejor como un problema de ingeniería social que como un problema de programación.En lugar de centrarse en hacer que sea imposible hacer trampa, concéntrese en hacerlo aburrido eliminando los incentivos para hacer trampa.Por ejemplo, si el incentivo principal son las puntuaciones altas visibles públicamente, simplemente retrasar el momento en que se muestran las puntuaciones altas puede reducir significativamente las trampas al eliminar el ciclo de retroalimentación positiva para los tramposos.

No puede confiar en ningún dato que le devuelva el cliente.La validación debe realizarse en el lado del servidor.No soy desarrollador de juegos, pero sí creo software empresarial.En ambos casos, puede haber dinero en juego y la gente romperá las técnicas de ofuscación del lado del cliente.

Tal vez envíe datos al servidor periódicamente y realice alguna validación.No se concentre en el código del cliente, incluso si ahí es donde reside su aplicación.

Siempre que su sistema de puntuación más alta se base en el hecho de que la aplicación Flash envía datos de puntuación más alta sin cifrar/sin firmar a través de la red, estos pueden ser interceptados y manipulados/reproducidos.La respuesta se desprende de eso:cifrar (¡decentemente!) o firmar criptográficamente datos de puntuación más alta.Esto, al menos, hace que sea más difícil para las personas descifrar su sistema de puntuación más alta porque necesitarán extraer la clave secreta de su archivo SWF.Probablemente mucha gente se rendirá allí mismo.Por otro lado, basta con que una sola persona extraiga la clave y la coloque en algún lugar.

Las soluciones reales implican una mayor comunicación entre la aplicación Flash y la base de datos de puntuaciones altas para que esta última pueda verificar que una puntuación determinada es algo realista.Probablemente esto sea complicado dependiendo del tipo de juego que tengas.

No hay forma de hacerlo completamente imposible de piratear, ya que es fácil descompilar archivos SWF, y un desarrollador hacker experto podría rastrear su código y descubrir cómo evitar cualquier sistema cifrado que pueda emplear.

Si simplemente desea evitar que los niños hagan trampa mediante el uso de herramientas simples como TamperData, entonces puede generar una clave de cifrado que pasará al SWF al inicio.Entonces usa algo como http://code.google.com/p/as3crypto/ para cifrar la puntuación más alta antes de pasarla de nuevo al código PHP.Luego, descifrelo en el extremo del servidor antes de almacenarlo en la base de datos.

Estás hablando de lo que se llama el problema de la "confianza del cliente".Porque el cliente (en este caso, un SWF que se ejecuta en un navegador) está haciendo algo para lo que está diseñado.Guarde una puntuación alta.

El problema es que desea asegurarse de que las solicitudes de "guardar puntuación" provengan de su película flash y no de alguna solicitud HTTP arbitraria.Una posible solución para esto es codificar un token generado por el servidor en el SWF en el momento de la solicitud (usando flama) que debe acompañar la solicitud para guardar una puntuación alta.Una vez que el servidor guarda esa puntuación, el token caduca y ya no se puede utilizar para solicitudes.

La desventaja de esto es que un usuario sólo podrá enviar una puntuación alta por cada carga de la película flash; debe obligarlo a actualizar/recargar el SWF antes de que pueda reproducir nuevamente para obtener una nueva puntuación.

Normalmente incluyo "datos fantasma" de la sesión de juego con la entrada de puntuación más alta.Entonces, si estoy creando un juego de carreras, incluyo los datos de repetición.A menudo ya tienes los datos de repetición para la funcionalidad de repetición o la funcionalidad de carreras de fantasmas (jugar contra tu última carrera o jugar contra el fantasma del tipo #14 en la tabla de clasificación).

Verificarlos es un trabajo muy manual, pero si el objetivo es verificar si las 10 mejores entradas de un concurso son legítimas, esto puede ser una adición útil al arsenal de medidas de seguridad que otros ya han señalado.

Si el objetivo es mantener la lista de puntuaciones altas en línea hasta el final de los tiempos sin que nadie tenga que mirarlas, esto no le aportará mucho.

La forma en que lo hace un nuevo mod arcade popular es que envía datos desde flash a php, de regreso a flash (o lo recarga) y luego de regreso a php.Esto le permite hacer cualquier cosa que desee para comparar los datos, así como evitar los hacks de descifrado/datos de publicación y similares.Una forma de hacerlo es asignando 2 valores aleatorios de php al flash (que no puede capturar ni ver incluso si ejecuta un capturador de datos flash en tiempo real), usando una fórmula matemática para agregar la puntuación con los valores aleatorios y luego verificarlo usando la misma fórmula para revertirlo para ver si la puntuación coincide cuando finalmente pasa al php al final.Estos valores aleatorios nunca son visibles y también cronometran la transacción que se realiza y, si dura más de un par de segundos, también la marca como trampa porque supone que ha detenido el envío para intentar descubrir los valores aleatorios o ejecutar los números a través de algún tipo de cifrado para devolver posibles valores aleatorios para comparar con el valor de la puntuación.

Esta parece una solución bastante buena si me preguntas, ¿alguien ve algún problema con el uso de este método?¿O posibles formas de evitarlo?

Creo que la forma más sencilla sería realizar llamadas a una función como RegisterScore(score) cada vez que el juego registra una puntuación para agregarla y luego codificarla, empaquetarla y enviarla a un script php como una cadena.El script php sabría cómo decodificarlo correctamente.Esto detendría cualquier llamada directa al script php, ya que cualquier intento de forzar una puntuación resultaría en un error de descompresión.

Sólo es posible manteniendo el toda la lógica del juego en el lado del servidor que también almacena la puntuación internamente sin el conocimiento del usuario.Por razones económicas y científicas, la humanidad no puede aplicar esta teoría a todos los tipos de juegos, excepto los por turnos.Por ej.Mantener la física en el lado del servidor es computacionalmente costoso y es difícil lograr una respuesta tan rápida como la mano.Incluso es posible, mientras juega ajedrez, cualquiera puede hacer coincidir el juego de ajedrez de IA con el oponente.Por lo tanto, mejor juegos multijugador También debe contener creatividad bajo demanda.

Realmente no es posible lograr lo que quieres.Las partes internas de la aplicación Flash siempre son parcialmente accesibles, especialmente cuando sabes cómo usar cosas como motor de trampa, lo que significa que no importa cuán seguras sean las comunicaciones entre su sitio web y su navegador<->servidor, seguirá siendo relativamente sencillo superarlo.

Podría ser una buena idea comunicarse con el backend a través de AMFPHP.Debería disuadir al menos a los perezosos de intentar enviar los resultados a través de la consola del navegador.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top