← Back to Writeups
HTBN/AReversing

CubeMadness2

XESXOR8/23/20265 min read
#reversing#htb#n/a

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
PortServiceVersionNotes
<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

  1. Anticheat_bypass
  2. Conditional_jump_bypass
  3. Il2cpp_metadata_extraction
  4. Remote_game_interaction
  5. 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

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
<command>

Flags

FlagLocationValue
flagREDACTED

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):

ClassParentDescriptionKey fields/methods
CubeCounter (extends j)MonoBehaviourCube counterText rmu (0x18), int rmv (0x20), int rmw (0x24)
FlagCheck (extends m)MonoBehaviourWin checkSpriteRenderer rmx (0x18), string rmy (0x20)
PlayerMonoBehaviourPlayerTypeDefIndex 2814
Cube (extends g)MonoBehaviourCollectible cubeTypeDefIndex 2815
ObscuredString (extends g)Encrypted stringTypeDefIndex 2820

FlagCheck (m) methods:

  • Start() RVA 0x740660
  • Update() RVA 0x740810 — main win condition check
  • cbm() RVA 0x7409B0
  • onh() 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 (opcode 7D) — 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 scanner
  • M.exe — integer value scanner
  • W.exe — process memory writer

Result:

  1. Found ObscuredInt cube counter in memory
  2. Successfully changed to 19, then 20
  3. AntiCheat detected mismatch between hiddenValue and fakeValue and reset the value
  4. 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 jgejmp (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-revise to fold lessons into XESXor_Methodology.md.

signed by XESXOR