Sistemas anti-trampas (anti-cheating)

El hecho de jugar es algo natural en muchos seres vivos, no solo los humanos. Pero si hay algo que seguramente sí que es una característica nuestra es el hacer trampas. Saltarnos las restricciones impuestas en el contexto del juego con tal de obtener una ventaja. Como comentábamos en un conjunto de entradas anteriores, existen diversos sistemas para poder hacer trampas. Pero analicemos el problema desde la perspectiva del “defensor”. ¿Podemos evitar que un jugador haga trampas en nuestro videojuego?

(c) Glasgow Museums; Supplied by The Public Catalogue Foundation

Ahora juego la carta, un momento… (“A Gambling Party”. Jan Cossiers)

Antes de plantearnos proteger nuestro juego contra los tramposos, y dedicar un esfuerzo a ello, sí que es interesante hacer antes la reflexión de si vale la pena. El caso más interesante para un debate es el de un juego de un solo jugador. Por un lado, hacer trampas no es más que el equivalente a “mirar las soluciones del crucigrama” en el reverso de la página. Aún así, también vale la pena tener en mente que el sentido de la diversión y la dificultad es muy personal. ¿Qué tiene de malo dejar que alguien obtenga ciertas ventajas, o se salte una parte, y así no abandone el juego? Al final, cada uno es dueño de su propia diversión y escapismo. Sin embargo, y por otra parte, sí que existen juegos que dentro de ciertos contextos sociales o colectivos, aún cuando se platean como experiencias individuales, se considera que conseguir ciertos hitos otorga “derechos al fanfarroneo” (bragging rights). Para la comunidad de jugadores, los tramposos, en cierto modo, devalúan los logros de los jugadores “legales”. Por lo tanto, se valora que el juego esté protegido contra tramposos igualmente, para que solo la gente habilidosa consiga superarlo.

De todos modos, el caso evidente es el de los juegos multijugador, sobretodo competitivos, pues hacer trampas no solo atenta contra los valores de la deportividad, sino que va en detrimento de la diversión de los otros jugadores.

En realidad, los vectores de ataque para hacer trampas, en términos generales, no son tan distantes de cualquier modelo de seguridad. Sin embargo, hay la particularidad que esta vez es el propio usuario, y propietario de todo el sistema, el atacante. Normalmente, la primera aproximación de los tramposos se basa en explorar y alterar:

  • a) La memoria del proceso
  • b) El propio ejecutable

Las acciones han de ir dirigidas a dificultar y/o detectar este tipo de acciones. Sin embargo,  al ser el usuario el atacante, existe un problema muy grande, y es que este tiene a su disposición absolutamente toda la información sobre el sistema y el entorno de ejecución. Esto es una lección que ya aprendieron los creadores sistemas de protección para DVD. Por lo tanto, jamás podremos detener con un 100% de eficacia a un atacante absolutamente dedicado. Pero si que le podemos hacer la vida más difícil y esperar que se canse antes.

El caso a)  se suele basar en herramientas como puede ser CheatEngine, que hacen de la labor de exploración algo incluso trivial si las búsquedas son simples. Para juegos sin protección, es un ataque al alcance de cualquier jugador, sin necesidad de ser un experto. Por ejemplo, si tienes 500 monedas, hay que buscar un valor “500” en el espacio de memoria del proceso del juego. Seguramente existan muchas (muchísimas) posiciones de memoria con este valor, pero en sucesivas iteraciones de refinamiento, ejecutadas cada vez que se provoque un cambio de valor en el juego, será fácil para encontrar la posición de memoria que nos interesa finalmente. Por lo tanto, una primera barrera que podemos poner es ofuscar dicha información.

Y dónde antes había 1 gema en la memoria, ahora tengo 100.

Y dónde antes había 1 gema en la memoria, ahora tengo 100.

Para aquellos datos sensibles (energía, monedas, vidas, etc.), nunca guardar el valor real tal como se muestra en el juego, sino uno diferente que decodificaremos mediante la propia lógica del programa. Esto puede ser algo tan tonto como guardar el doble del valor real, por decir algo, hasta cifrar los datos en memoria. El ideal seria el cifrado, pero aún así, hay que tener presente que sigue existiendo el problema de que la clave criptográfica también estará en algún sitio de la memoria, localizable por el atacante (pero ya es algo que es más difícil de encontrar que un valor concreto). Si somos un poco más insidiosos, podemos no ofuscar todos los datos, pero si tener una copia ofuscada en otra posición de memoria, de modo que en realidad podemos ir más allá y incluso detectar el intento de hacer trampas. Entonces, aplicamos el castigo correspondiente al jugador, preferiblemente, de modo no evidente y mucho más tarde.

Complementaria a esta medida, vale la pena también no declarar estos valores sensibles como estáticos, de modo que en cada ejecución varíe su ubicación en la memoria del proceso y el atacante deba empezar la búsqueda desde cero cada vez que ejecute el juego. Si queremos ir más lejos, podemos crear en la lógica del programa mecanismos para mover la ubicación de los valores constantemente durante la propia ejecución, de modo que la exploración de memoria pierda el rastro durante su proceso de búsqueda iterativo. Pero nada será infalible, es sólo el juego del ratón y el gato, esperando que el tramposo desista por cansancio.

Para el caso b), y aquí ya sí que el atacante que tome esta vía será un experto, existen depuradores o decompiladores que permiten explorar la lógica del programa, e incluso recuperar partes del código fuente. Por lo tanto, hay que ser muy cuidadosos con el tema de dejar símbolos de depuración en nuestro código o literales de tipo cadena de texto que permitan dar pistas a los atacantes. También, claro está, existe la opción de ejecutar ofuscadores de código, especialmente aquellos que oculten dichos literales. Finalmente, aún cuando se trata de una acción que va en contra de los principios de mantenibilidad del software, es recomendable no usar bibliotecas dinámicas, pues ello facilita enormemente la tarea de capturar llamadas o reemplazar fácilmente partes del código en ejecución. Llegado a este tipo de ataque volvemos al caso de que cualquier clave de cifrado o lógica para ocultar datos en memoria puede ser detectado y modificado explorando el propio código.

Cuando se trata de juegos multijugador, una opción mejor es mover parte de la información sensible al servidor y que la gestione él. El servidor posee el estado del juego y valida cuidadosamente cualquier petición de los clientes para ver si una acción se puede llevar a cabo o no. Pero un cliente no puede modificar directamente los valores, ni consultar valores que el jugador no puede conocer. De todos modos, la comunicación implica un retraso y dado que ejecutar toda la simulación del juego en el servidor, normalmente, será inviable, la solución deberá ser híbrida respecto a las mencionadas anteriormente.

Cuando entramos en el caso multijugador, aparece un nuevo punto de ataque, que nuevamente, es muy típico si hablamos de seguridad:

  • c) El tráfico de red

Así pues, los datos se deben transmitir siempre cifrados. Nuevamente, esto no evita que puedan ser capturados, pues dichos datos se generan o se leen en la máquina del jugador (ver ataque a) ), pero, nuevamente, dificultan la tarea del tramposo, en este caso, incluso terceros (el jugador A intercepta el tráfico del jugador B para saber sus movimientos). Aún así, hay pequeños detalles que hay que cuidar, más allá de la intercepción de los datos en si. Por  una parte, el análisis del tamaño o formato de los paquetes. Si el atacante puede identificar qué tipo de paquetes se envían, aún cuando no sabe su contenido ni puede modificar el ejecutable, puede descartar de algún modo aquellos que le interesen. Por ejemplo, si los paquetes que indican actualizaciones en la energía del personaje son siempre de cierto tamaño, descartándolos puede volverse invulnerable. Por ello, todo el tráfico debe ser del mismo tamaño, o hacer buffering de las órdenes hacia el servidor. Por otra parte, existe siempre la posibilidad de “tirar del cable” para evitar una penalización. Por ejemplo, forzar una desconexión cuando tu personaje está a punto de morir.  En este caso, la lógica del servidor ha de considerar qué sucede. Puede penalizar al jugador o incluso decidir que la simulación prosigue en el servidor sin él, y que el personaje muere de todos modos. Sin embargo, ser muy estricto va a penalizar a jugadores que, realmente han tenido un error de conexión. Decisiones, decisiones…

Y, independientemente de las estrategias a “bajo nivel”, también vale la pena siempre tener un sistema de registro que analice el comportamiento de los jugadores para establecer si se producen anomalías. Si según la lógica del juego en el cargador de la pistola caben seis balas, alerta si alguien lleva diez. Si un jugador actúa con reflejos sobrehumanos y no falla nunca, hay que mirar con más detalle qué está pasando. Un ejemplo bastante reciente es la ronda de castigo a tramposos del Pokémon Go, dirigida a gente que había sido capaz de capturar 2000 pokémon, o pasar por 1000 poképaradas, en un periodo de 24 horas. Pero hay que decir que, a veces, hay jugadores que realmente son así de buenos (o eso cuentan por Internet).

Nada como una buena estrategia...

La estrategia sin táctica es el camino más lento a la derrota (Art of War)

Para acabar, una vía por la que optan grandes proveedores de servicios multijugador es, simplemente, forzar la ejecución de un programa en el ordenador de cada jugador que monitoriza en todo momento la integridad de los ejecutables del juago, a si vez que inspecciona la memoria buscando trazas de programas sospechosos. Un “antivirus” contra programas para hacer trampas. Por ejemplo, la plataforma Steam tiene el servicio de VAC (Valve Anti-Cheat) y Blizzard tiene Warden. Así, se libera los desarrolladores de una parte de esta tarea de control anti-trampas. De todos modos, alerta, ya que con esto de instalar programas que controlan un equipo para evitar las trampas a veces a los desarrolladores se les va de las manos y igual te instalan un rootkit

Como no, todo esto implica más trabajo de desarrollo, a veces la creación de un programa más difícil de mantener, y sobretodo, un impacto en el rendimiento del juego (que en algunos casos ya puede estar rozando el límite de las capacidades de la máquina). Seguridad y rendimiento o usabilidad nunca compartieron una gran amistad. Pero en el caso de juegos multijugador, se ha de hacer sí o sí. Para juegos de un jugador, ya es una decisión de diseño.

Para acabar, me quedo con una frase que leí, pero no recuerdo dónde y no puedo atribuir como correspondería: “Y es que, al final, la mejor manera de desengancharse de un juego es aprendiendo a hacer trampas” :-)

Joan Arnedo es profesor de los estudios de Informática, Multimedia y Telecomunicación en la UOC. Director académico del Máster en Diseño y Desarrollo de Videojuegos e investigador en el campo de la ludificación y los juegos serios. Su experiencia se remonta a cuando los ordenadores MSX poblaban la Tierra…

CC BY-NC-SA 4.0 Sistemas anti-trampas (anti-cheating) por jarnedo está licenciado bajo una Licencia Creative Commons Atribución-NoComercial-CompartirIgual 4.0 Internacional.

Comentar

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Leer entrada anterior
El Congreso Español de Informática (CEDI)

Este septiembre de 2016 se ha celebrado en Salamanca el Congreso Español de Informática (CEDI), uno de los eventos científicos más importantes...

Cerrar