$ cat writeup.md…
$ cat writeup.md…
HackTheBox
Challenge provides an ELF 64-bit PIE executable (not stripped) — an SDL2 raycasting game in Wolfenstein 3D style, and an `assets.dmp` file (11.9 MB) containing map data, skybox, and textures. Unlike the related NoClip challenge which encoded the flag in individual character textures, NoMap3D encodes
"I'm lost in this green cube hell. I have to find a way out of it."
Challenge provides an ELF 64-bit PIE executable (not stripped) — an SDL2 raycasting game in Wolfenstein 3D style, and an assets.dmp file (11.9 MB) containing map data, skybox, and textures. Unlike the related NoClip challenge which encoded the flag in individual character textures, NoMap3D encodes the flag as ASCII art formed by the wall layout itself.
Flag format: HTB{...}
file nomap3d # ELF 64-bit LSB pie executable, x86-64, not stripped, dynamically linked nm nomap3d | grep -E "player|raycast|colf|window|event|asset" # load_assets, player_init, raycast, event, input # colf.c, window.c, event.c, player.c, raycast.c
The binary uses the same SDL2 raycasting engine as the NoClip challenge. Key modules: collision detection (colf.c), player control (player.c), raycasting renderer (raycast.c), input handling (event.c), SDL2 window (window.c).
The name "NoMap3D" is a direct hint: "No Map" → the flag says "Who needs a map" in leetspeak. The map layout itself IS the flag.
The file uses the same chunk-based format as NoClip:
Chunk 1 (id=1): Player position
| Offset | Type | Value | Description |
|---|---|---|---|
| 0x00 | uint32 | 1 | Chunk ID |
| 0x04 | double | 103.0 | Player X position |
| 0x0C | double | 7.0 | Player Y position |
Chunk 2 (id=2): Map data
| Offset | Type | Value | Description |
|---|---|---|---|
| 0x14 | uint32 | 2 | Chunk ID |
| 0x18 | uint32 | 259 | Map width |
| 0x1C | uint32 | 12 | Map height |
| 0x20 | byte[] | 259×12×3 | Map data (9324 bytes) |
Each map cell is 3 bytes (column-major order: map[x * height + y]):
0=empty, 1=standard wall, 2=wall_circuit texture2=floor, 3=arrow upChunk 3 (id=3): Skybox texture
Chunk 4 (id=4): Textures — Only 3 generic textures:
| Index | Name | Size | Purpose |
|---|---|---|---|
| 0 | htb | 128×128 | HTB logo |
| 1 | wall_circuit | 1024×985 | Wall texture |
| 2 | floor_circuit | 1024×997 | Floor texture |
Unlike NoClip (which had 18 textures including individual character textures), NoMap3D has only 3 generic textures. The flag is encoded differently: the wall layout itself forms ASCII art text.
The map is 259 columns × 12 rows — a wide horizontal banner. The outer border (y=0, y=11, x=0, x=258) is all wall_circuit walls. The inner area (y=1 to y=10) contains the flag text formed by standard walls (wall_type=1).
All floor tiles have arrow-up texture (floor_tex=3), meaning the player looks upward at the walls — a hint to visualize the map from above.
Extracting the wall pattern for y=2 to y=8 (the 7-row letter area) reveals clear ASCII art characters:
##### ##### ############# ########## ### ###### ###### #### #### #### #### #### ########## ####### # ### ##### # ###
### ### # ### # ### ### ## ### ### # ### ## ### ######## ######## ### ### ### ### ### ## ### ### ##
### ### ### ### ### # ### #### # ### ## ## ## ## ### ## ## ## ### ### ##### ##### #### # ### # ### ## ##### #
########### ### ########## ## ### # ### # ######### ## ## ## ######### #### #### ### ### ####### # ### # ### # ### # ### ### ### ##
### ### ### ### ### # ### # ### # ### ### ## ## ## ### ### ### ### ### ### ##### ######### # ### ### ########## ### ### #
### ### ### ### ### ## ### ### ### ### ### ## ### ### # ### # ### ### ### ## ### # ### # # ### ### ### ### ##
##### ##### ##### ########## ### # # ##### ##### ###### ########### ##### ##### ###### ###### ########## ######### ########### #### ###### ########### ### ##### ##### ######## ###
Segmenting by all-space columns yields 20 character segments. Reading left to right:
| Seg | Character | Notes |
|---|---|---|
| 0 | H | Two vertical bars, horizontal middle |
| 1 | T | Full top bar, vertical center |
| 2 | B | Left bar with right bumps |
| 3 | { | Opening brace |
| 4 | W | Two V-shapes |
| 5 | h | Lowercase: left bar full height, right starts mid |
| 6 | 0 | Oval with diagonal |
| 7 | _ | Only bottom row |
| 8 | n | Lowercase: only bottom 5 rows, arch shape |
| 9 | 3 | Right-facing curves |
| 10 | 3 | Same as seg 9 |
| 11 | D | Left bar, right curve |
| 12 | S | S-curves |
| 13 | _ | Only bottom row |
| 14 | A | Triangle/pyramid |
| 15 | _ | Only bottom row |
| 16 | M | Left bar with right diagonals |
| 17 | 4 | Vertical with crossbar |
| 18 | p | Lowercase with descender to y=9,10 |
| 19 | } | Closing brace |
#!/usr/bin/env python3 """ HTB Challenge: NoMap3D (GamePwn) Solution: Extract flag from SDL2 raycasting game assets The game uses the same engine as NoClip, but the flag is encoded differently. Instead of character textures on special walls, the flag is ASCII art formed by the wall layout itself. The map is 259x12 - a wide horizontal banner. The "NoMap" name hints that the map IS the flag: "Who needs a map" in leetspeak. """ import struct def solve(): with open("assets.dmp", "rb") as f: data = f.read() offset = 0 # Chunk 1: Player position chunk_id = struct.unpack_from("<I", data, offset)[0] assert chunk_id == 1 offset += 4 player_x, player_y = struct.unpack_from("<dd", data, offset) offset += 16 print(f"Player start: ({player_x}, {player_y})") # Chunk 2: Map data chunk_id = struct.unpack_from("<I", data, offset)[0] assert chunk_id == 2 offset += 4 map_width, map_height = struct.unpack_from("<II", data, offset) offset += 8 print(f"Map size: {map_width}x{map_height}") # Map is stored column-major: map[x * height + y] with 3 bytes per cell # Byte 0: wall type (0=empty, 1=standard wall, 2=wall_circuit) # Byte 1: side texture index (unused) # Byte 2: floor texture (2=floor, 3=arrow up) map_data = data[offset:offset + map_width * map_height * 3] # Extract wall pattern as ASCII art (y=2 to y=8 is the main letter area) print("\n=== ASCII Art Wall Pattern ===\n") for y in range(2, 9): row = "" for x in range(1, map_width - 1): idx = (x * map_height + y) * 3 wall_type = map_data[idx] row += "#" if wall_type >= 1 else " " print(row) # The pattern clearly shows: HTB{REDACTED} flag = "HTB{REDACTED}" print(f"\n{'=' * 50}") print(f"FLAG: {flag}") print(f"{'=' * 50}") return flag if __name__ == "__main__": solve()
.rodata$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar