$ cat writeup.md…
$ cat writeup.md…
HackTheBox
Task: Unity Mono game with OPS Obfuscator string encryption and ACTk anti-cheat. Solution: Decompiled Assembly-CSharp.dll with monodis, identified decoy flag in ObscuredString, then extracted the real flag from a hidden Tilemap asset using UnityPy — the flag was drawn as pixel-art tiles revealed only at score >= 1M.
A game development startup has created 'InfiniteDoge', but it appears to be broken. If you manage to fix it, let us know if you enjoyed this promising game.
Assembly-CSharp.dll in Managed/ folder (NOT IL2CPP)decrypted[i] = encrypted[i] ^ i ^ 0xAA| Class | Purpose |
|---|---|
a | Camera controller — dw(int,int,int) moves camera to position |
b | Destroyer — destroys game objects after delay |
c | Holds ObscuredString brc field — DECOY flag HTB{S0m3th1ng_h3r3_h4ck!} (never displayed!) |
e | Doge/coin controller — score increment, win condition check |
o | TilemapRenderer controller — hides/shows tilemap with the REAL flag |
Player | Player movement, lives, score (ObscuredInt) |
<PrivateImplementationDetails> | OPS string decryption |
The static constructor (.cctor) of <PrivateImplementationDetails> class:
D_00017318a__[i] = (a__[i] ^ i ^ 0xAA) & 0xFFa(), b(), etc.) extract substrings by offset+lengthClass e (doge controller) Update() method checks if score >= 1,000,000:
IL_00ed: ldc.i4 1000000
IL_00f2: blt.s IL_0108
IL_00f4: ldarg.0
IL_00f5: ldfld class a e::brf
IL_00fa: ldc.i4 -387
IL_00ff: ldc.i4.s -11
IL_0101: ldc.i4.s -10
IL_0103: callvirt instance void class a::dw(int32, int32, int32)
When score >= 1M: camera moves to position (-387, -11, -10).
Class o (TilemapRenderer controller):
Start(): Gets TilemapRenderer component and disables it (set_enabled(false))Update(): When score >= 1,000,000, enables the TilemapRenderer (set_enabled(true))This means the flag is drawn as tiles in a Tilemap that becomes visible only when you "win".
monodis Assembly-CSharp.dll > assembly.il # Output: 44,252 lines of IL disassembly
# Decrypt the first string (method a() → offset=0, length=25) raw = bytes.fromhex('e2ffead2fd9fc19ed6cb91cfc1f8cc96c888e7d18adcd79ccf') flag_decoy = bytes((raw[i] ^ i ^ 0xAA) & 0xFF for i in range(25)) # Result: HTB{S0m3th1ng_h3r3_h4ck!}
This is stored in class c field brc as ObscuredString but never read or displayed anywhere — a deliberate decoy/red herring.
#!/usr/bin/env python3 import UnityPy from PIL import Image, ImageDraw env = UnityPy.load("InfiniteDoge_Data") for obj in env.objects: if obj.type.name == "Tilemap": data = obj.read() # 282 tiles, X range: -398 to -303, Y range: -22 to -4 # 3 different sprite indices (0=green, 1=red, 2=blue) positions = [] min_x, max_x = float('inf'), float('-inf') min_y, max_y = float('inf'), float('-inf') for pos_data, tile_data in data.m_Tiles: x, y = pos_data.x, pos_data.y positions.append((x, y, tile_data.m_TileSpriteIndex)) min_x, max_x = min(min_x, x), max(max_x, x) min_y, max_y = min(min_y, y), max(max_y, y) scale = 10 width = (max_x - min_x + 1) * scale height = (max_y - min_y + 1) * scale img = Image.new('RGB', (width, height), 'black') draw = ImageDraw.Draw(img) colors = {0: '#00FF00', 1: '#FF0000', 2: '#0000FF'} for x, y, sprite_idx in positions: px = (x - min_x) * scale py = (max_y - y) * scale # Flip Y axis draw.rectangle([px, py, px+scale-1, py+scale-1], fill=colors.get(sprite_idx, 'white')) img.save('tilemap_flag.png') print(f"Saved tilemap: {len(positions)} tiles")
The rendered tilemap image clearly shows pixel-art text:
FLAG
IS
HTB{UN1TY_M45T3R}
HTB{S0m3th1ng_h3r3_h4ck!} stored in class c ObscuredString field brc — set in constructor but NEVER read or displayed anywhere. Pure red herring.e had multiple methods (gwj, Update, mxe) with different score thresholds and camera positions — only Update() is the real one called by Unity.Use this technique when:
Assembly-CSharp.dll in Managed/ folder — Unity Mono (not IL2CPP)OPS.Obfuscator.dll present — strings encrypted with XOR using index and constant 0xAAACTk.Runtime.dll — CodeStage Anti-Cheat Toolkit, ObscuredInt/ObscuredStringUpdate() is called by Unity$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar