Challenge CubeMadness2
This challenge consists of a 3D platformer game where the player must collect 20 cubes to spawn a flag (with only 14 available on the map), and the solution consists of analyzing the IL2CPP game structure with il2cpp-dumper and Cutter to locate the flag decryption function in memory, and utilizing a Frida script to invoke this native function directly to bypass the cube requirement and extract the decrypted flag.
Initial Analysis and Game Structure
Once the challenge files are extracted, the structure of the game directory can be observed.
$ 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'
Since the game is a Windows executable, Wine is used to run it on a Linux environment. By playing the game, it becomes evident that the objective is to collect 20 cubes.
$ 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)
...
However, only 14 cubes are visible on the map. This implies that the solution requires modifying the game parameters or analyzing its internal logic.
IL2CPP Structure Analysis
As this is a game built with Unity and IL2CPP (indicated by the presence of GameAssembly.dll and the il2cpp_data folder), il2cpp-dumper is executed to extract the simulated dynamic libraries and analyze the class structure.
$ 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...
Inside the files generated in the dumper folder, the Assembly-CSharp.dll file is inspected using tools like ILSpy to identify the main classes. Interesting classes such as CubeCounter, FlagCheck, and Player are found, and their offsets and relative virtual addresses (RVA) are obtained:
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()
{
}
With the obtained RVA values, the GameAssembly.dll file is opened in Cutter to analyze the pseudo-code of the FlagCheck function. During the analysis, a condition is found that compares the number of cubes collected by the user with the hexadecimal value 13 (0xd).
if (0xd < *(uint32_t *)(iVar2 + 0x18)) {
Reviewing the complete pseudo-code of the function provides a better understanding of the flag validation logic.
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;
}
}
It is observed that the flag validator runs through an invisible trigger. If the player does not meet the condition of having more than 13 cubes, the game crashes due to a software interrupt (swi(3)). Since the game does not crash when the 14 visible cubes are collected, it is deduced that the trigger is not activated just by collecting cubes. If the condition of having more than 13 cubes is met, the program calls the function fcn.18073e830, which apparently decrypts the flag, and passes its memory address to fcn.18010dab0. Thus, it is concluded that the flag is never printed on the game interface, but remains in the RAM.
Flag Extraction with Frida
Because the flag is not displayed in the game, Frida is utilized to directly invoke the decryption function and recover the flag from memory. First, the Windows version of Frida-server is downloaded and extracted.
$ 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
The game and the Frida server are executed simultaneously using 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
A JavaScript script is developed for Frida to connect to the game process, locate GameAssembly.dll, and invoke the decryption function located at the relative address 0x73e830 by passing the three required integers (0xd, 0x1ca, and 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);
The exploit is run with Frida, saving the output.
$ frida -H 127.0.0.1 -n "HackTheBox CubeMadness2.exe" -l exploit.js > frida_output.txt
Subsequently, the output is processed to extract the Base64 string and decode it into a PNG image file, since the decoded flag is actually an image.
$ grep "FLAG OBTAINED:" frida_output.txt | awk '{print $4}' | base64 -d > flag.png
By opening the PNG file, the final flag of the challenge is successfully recovered.
HTB{FLAG}