CubeMadness2
CubeMadness2
Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Windows | Author: D3v0o0Nu11 | Date: 2026-02-05 | Status: Solved Techniques: anticheat_bypass, conditional_jump_bypass, il2cpp_metadata_extraction, remote_game_interaction, runtime_binary_patching, virtualprotectex, winrm_exploitation
Summary
Unity IL2CPP game protected by CodeStage AntiCheat Toolkit (ObscuredInt, ObscuredFloat, ObscuredString). The goal is to collect 20 cubes to get the flag, but only ~14 cubes exist in the game world, making legitimate victory impossible. The game runs on a remote Windows server accessible via WinRM.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
HackTheBox| ID:20260205_hackthebox_cubemadness2 - Tags: game_hacking, windows, binary_patching, unity, il2cpp, anticheat, codestage, obscuredint, obscuredstring
- Indicators: Unity IL2CPP game, GameAssembly.dll, CodeStage AntiCheat, ObscuredInt/ObscuredFloat/ObscuredString, impossible win condition
- Source:
20260205_hackthebox_cubemadness2.md
Foothold
Vulnerability / Misconfiguration
- Anticheat_bypass
- Conditional_jump_bypass
- Il2cpp_metadata_extraction
- Remote_game_interaction
- Runtime_binary_patching
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- anticheat_bypass
- conditional_jump_bypass
- il2cpp_metadata_extraction
- remote_game_interaction
- runtime_binary_patching
- virtualprotectex
- winrm_exploitation
- Tags: game_hacking, windows, binary_patching, unity, il2cpp, anticheat, codestage, obscuredint, obscuredstring
Original Writeup
<details><summary>Click to expand original content</summary>Description
Unity IL2CPP game protected by CodeStage AntiCheat Toolkit (ObscuredInt, ObscuredFloat, ObscuredString). The goal is to collect 20 cubes to get the flag, but only ~14 cubes exist in the game world, making legitimate victory impossible. The game runs on a remote Windows server accessible via WinRM.
Analysis
Metadata Extraction (Il2CppDumper)
IL2CPP compiles C# to native code but preserves metadata (class names, methods, fields) in global-metadata.dat. Il2CppDumper extracts this metadata:
GameAssembly.dll + global-metadata.dat → dump.cs, script.json, stringliteral.json
Key classes (names obfuscated):
| Class | Parent | Description | Key fields/methods |
|---|---|---|---|
CubeCounter (extends j) | MonoBehaviour | Cube counter | Text rmu (0x18), int rmv (0x20), int rmw (0x24) |
FlagCheck (extends m) | MonoBehaviour | Win check | SpriteRenderer rmx (0x18), string rmy (0x20) |
Player | MonoBehaviour | Player | TypeDefIndex 2814 |
Cube (extends g) | MonoBehaviour | Collectible cube | TypeDefIndex 2815 |
ObscuredString (extends g) | — | Encrypted string | TypeDefIndex 2820 |
FlagCheck (m) methods:
Start()RVA 0x740660Update()RVA 0x740810 — main win condition checkcbm()RVA 0x7409B0onh()RVA 0x740A40
Win Condition Analysis
In m.Update() (RVA 0x740810) found the check:
mov rcx, [rax+0xB8] ; load ObscuredInt cube counter cmp dword ptr [rcx], 0x6874 ; compare with encrypted threshold jge +0x17 ; if >= threshold → show flag (SpriteRenderer.SetActive)
0x6874— encrypted threshold value (ObscuredInt:hiddenValue = value ^ currentCryptoKey)jge(opcode7D) — conditional jump controlling flag sprite display- Flag is not stored in files — constructed at runtime from ObscuredString
Asset Extraction (AssetRipper)
AssetRipper extracted sprites, textures, scenes, but:
- Scripts are empty stubs (AssetRipper doesn't decompile IL2CPP)
- Flag text not found in any file — confirms runtime construction
CodeStage AntiCheat — ObscuredInt
ObscuredInt memory structure:
Offset 0x00: currentCryptoKey (int)
Offset 0x04: hiddenValue (int) = value ^ currentCryptoKey
Offset 0x08: inited (bool)
Offset 0x0C: fakeValue (int) = value (for integrity check)
Offset 0x10: fakeValueActive (bool)
AntiCheat compares hiddenValue ^ currentCryptoKey with fakeValue. On mismatch — the value is reset.
Failed Approaches
Memory Hacking (didn't work)
Created several tools for memory scanning and modification:
FS.exe— full ObscuredInt pattern scannerM.exe— integer value scannerW.exe— process memory writer
Result:
- Found ObscuredInt cube counter in memory
- Successfully changed to 19, then 20
- AntiCheat detected mismatch between
hiddenValueandfakeValueand reset the value - Even when updating both fields simultaneously — AntiCheat still caught the modification
Conclusion: ObscuredInt memory modification is unreliable due to CodeStage integrity checks.
Solution
Binary Patching — Bypassing the Condition Check
Instead of modifying data (counter value) — modify the code (check condition).
Step 1: Find GameAssembly.dll Base Address
$proc = Get-Process CubeMadness2
$ga = $proc.Modules | Where-Object { $_.ModuleName -eq "GameAssembly.dll" }
$base = $ga.BaseAddress # e.g., 0x7FFB668B0000
Step 2: Calculate jge Instruction Address
Runtime address = base + RVA_offset_of_jge_instruction
RVA found by analyzing disassembled m.Update() in GameAssembly.dll.
Step 3: Patch jge → jmp (unconditional)
Created P.exe — binary patching tool:
// Key steps:
// 1. OpenProcess with PROCESS_ALL_ACCESS
// 2. VirtualProtectEx — remove page protection (PAGE_EXECUTE_READWRITE)
// 3. WriteProcessMemory — replace opcode
// 4. VirtualProtectEx — restore protection
// Patch: 7D → EB
// 7D = jge (jump if greater or equal) — conditional jump
// EB = jmp short — unconditional jump
WriteProcessMemory(hProcess, targetAddr, new byte[] { 0xEB }, 1, out _);
Result: The "show flag" branch executes always, regardless of collected cube count.
Step 4: Capture Screen with Flag
Game runs on remote Windows server via WinRM (Session 0, non-interactive):
# WinRM runs in Session 0 — direct screenshots give empty screen # Solution: run via Scheduled Task in interactive Session 1 $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-File C:\screenshot.ps1" $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(5) Register-ScheduledTask -TaskName "Screenshot" -Action $action -Trigger $trigger -User "SYSTEM"
Screenshot script uses System.Drawing.Graphics.CopyFromScreen() for screen capture.
Final Attack Chain
Il2CppDumper → dump.cs (metadata)
↓
Analyze m.Update() → found jge (opcode 7D) for counter check
↓
P.exe: VirtualProtectEx + WriteProcessMemory → patch 7D → EB
↓
Scheduled Task → screenshot of interactive session
↓
HTB{REDACTED}
</details>
Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR