miércoles, 20 de marzo de 2019

Tutorial - Cheat Engine: Parte 3: Punteros



Tutorial sobre Cheat Engine


Tercer tutorial sobre Cheat Engine, tocando temas básicos sobre punteros. Se da por hecho que se ha visto la entrada sobre punteros: Introducción a Punteros
Como programa base para trabajar en este tutorial, voy a usar un juego muy simple por turnos de lucha donde cada luchador tiene 3 estadísticas: vida, maná y ataque.

Así se ve un turno del juego:


Por otro lado, cada vez que perdamos, se cerrará el juego.

En este tutorial, obtendremos la memoria de nuestro personaje y de los personajes enemigos contra los que luchemos.
Empezamos sacando la memoria de nuestra vida. Para ello, buscamos el valor 48, y pasamos un turno, en el cual nos harán daño:

 
 Tras recibir daño, tenemos la vida a 39, y vemos que solo queda una dirección:


 Podemos observar que la dirección está en verde en vez del habitual color negro. Esto se debe a que esta dirección corresponde a una zona de memoria estática, es decir, no va a cambiar cuando se reinicie el programa. Podemos agregar esta dirección a nuestra lista, y reiniciar el programa. Veremos que esta misma dirección sigue apuntando a la vida de nuestro personaje:


Bien. La segunda parte, sería obtener las estadísticas de nuestro enemigo. Para ello, mismo procedimiento.
Tras obtener la dirección, veremos que esta no está en verde, no es una dirección de memoria estática. Por otro lado, vemos, en base a la dirección, que son direcciones de memoria muy distantes entre sí. Esto nos puede sugerir que están en zonas de memoria diferente:

 Para verificar que todo está correcto, reiniciaremos el programa.
Esta vez, veremos que tras el reinicio, el valor de la vida del enemigo no coincide, o no es una zona de memoria que pertenezca al programa:

 Damos por hecho pues, que esta dirección está tras un puntero (o una cadena de punteros). Para almacenarlo y no tenerlo que buscar tras cada reinicio, debemos buscar el puntero.
Hay varias formas de encontrar punteros. La más simple, en muchos casos, es la más evidente: Un puntero es una variable que almacena una dirección de memoria. Dicho esto, podemos buscar la dirección de memoria del valor.
Para ello, primero buscamos de nuevo la dirección de la vida:

 Luego, copiamos al dirección hexadecimal (con doble click sobre la dirección podremos copiarla), y la ponemos en el cuadro de búsqueda, marcando la casilla "Hex":

 Vemos que hay 4 posibles punteros.
Tras hacer un ataque, vemos que hay 3 que cambian, para luego volver al valor buscado:

 Nos quedamos con el último, aunque esto no asegura que este sea el correcto. Podemos almacenar los 4, y reiniciar el juego para verificar que siguen apuntando a lo que deben.
Para almacenar un puntero, copiamos su dirección de memoria, y le damos a "Add Address Manually", a la derecha:


 En la ventana que aparece, marcamos la casilla "Pointer", y ponemos la dirección en el último recuadro. Ya veremos posteriormente qué son los "offsets".
Vemos que arriba, aparece la dirección a la que apunta, y su valor:

 tras guardarlo, veremos que en la dirección aparece un "P->" indicando que es un puntero, y que el valor es el correcto.
Ahora sí, podemos reiniciar, y si hemos escogido el puntero correcto, veremos que la dirección corresponde a la del enemigo.
Analizando los valores que toma la memoria, podemos deducir que los enemigos se reservan en memoria tras cada batalla, ya que veremos como la dirección a la que apunta el puntero cambia.

Y hasta aquí la iniciación rápida a punteros.
Antes hemos comentado que un puntero se puede componer de offsets. Es común encontrarse con cadenas de punteros (punteros apuntando a punteros).

P1 -> P2 -> P3 -> Valor

Sin embargo, rara vez un puntero apuntará directamente a otro puntero (puede pasar). Nos encontraremos en numerosas ocasiones con que al resultado de un puntero se le suma un valor (offset) para formar el nuevo puntero. Eso quedaría así:

P1 -> P2, (P2 + O1) -> P3, (P3 + O2) -> Valor

Estos offsets, a nivel de programación, son usualmente accesos a miembros de estructuras/clases.
Por ejemplo, esta estructura en C++:

struct Test {
    int a;
    int b;
};

Si tuviéramos un puntero a un objeto de esta estructura (Test* p), y accediéramos al miembro 'a', que se posiciona de primero en la estructura, podríamos ver una cadena como esta:

[p] -> a

Sin embargo, al acceder al miembro 'b', que está después, estará desplazado unos 4 bytes:

[p] + 4 -> b

Esto pasa así tanto para clases y estructuras, como para arrays, en los cuales el offset sería el índice (multiplicado por el tamaño de cada elemento).

Cheat Engine nos visualiza esta cadena, leída de abajo para arriba:



Como utildiad práctica de los offsets en este caso, podemos ver que, al menos en mi caso, 0069FE64 apunta a la vida con un offset de 0. Si incrementamos el offset a 8, veremos que, en mi caso, apunta al valor 12, que coincide con el maná del adversario, el offset 0x10, al valor 2, que coincide con su nivel, y el offset 0x14, al valor 20, que coincide con el ataque.

Aprovechando que tenemos el código del programa, se puede ver la equivalencia con el orden de declaración de las variables (sacado del código, C++):

int _vida,
     _vidaMax,
     _mana,
     _manaMax,
     _nivel,
     _ataque,
     _habilidad;


Y con esto, cerramos la entrada. Hay varias cosas que tratar sobre el tema de punteros, pero las trataré en entradas posteriores.

Un saludo!

No hay comentarios:

Publicar un comentario