Challenge / GamePwn

CubeMadness2

CubeMadness2 is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

EasyPublished 2024-07-21Sanitized local writeup

Scenario

CubeMadness2 attack path

CubeMadness2 is a sanitized challenge note from the local HTB archive, organized for quick review by category, difficulty, evidence flow, and reusable operator

Objective

Challenge walkthrough focused on GamePwn evidence, validation, and reusable operator lessons.

CubeMadness2 sanitized attack graph

Walkthrough flow

01

Treat the archive as a Unity IL2CPP Windows game and...

02

Export Unity textures first; if no proof appears,...

03

Use Il2CppDumper to recover script classes and method...

04

Disassemble the FlagCheck base class methods. The...

05

Reimplement the private metadata string decoder...

Source coverage

High source coverage

Status: complete. This article is generated from 6 sanitized Markdown sources and keeps raw flags, credentials, keys, cookies, and reusable secrets out of the rendered blog.

100% coverage
Evidence verdict

High confidence: the page is reconstructed from a primary walkthrough plus multiple supporting notes or evidence sources. Treat the chain as source-backed, while still checking the listed source files for sensitive values.

  • GamePwn/CubeMadness2/writeup.md
  • htb-challenge/GamePwn/CubeMadness2/notes.md
  • htb-challenge/GamePwn/CubeMadness2/memory-summary.md
  • htb-challenge/GamePwn/CubeMadness2/hypothesis-board.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__GamePwn__CubeMadness2__memory-summary.md.ad525f9bfa.md
  • HTB/_knowledge/exports/ctf-lightrag-latest-203412/documents/challenge__GamePwn__CubeMadness2__notes.md.e8163ade5e.md

Technical Walkthrough

Writeup

Challenge

  • Name: CubeMadness2
  • Category: GamePwn
  • Difficulty: Easy
  • Mode: file

Summary

CubeMadness2 is a Windows Unity IL2CPP challenge. The visible asset pass did not contain a rendered flag, so the solve moved into GameAssembly.dll plus global-metadata.dat. The key class chain was FlagCheck : m: its constructor loads a base64 string from an obfuscated private implementation metadata blob, Start() decodes that string into a 1280x720 texture, and Update() only displays it after the global cube counter reaches 0x6874.

The reproducible solve extracts the same metadata blob statically, applies the XOR transform used by the game's static constructor, base64-decodes the hidden PNG, OCRs the image, corrects the ambiguous leading glyphs by crop comparison, and stores the recovered HTB-format flag in loot/flag.txt.

Artifact Inventory

The provided ZIP extracts with the standard HTB archive password and contains a Unity IL2CPP Windows game:

  • GameAssembly.dll
  • HackTheBox CubeMadness2_Data/il2cpp_data/Metadata/global-metadata.dat
  • HackTheBox CubeMadness2_Data/sharedassets0.assets
  • HackTheBox CubeMadness2_Data/level0

The Unity scene showed Player, Cube, CubeCounter, and FlagCheck scripts. Texture export from Unity assets produced no direct flag image, which made the IL2CPP metadata path the stronger branch.

Analysis

Local evidence showed the scene only contained 14 cube pickup objects while the UI text referenced Cubes: 0 / 20, so normal collection was unlikely to be the whole solve. Il2CppDumper recovered the obfuscated classes:

  • g / Cube: updates the global cube state.
  • j / CubeCounter: reads and renders that state.
  • m / FlagCheck: owns the hidden sprite payload.

Disassembly confirmed m.Start() calls System.Convert.FromBase64String, creates a Texture2D(1280, 720), loads image bytes into it, creates a sprite, and disables the renderer. m.Update() enables the renderer only when the global counter is at least 0x6874.

The m constructor calls the private string decoder with offset 0x1ca and length 0x4c40. dump.cs identifies the backing metadata blob as <PrivateImplementationDetails>{9BE6900D-7F10-415B-<secret redacted>}.a.a_ at metadata offset 0x1609cb. The decoder's static constructor transforms each byte as byte ^ (index & 0xff) ^ 0xaa. After applying that transform, the slice at 0x1ca starts as PNG base64 and decodes to the hidden flag image.

Solve

Run:

bash
cd <local workspace>
.venv/bin/python solve/solve.py

The script writes the decoded raw image and OCR output into loot/, writes a redacted image plus metadata summary into analysis/, and stores the flag candidate for harness capture. OCR reads several glyphs incorrectly; indexed crop comparison shows the rendered phrase uses zero, 8, 5 in FU5C, and another zero in UNKN0WN. The harness then validates the flag format with:

bash
cd <local workspace>
python3 scripts/challenge_harness.py capture-flag GamePwn/CubeMadness2 --from loot/flag-candidate.txt

Flag

Raw flag is stored in loot/flag.txt and intentionally not reproduced here.

Lessons

  • For Unity IL2CPP GamePwn challenges, texture export alone can miss payloads generated at runtime from metadata strings.
  • When Il2CppDumper shows private implementation metadata offsets, validate them directly against global-metadata.dat.
  • Obfuscated string decoders are often cheaper to reimplement than patching or running the game.

Source-Backed Dossier

The sections below are merged from companion Markdown notes for the same case. They are rendered after sanitization so the article stays precise without publishing raw flags, credentials, or target-specific secrets.

Notes

Scope

  • Challenge: CubeMadness2
  • Category: GamePwn
  • Difficulty: Easy
  • Mode: file
  • Remote instance: none
  • Start time: 2026-06-09T15:05:29Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c7365-32e0-425e-8586-41d85153daa6.zip20024496<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 52 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T15:05:29Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T15:05:38Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T15:05:51Zhypothesis recordedhypothesis-board.mdUnity sequel asset-first path: extract the archive, compare engine/assets to CubeMadness1, export Unity textures/assets, and recover any rendered flag before attempting runtime patching.MediumUnzip with HTB archive password, inspect file tree for Unity Data/sharedassets/global-metadata, then run targeted strings and texture export.
2026-06-09T15:05:51Zresearch taskanalysis/research/task-20260609T150551291460Z-621b0c66.mdResearch task created for advisory investigationMediumRecord research output
2026-06-09T15:06:10Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-09T15:21:05Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-09T15:22:53Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-09T15:25:09Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-09T15:28:33Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T07:20:00Zvisual flag correction after rejected submissionsloot/flag-image.png, analysis/flag-tight-crop.png, analysis/char-contact-sheet-indexed.png, analysis/char-04.png, analysis/char-05.png, analysis/char-08.png, analysis/char-23.pngPrior OCR/manual readings were rejected by HTB. Indexed crop comparison shows the rendered text is 08FU5C473D_4ND_UNKN0WN: first body glyph is zero-with-dot, second is 8, the FUSC-looking segment is actually FU5C, and the UNKNOWN glyph is UNKN0WN. Updated solve/solve.py to normalize OCR to this exact visual reading.HighRe-run solve script and capture the corrected candidate through the harness.
2026-06-10T08:33:07Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T08:33:07Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08:33:57Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08:33:57Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T08:39:48Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T08:39:48Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08:45:33Zflag captureloot/flag.txtHTB-format flag captured; raw value kept in loot onlyHighWrite solution and run completion gate
2026-06-10T08:45:33Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The archive is a Windows Unity IL2CPP game. The relevant runtime files are GameAssembly.dll and HackTheBox CubeMadness2_Data/il2cpp_data/Metadata/global-metadata.dat.
  • Unity scene/object analysis identified Player, Cube, CubeCounter, and FlagCheck scripts. The scene has fewer cube pickups than the displayed target count, so the solve needed static/runtime logic analysis rather than normal collection.
  • Texture export from Unity assets did not reveal the flag directly.
  • Il2CppDumper plus disassembly showed FlagCheck : m stores a base64 image payload in an obfuscated private implementation metadata blob and only renders it when the global cube counter reaches 0x6874.
  • The verified metadata blob is at global-metadata.dat offset 0x1609cb; the byte transform is byte ^ (index & 0xff) ^ 0xaa; the flag-image base64 slice is offset 0x1ca, length 0x4c40.
  • solve/solve.py reproduces the extraction and stores raw outputs only under loot/. analysis/flag-image-redacted.png is safe to view. The corrected OCR normalization is the zero/8/zero reading from analysis/flag-tight-crop.png.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: GamePwn
  • Challenge: CubeMadness2
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Treat the archive as a Unity IL2CPP Windows game and preserve originals under files/.
  2. Export Unity textures first; if no flag appears, inspect GameAssembly.dll, global-metadata.dat, scene objects, and script metadata.
  3. Use Il2CppDumper to recover script classes and method RVAs. In this challenge the key classes were Cube, CubeCounter, and FlagCheck.
  4. Disassemble the FlagCheck base class methods. The constructor loaded a private decoded string; Start() base64-decoded it into a texture; Update() hid or showed the sprite based on a high cube-counter threshold.
  5. Reimplement the private metadata string decoder instead of trying to play or patch the game. The static byte-array transform was byte ^ (index & 0xff) ^ 0xaa.
  6. Decode the relevant metadata slice to a PNG, OCR the image, and capture the HTB-format flag through the harness.

Reusable Lessons

  • Unity IL2CPP GamePwn flags may be generated from metadata-backed strings rather than stored as ordinary textures.
  • A scene mismatch between collectible count and UI target is a strong hint to inspect win/flag logic rather than continue manual gameplay.
  • Il2CppDumper Metadata offset comments can be directly useful for extracting private implementation blobs from global-metadata.dat.
  • Keep decoded flag images and OCR text in loot/; store only redacted images in analysis/.

Dead Ends

  • Raw Unity texture export did not reveal the flag.
  • Normal collection was unlikely because the scene had fewer pickup objects than the UI target.

Tool Quirks

  • Docker was present but the daemon was unavailable, so Il2CppDumper was run locally with a downloaded .NET runtime.
  • Tesseract OCR was sufficient once the hidden PNG was decoded.

Evidence Paths

  • analysis/il2cppdump/dump.cs
  • analysis/il2cppdump/m-flag-disasm-annotated.txt
  • analysis/metadata-payload-summary.txt
  • analysis/flag-image-redacted.png
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Hypothesis Board

Keep no more than 3 active hypotheses on Easy/Medium and 5 on Hard unless the user explicitly asks for breadth.

RankPathEvidenceMissing ProofCheapest ValidationConfidenceStatus
1Unity sequel asset-first path: extract the archive, compare engine/assets to CubeMadness1, export Unity textures/assets, and recover any rendered flag before attempting runtime patching.Challenge name CubeMadness2 and local solved CubeMadness1 show Unity GamePwn asset-texture recovery was decisive after runtime UI patching failed.Unzip with HTB archive password, inspect file tree for Unity Data/sharedassets/global-metadata, then run targeted strings and texture export.MediumActive

Closed Branches

BranchEvidence TestedFailure OutputReason ClosedRevisit Condition

Memory Summary

approval_required: true

Sanitized Memory Summary

Metadata

  • Platform: HackTheBox Challenges
  • Category: GamePwn
  • Challenge: CubeMadness2
  • Difficulty: Easy
  • Source workspace: <local workspace>

Validated Solve Chain

Concepts only. Do not include raw flags, reusable credentials, tokens, cookies, private keys, or live secrets.

  1. Treat the archive as a Unity IL2CPP Windows game and preserve originals under files/.
  2. Export Unity textures first; if no flag appears, inspect GameAssembly.dll, global-metadata.dat, scene objects, and script metadata.
  3. Use Il2CppDumper to recover script classes and method RVAs. In this challenge the key classes were Cube, CubeCounter, and FlagCheck.
  4. Disassemble the FlagCheck base class methods. The constructor loaded a private decoded string; Start() base64-decoded it into a texture; Update() hid or showed the sprite based on a high cube-counter threshold.
  5. Reimplement the private metadata string decoder instead of trying to play or patch the game. The static byte-array transform was byte ^ (index & 0xff) ^ 0xaa.
  6. Decode the relevant metadata slice to a PNG, OCR the image, and capture the HTB-format flag through the harness.

Reusable Lessons

  • Unity IL2CPP GamePwn flags may be generated from metadata-backed strings rather than stored as ordinary textures.
  • A scene mismatch between collectible count and UI target is a strong hint to inspect win/flag logic rather than continue manual gameplay.
  • Il2CppDumper Metadata offset comments can be directly useful for extracting private implementation blobs from global-metadata.dat.
  • Keep decoded flag images and OCR text in loot/; store only redacted images in analysis/.

Dead Ends

  • Raw Unity texture export did not reveal the flag.
  • Normal collection was unlikely because the scene had fewer pickup objects than the UI target.

Tool Quirks

  • Docker was present but the daemon was unavailable, so Il2CppDumper was run locally with a downloaded .NET runtime.
  • Tesseract OCR was sufficient once the hidden PNG was decoded.

Evidence Paths

  • analysis/il2cppdump/dump.cs
  • analysis/il2cppdump/m-flag-disasm-annotated.txt
  • analysis/metadata-payload-summary.txt
  • analysis/flag-image-redacted.png
  • solve/solve.py
  • loot/flag.txt

Ingestion Decision

  • Proposed for LightRAG: yes
  • Requires user approval before ingestion: yes

Notes

Notes

Scope

  • Challenge: CubeMadness2
  • Category: GamePwn
  • Difficulty: Easy
  • Mode: file
  • Remote instance: none
  • Start time: 2026-06-09T15:05:29Z
  • Operator: harness
  • State file: challenge-state.json

Harness Status

  • Current phase: see challenge-state.json
  • Next allowed actions: see next-action.json
  • Raw flags and sensitive material stay in loot/ only. Do not paste them here.

Artifact Inventory

FileSizeSHA256TypeNotes
files/a12c7365-32e0-425e-8586-41d85153daa6.zip20024496<hash redacted>Zip archive data, at least v1.0 to extract, compression method=storezip entries: 52 shown in artifact inventory JSON

Evidence Ledger

TimeActionOutput/FileFindingConfidenceNext
2026-06-09T15:05:29Zharness initchallenge-state.jsonWorkspace initialized with deterministic state fileHighInventory artifacts
2026-06-09T15:05:38Zartifact inventoryanalysis/artifact-inventory.json1 artifact(s) inventoriedHighBuild or update hypotheses
2026-06-09T15: <REDACTED>, compare engine/assets to CubeMadness1, export Unity textures/assets, and recover any rendered flag before attempting runtime patching.MediumUnzip with HTB archive password, inspect file tree for Unity Data/sharedassets/global-metadata, then run targeted strings and texture export.
2026-06-09T15:05:51Zresearch taskanalysis/research/task-20260609T150551291460Z-621b0c66.mdResearch task created for advisory investigationMediumRecord research output
2026-06-09T15:06:10Zresearch recordanalysis/research/research-records.mdResearch tagged PARTIALMediumValidate against current evidence
2026-06-09T15: <REDACTED>
2026-06-09T15:22:53Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-09T15: <REDACTED>
2026-06-09T15: <REDACTED>
2026-06-10T07: <REDACTED>, analysis/flag-tight-crop.png, analysis/char-contact-sheet-indexed.png, analysis/char-04.png, analysis/char-05.png, analysis/char-08.png, analysis/char-23.pngPrior OCR/manual readings were rejected by HTB. Indexed crop comparison shows the rendered text is 08FU5C473D_4ND_UNKN0WN: <REDACTED>, second is 8, the FUSC-looking segment is actually FU5C, and the UNKNOWN glyph is UNKN0WN. Updated solve/solve.py to normalize OCR to this exact visual reading.HighRe-run solve script and capture the corrected candidate through the harness.
2026-06-10T08: <REDACTED>
2026-06-10T08:33:07Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08:33:57Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08: <REDACTED>
2026-06-10T08: <REDACTED>
2026-06-10T08:39:48Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval
2026-06-10T08: <REDACTED>
2026-06-10T08:45:33Zcompletion gatechallenge-state.jsonCompletion gate passed; state marked COMPLETEHighOptional sanitized memory summary approval

Key Findings

  • The archive is a Windows Unity IL2CPP game. The relevant runtime files are GameAssembly.dll and HackTheBox CubeMadness2_Data/il2cpp_data/Metadata/global-metadata.dat.
  • Unity scene/object analysis identified Player, Cube, CubeCounter, and FlagCheck scripts. The scene has fewer cube pickups than the displayed target count, so the solve needed static/runtime logic analysis rather than normal collection.
  • Texture export from Unity assets did not reveal the flag directly.
  • Il2CppDumper plus disassembly showed FlagCheck : m stores a base64 image payload in an obfuscated private implementation metadata blob and only renders it when the global cube counter reaches 0x6874.
  • The verified metadata blob is at global-metadata.dat offset 0x1609cb; the byte transform is byte ^ (index & 0xff) ^ 0xaa; the flag-image base64 slice is offset 0x1ca, length 0x4c40.
  • solve/solve.py reproduces the extraction and stores raw outputs only under loot/. analysis/flag-image-redacted.png is safe to view. The corrected OCR normalization is the zero/8/zero reading from analysis/flag-tight-crop.png.

RAG / Advisory Memory

RAG output is advisory only. Record evaluated retrievals with:

bash
scripts/challenge_harness.py rag-record <workspace> --query "..." --tag MATCHED|PARTIAL|MISSING|<secret redacted>|GENERIC --validation "..."

Secrets/Flags

Raw flags and sensitive material stay in loot/ only. Use scripts/challenge_harness.py capture-flag to validate and record flag capture without printing the value.

Technical analogy

How to remember this solve

Think of the game like an arcade cabinet with a score counter behind the glass. The solve is finding where the game stores state and reading or changing it at the right moment.

For CubeMadness2, keep the mental model simple: identify the trusted assumption, prove it with the smallest safe test, then automate or repeat only the part that directly leads to the flag.