Preparación del laboratorio

Contenido descargable del reto:

El archivo descargable del reto contiene el comprimido a12c7365-32e0-425e-8586-41d85153daa6.zip.

Extrae el contenido en una carpeta para ver los siguientes archivos:

HackTheBox CubeMadness2_Data
baselib.dll
GameAssembly.dll
HackTheBox CubeMadness2.exe
UnityCrashHandler64.exe
UnityPlayer.dll

La presencia de UnityPlayer.dll, GameAssembly.dll y el directorio “HackTheBox CubeMadness2_Data” indica que estamos ante un juego desarrollado con el motor Unity.

En particular, GameAssembly.dll suele aparecer en aplicaciones de Unity compiladas mediante IL2CPP. En este tipo de compilación, parte de la lógica originalmente escrita en C# se transforma en código nativo, por lo que durante el análisis encontraremos instrucciones ensamblador ejecutadas directamente por el proceso.

El objetivo del laboratorio consiste en alcanzar una puntuación determinada dentro del juego. En lugar de completar el reto únicamente mediante interacción manual, analizaremos cómo se almacena y actualiza dicho valor en memoria.

Ejecución del juego

Ejecutamos el archivo:

HackTheBox CubeMadness2.exe

Al iniciar el juego podemos observar un contador que aumenta al alcanzar un cubo.

Este valor será nuestro punto de partida para el análisis dinámico. Tenemos que localizar qué dirección de memoria contiene el contador y, posteriormente, determinar cuál de las direcciones encontradas participa realmente en la lógica del programa.

Búsqueda inicial del valor

Abrimos Cheat Engine y seleccionamos el proceso correspondiente al juego.

Cheat Engine, Abrir proceso

Como conocemos que el valor actual del contador es 0, realizamos una primera búsqueda utilizando un tipo de dato de 4 bytes, en “value” ponemos el valor 0 y pulsamos el botón First Scan.

Tras modificar el contador en el juego alcanzando un cubo, repetimos la búsqueda con el nuevo valor (1) haciendo clic en Next Scan. Repetimos este proceso iterativamente hasta filtrar la lista a un número reducido de memoria candidata. Tras varios escaneos, logramos filtrar los resultados a solo 2 direcciones.

Cheat Engine, Procesos Filtrados

Análisis de instrucciones de memoria

Hacemos clic derecho en cada una de las tres direcciones y ejecutamos Find out what writes to this address. Esto nos permitirá detectar la instrucción en ensamblador que escribe en la memoria.

Cheat Engine, Escritura en memoria

Descartamos por el momento la dirección 17F63AE6800 debido a que presenta escritura continua en segundo plano, incluso cuando el personaje permanece estático.

mov [rdi], edx

Escribe en la dirección base apuntada por rdi, sin desplazamiento. También puede ser la variable correcta, pero podría tratarse del primer campo de una estructura, un búfer o un destino genérico.


A continuación, recolectamos el último cubo dentro del juego para provocar la escritura en la memoria y capturar la instrucción correspondiente en la dirección que nos queda.

Cheat Engine, Captura de la escritura en memoria 17F63AE6804

En la ventana correspondiente a la dirección 17F63AE6804 tenemos la instrucción mov [rbx+24],edx.

Aquí, el registro rbx probablemente almacena la dirección base del objeto, y +24 corresponde al desplazamiento (offset) del atributo, es un patrón habitual en variables como vida, munición o coleccionables.


En esta ventana pulsamos sobre el botón “More Information” para analizar el contexto de la instrucción.

Cheat Engine, 17F63AE6804 Más Información

La secuencia de instrucciones que observamos es:

cmp [rbx+24], edx       ; Compara el valor actual en [rbx+24] con EDX
je  7FFA6F11FEFE        ; Si son iguales, salta (no vuelve a escribir)
mov [rbx+24], edx       ; Si son distintos, copia el valor de EDX en [rbx+24]

En el panel inferior observamos los valores de los registros tras la ejecución:

RAX = 00000000
RBX = 17F63AE67E0
RDX = 00000005

Haciendo la aritmética de punteros, confirmamos la dirección:

RBX + 0x24
17F63AE67E0 + 0x24 = 17F63AE6804.

Por lo tanto, la instrucción mov [rbx+24], edx se encarga de escribir el valor del registro EDX directamente en la dirección de memoria 17F63AE6804.

Por lo que sabemos que el comportamiento en C, seria así:

if (*(int *)(rbx + 0x24) != edx) {
    *(int *)(rbx + 0x24) = edx;
}

¿De dónde proviene el valor de EDX?

Esta ventana únicamente muestra que, en ese instante, EDX contenía el valor 00000005, pero no muestra cómo al registro. Para averiguarlo, el siguiente paso correcto es abrir Show disassembler y examinar las instrucciones anteriores.

¿Qué función desempeña la dirección 17F63AE6804 dentro del juego?

Aunque hemos confirmado que la instrucción mov [rbx+24], edx escribe el valor 5 en 17F63AE6804, todavía no sabemos si esta dirección contiene el contador principal de cubos, una copia sincronizada o un valor utilizado por otro componente del juego.


Para entender el origen de estos datos, pulsamos en el botón “Show Disassembler” y nos desplazamos un poco hacia arriba:

Cheat Engine, 17F63AE6804 Mostrar Desensamblador

Analizando el bloque de código completo:

lea  rsi,[rbx+20]               ; RSI apunta al campo situado en RBX+0x20 y guarda su dirección.
mov  rdx,[rax+000000B8]         ; Carga en RDX el valor almacenado en [RAX+0xB8].
mov  eax,6208CECB               ; Carga en EAX la constante mágica 0x6208CECB.
imul [rdx]                      ; Multiplicación con signo (EDX contiene los 32 bits superiores y EAX los inferiores)
sar  edx,09                     ; Desplaza EDX 9 bits a la derecha conservando el signo.
mov  eax,edx                    ; Copia el resultado parcial a EAX para calcular
shr  eax,1F                     ; Desplaza EAX 31 bits a la derecha. Produce 0 si es positivo y 1 si es negativo.
add  edx,eax                    ; Aplica la corrección de signo para que la división redondee hacia cero.
mov  [rsi],edx                  ; Guarda el resultado en la dirección apuntada por RSI.
cmp  [rbx+24],edx               ; Compara el campo situado en RBX+0x24 con el valor calculado.
je   GameAssembly.dll+73FEFE    ; Si ambos valores son iguales, salta a 73FEFE.
mov  [rbx+24],edx               ; Si los valores eran distintos, actualiza RBX+0x24

Notas.

Al analizar el desensamblado, vemos la carga de la constante 0x6208CECB (1644744395 en decimal) seguida de una multiplicación (imul) y un desplazamiento de bits sar edx, 09.

Esto no es un valor estático, sino un truco de optimización del compilador conocido como “división por multiplicación” (magic number multiplication). La instrucción de división (div) en x86/x64 es costosa en ciclos de CPU, por lo que el compilador la reemplaza calculando:

Divisor = 2^(32 + shift) / Magic_Number

Sustituyendo los valores del binario obtenemos 2^(32 + 9) / 1644744395 = 2^41 / 1644744395 = 1337

El bloque de código mov eax,6208CECB no está asignando 1337, sino que está ejecutando matemáticamente la operación: Variable / 1337

A continuación, colocamos un Punto de interrupción (breakpoint) justo antes de la instrucción imul [rdx] porque, en ese punto, RDX todavía conserva la dirección del valor que se utilizará como entrada en la multiplicación.

La instrucción imul con un solo operando opera multiplicando implícitamente el registro EAX por el valor contenido en la dirección apuntada (es decir, por [RDX]). El resultado de 64 bits de esta multiplicación se almacena en el par de registros EDX:EAX, donde EDX recibe la parte alta del producto. Por esta razón, inmediatamente después de ejecutarse la instrucción, la mitad inferior de RDX es sobrescrita por el resultado aritmético y la dirección de memoria original se pierde.

Cheat Engine, 17F63AE6804 Mostrar Desensamblador, interrupción

Copiamos entonces el valor de RDX y añadimos la dirección 17F63AE2A60 manualmente en Cheat Engine como un valor de 4 bytes.

El contenido correspondía al contador interno asociado a los cuatro cubos que he recogido. Como el juego almacena ese contador multiplicado por 1337, el valor esperado para cuatro cubos es 4 × 1337 = 5348.

Al sustituirlo por 20 × 1337 = 26740, la rutina divide posteriormente ese valor entre 1337 y obtiene 20. Finalmente, el resultado se almacena en RBX+0x20 y RBX+0x24, provocando que el juego muestre 20 de 20 cubos y active la condición de finalización.

CubeMadness2 Pawned

La flag ha sido obtenida con éxito y ha sido censurada para respetar las reglas de HTB.