NoMap3D
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
$ ls tags/ techniques/
NoMap3D — HackTheBox
Description
"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{...}
Analysis
Step 1: Initial Recon
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.
Step 2: Parsing assets.dmp
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]):
- Byte 0: Wall type —
0=empty,1=standard wall,2=wall_circuit texture - Byte 1: Side texture index (unused)
- Byte 2: Floor texture —
2=floor,3=arrow up
Chunk 3 (id=3): Skybox texture
- Name: "skybox", 1024×906 RGBA
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 |
Step 3: Key Insight — Map IS the Flag
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.
Step 4: Extracting the ASCII Art
Extracting the wall pattern for y=2 to y=8 (the 7-row letter area) reveals clear ASCII art characters:
##### ##### ############# ########## ### ###### ###### #### #### #### #### #### ########## ####### # ### ##### # ###
### ### # ### # ### ### ## ### ### # ### ## ### ######## ######## ### ### ### ### ### ## ### ### ##
### ### ### ### ### # ### #### # ### ## ## ## ## ### ## ## ## ### ### ##### ##### #### # ### # ### ## ##### #
########### ### ########## ## ### # ### # ######### ## ## ## ######### #### #### ### ### ####### # ### # ### # ### # ### ### ### ##
### ### ### ### ### # ### # ### # ### ### ## ## ## ### ### ### ### ### ### ##### ######### # ### ### ########## ### ### #
### ### ### ### ### ## ### ### ### ### ### ## ### ### # ### # ### ### ### ## ### # ### # # ### ### ### ### ##
##### ##### ##### ########## ### # # ##### ##### ###### ########### ##### ##### ###### ###### ########## ######### ########### #### ###### ########### ### ##### ##### ######## ###
Step 5: Reading the 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 |
Solution
#!/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{Wh0_n33DS_A_M4p} flag = "HTB{Wh0_n33DS_A_M4p}" print(f"\n{'=' * 50}") print(f"FLAG: {flag}") print(f"{'=' * 50}") return flag if __name__ == "__main__": solve()
Related Challenges
- NoClip (HackTheBox, GamePwn, medium) — Same SDL2 raycasting engine, but flag encoded in individual character textures on special walls along a spiral maze path
- NoRadar (HackTheBox, GamePwn) — Same raycaster engine, flag encoded in entity waypoint trajectory in
.rodata - CubeMadness1 (HackTheBox, GamePwn, easy) — Unity game with hidden flag in textures
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar
Similar writeups
- [gamepwn][free]NoClip— HackTheBox
- [gamepwn][free]NoRadar— HackTheBox
- [reverse][free]Challenge Scenario (rev_gameloader)— HackTheBox
- [gamepwn][free]StayInTheBoxCorp— HackTheBox
- [reverse][free]TunnelMadness— hackthebox