Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
HackTheBox/Challenges/Challenge cubemadness2.md READ_ONLY

Challenge CubeMadness2

Este reto consiste en un juego de plataformas en 3D donde el jugador debe recolectar 20 cubos para que aparezca una flag (con solo 14 disponibles en el mapa), y la solución consiste en analizar la estructura del juego IL2CPP con il2cpp-dumper y Cutter para localizar la función de descifrado en memoria, y utilizar un script de Frida para invocar esta función nativa directamente, evadiendo el requisito de los cubos y extrayendo la flag descifrada.

Análisis Inicial y Estructura del Juego

Una vez extraídos los archivos del reto, se puede observar la estructura del directorio del juego.

$ ls -R
CubeMadness2

./CubeMadness2:
baselib.dll       'HackTheBox CubeMadness2.exe'   UnityCrashHandler64.exe
GameAssembly.dll  'HackTheBox CubeMadness2_Data'  UnityPlayer.dll

'./CubeMadness2/HackTheBox CubeMadness2_Data':
app.info     globalgamemanagers         globalgamemanagers.assets.resS  level0     RuntimeInitializeOnLoads.json  sharedassets0.assets
boot.config  globalgamemanagers.assets  il2cpp_data                     Resources  ScriptingAssemblies.json       sharedassets0.assets.resS

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data':
etc  Metadata  Resources

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc':
mono

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono':
2.0  4.0  4.5  browscap.ini  config  mconfig

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/2.0':
Browsers  DefaultWsdlHelpGenerator.aspx  machine.config  settings.map  web.config

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/2.0/Browsers':
Compat.browser

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/4.0':
Browsers  DefaultWsdlHelpGenerator.aspx  machine.config  settings.map  web.config

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/4.0/Browsers':
Compat.browser

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/4.5':
Browsers  DefaultWsdlHelpGenerator.aspx  machine.config  settings.map  web.config

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/4.5/Browsers':
Compat.browser

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/etc/mono/mconfig':
config.xml

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/Metadata':
global-metadata.dat

'./CubeMadness2/HackTheBox CubeMadness2_Data/il2cpp_data/Resources':
mscorlib.dll-resources.dat

'./CubeMadness2/HackTheBox CubeMadness2_Data/Resources':
unity_builtin_extra  'unity default resources'

El juego es un ejecutable de Windows, por lo que se utiliza Wine para ejecutarlo en un entorno Linux. Al jugar, se evidencia que el objetivo es recolectar 20 cubos.

$ cd CubeMadness2
$ wine HackTheBox\ CubeMadness2.exe
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

pci id for fd 37: 10de:25a0, driver (null)
pci id for fd 38: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

pci id for fd 37: 10de:25a0, driver (null)
pci id for fd 38: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

0024:fixme:combase:RoSetErrorReportingFlags (00000004): stub
0024:fixme:ntdll:NtQuerySystemInformation info_class SYSTEM_PERFORMANCE_INFORMATION
libEGL warning: pci id for fd 189: 10de:25a0, driver (null)

pci id for fd 190: 10de:25a0, driver (null)
...

Sin embargo, solo 14 cubos son visibles en el mapa. Esto sugiere que la solución requiere modificar los parámetros del juego o analizar su lógica interna.

Análisis de la Estructura IL2CPP

Dado que se trata de un juego creado con Unity e IL2CPP (evidenciado por la presencia de GameAssembly.dll y la carpeta il2cpp_data), se utiliza il2cpp-dumper para extraer las librerías dinámicas simuladas y poder analizar la estructura de clases.

$ sudo il2cpp-dumper GameAssembly.dll HackTheBox\ CubeMadness2_Data/il2cpp_data/Metadata/global-metadata.dat ./dumper
Initializing metadata...
Metadata Version: 27
Initializing il2cpp file...
Il2Cpp Version: 27
Searching...
Change il2cpp version to: 27.1
CodeRegistration : 18087c8f0
MetadataRegistration : 18087fdc0
Dumping...
Done!
Generate struct...
Done!
Generate dummy dll...
Done!
Press any key to exit...

Dentro de los archivos generados en la carpeta dumper, se inspecciona el archivo Assembly-CSharp.dll utilizando herramientas como ILSpy para identificar las clases principales. Se encuentran clases de interés como CubeCounter, FlagCheck y Player, de las cuales se obtienen sus desplazamientos (offsets) y direcciones virtuales relativas (RVA):

using Il2CppDummyDll;

[Token(Token = "0x600001C")]
[Address(RVA = "0x1AE4F0", Offset = "0x1AD2F0", VA = "0x1801AE4F0")]
public CubeCounter()
{
}

// FlagCheck
using Il2CppDummyDll;

[Token(Token = "0x600001D")]
[Address(RVA = "0x73E350", Offset = "0x73D150", VA = "0x18073E350")]
public FlagCheck()
{
}

// Player
using Il2CppDummyDll;
using UnityEngine;

[Token(Token = "0x6000016")]
[Address(RVA = "0x73E620", Offset = "0x73D420", VA = "0x18073E620")]
public Player()
{
}

Con los valores de RVA obtenidos, se abre el archivo GameAssembly.dll en Cutter para analizar el pseudo-código de la función FlagCheck. Durante el análisis, se encuentra una condición que compara la cantidad de cubos recolectados por el usuario con el valor 13 en hexadecimal (0xd).

if (0xd < *(uint32_t *)(iVar2 + 0x18)) {

Al revisar el pseudo-código completo de la función, se comprende mejor la lógica de validación de la flag.

void FlagCheck(int64_t arg1)
{
    code *pcVar1;
    int64_t iVar2;
    // ... [Initialization] ...
    iVar2 = *(int64_t *)(*(int64_t *)(_data.180933ef8 + 0xb8) + 20000);
    if (iVar2 != 0) {
        if (0xd < *(uint32_t *)(iVar2 + 0x18)) {
            iVar2 = *(int64_t *)(iVar2 + 0x88);
            if (iVar2 == 0) {
                iVar2 = fcn.18073e830(0xd, 0x1ca, 0x4c40);
            }
            *(int64_t *)(arg1 + 0x20U) = iVar2;
            fcn.18010dab0((uintmax_t)(int64_t *)(arg1 + 0x20U));
            return;
        }
        iVar2 = fcn.18010dcc0();
        fcn.18010e030(iVar2, 0);
        pcVar1 = (code *)swi(3);
        (*pcVar1)();
        return;
    }
}

Se observa que el validador de la flag se ejecuta mediante un disparador invisible. Si el jugador no cumple la condición de tener más de 13 cubos, el juego se bloquea debido a una interrupción de software (swi(3)). Como el juego no colapsa al recoger los 14 cubos visibles, se deduce que el disparador no se activa simplemente recolectando cubos. Si se cumple la condición de tener más de 13 cubos, el programa llama a la función fcn.18073e830, que aparentemente descifra la flag, y pasa la dirección de memoria de dicha flag a fcn.18010dab0. Así, se concluye que la flag nunca se imprime en la interfaz del juego, sino que permanece en la memoria RAM.

Extracción de la Flag con Frida

Debido a que la flag no se muestra en el juego, se utiliza Frida para invocar directamente la función de descifrado y recuperar la flag de la memoria. Primero, se descarga y extrae la versión de Frida-server para Windows.

$ cd ..
$ wget https://github.com/frida/frida/releases/download/17.15.3/frida-server-17.15.3-windows-x86_64.exe.xz
server-17.15.3-windows-x86_64.exe.xz
--2026-06-26 11:56:32--  https://github.com/frida/frida/releases/download/17.15.3/frida-server-17.15.3-windows-x86_64.exe.xz
Loaded CA certificate '/etc/ssl/certs/ca-certificates.crt'
Resolving github.com (github.com)... 140.82.113.4
Connecting to github.com (github.com)|140.82.113.4|:443... connected.
...
Resolving release-assets.githubusercontent.com (release-assets.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to release-assets.githubusercontent.com (release-assets.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 21262844 (20M) [application/octet-stream]
Saving to: ‘frida-server-17.15.3-windows-x86_64.exe.xz’

frida-server-17.15.3-windows-x86_64.exe.x 100%[====================================================================================>]  20.28M   625KB/s    in 28s

2026-06-26 11:57:00 (746 KB/s) - ‘frida-server-17.15.3-windows-x86_64.exe.xz’ saved [21262844/21262844]
$ unxz frida-server-17.15.3-windows-x86_64.exe.xz

Se ejecuta el juego y el servidor de Frida simultáneamente a través de Wine.

$ wine CubeMadness2/HackTheBox\ CubeMadness2.exe
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

pci id for fd 37: 10de:25a0, driver (null)
pci id for fd 38: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

pci id for fd 37: 10de:25a0, driver (null)
pci id for fd 38: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 36: 10de:25a0, driver (null)

0024:fixme:combase:RoSetErrorReportingFlags (00000004): stub
0024:fixme:ntdll:NtQuerySystemInformation info_class SYSTEM_PERFORMANCE_INFORMATION
libEGL warning: pci id for fd 189: 10de:25a0, driver (null)

pci id for fd 190: 10de:25a0, driver (null)
pci id for fd 191: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 189: 10de:25a0, driver (null)

pci id for fd 190: 10de:25a0, driver (null)
pci id for fd 191: 10de:25a0, driver (null)
libEGL warning: egl: failed to create dri2 screen
libEGL warning: pci id for fd 189: 10de:25a0, driver (null)
...
$ wine frida-server-17.15.3-windows-x86_64.exe
01f0:fixme:kernelbase:AppPolicyGetThreadInitializationType FFFFFFFFFFFFFFFA, 00007FFFFEE4FF50

Se desarrolla un script en JavaScript para Frida con el objetivo de conectarse al proceso del juego, localizar GameAssembly.dll e invocar la función de descifrado ubicada en la dirección relativa 0x73e830 pasándole los tres enteros requeridos (0xd, 0x1ca y 0x4c40).

$ cat exploit.js

setTimeout(function() {
    console.log("[*] Searching GameAssembly.dll...");
    const module = Process.findModuleByName('GameAssembly.dll');

    if (module) {
        const base = module.base;
        console.log("[*] GameAssembly found in: " + base);

        // Create a pointer to the function fcn.18073e830 (RVA: 0x73e830)
        // Get 3 integers (int, int, int) and throws back a pointer
        const decryptFunc = new NativeFunction(
            base.add(0x73e830), 'pointer', ['int', 'int', 'int']);

        console.log("[*] Calling the decrypt function...");

        // Arguments obtained in Cutter
        const stringPtr = decryptFunc(0xd, 0x1ca, 0x4c40);

        const length = stringPtr.add(0x10).readS32();

        const flag = stringPtr.add(0x14).readUtf16String(length);

        console.log("\n[+] ==========================================");
        console.log("[+] FLAG OBTAINED: " + flag);
        console.log("[+] ==========================================\n");
    } else {
        console.log("[-] GameAssembly.dll not found");
    }
}, 1000);

Se ejecuta el exploit con Frida y se guarda la salida.

$ frida -H 127.0.0.1 -n "HackTheBox CubeMadness2.exe" -l exploit.js > frida_output.txt

Posteriormente, se procesa la salida para extraer la cadena en formato Base64 y se decodifica en un archivo de imagen PNG, ya que la flag decodificada es en realidad una imagen.

$ grep "FLAG OBTAINED:" frida_output.txt | awk '{print $4}' | base64 -d > flag.png

Al abrir el archivo PNG, se recupera exitosamente la flag final del reto.

HTB{FLAG}