HTB: NoRadar Challenge
NoRadar - HackTheBox Challenge Writeup
Challenge Information
| Field | Value |
|---|---|
| Name | NoRadar |
| Category | Misc |
| Difficulty | Easy |
| Author | d3vn0mi |
Description
Track the elusive green cube through a labyrinth of obscure clues and cryptic messages to unravel its hidden location.
The challenge ships a small game binary (noradar) plus an asset dump (assets.dmp). The premise is a maze full of decoy “green cubes” — the flag is hidden in how the real cube moves, not in anything rendered on screen.
Solution
The staged challenge archive initially looked broken — the extracted noradar binary was 0 bytes. Re-extracting straight from the source zip with the correct password recovered the real 36 KB ELF and a 3.8 MB assets.dmp file.
From there the approach was pure static reverse engineering: no need to actually run the game or navigate the maze.
-
Reverse-engineered the asset format.
assets.dmpuses a simple TLV (tag-length-value) layout: tag1is the player’s starting position (two doubles), tag2is a 350×21 tile map (3 bytes/cell), tag3is a single texture, and tag4is a texture table (count + N × [64-byte name, u32 width, u32 height, RGBA pixels]). Extracting and rendering every texture (skybox,htb,floor,green_cube) confirmed none of them carried the flag — they’re just cosmetic, and the maze pattern itself is a repeating tile, not a message. -
Found which cube actually mattered. Disassembling
mainshowed the game spawns large swarms of decoy cubes:green_cube1_newat every cell where(x+y) % 15 == 0 && y % 8 == 0green_cube2_newat every cell where(x+y) % 5 == 0 && y % 3 == 0
That’s the “no radar” trick — dozens of green cubes scattered across the map to bury the real one. But
maincallsgreen_cube3_newexactly once, and only that instance is wired to a per-framegreen_cube3_updatepatrol routine. -
Extracted the patrol waypoints.
green_cube3_updatewalks a hardcoded array of 374 doubles (187 x/y pairs) stored in.rodataat offset0x5060. Dumping and pairing those doubles gave the full patrol path the cube walks across the map. -
Rendered the path as glyphs. A waypoint of exactly
(0, 0)acts as a pen-lift / letter-break marker: each time it appears, a staticoffset_xaccumulator increases by 14. Plotting every waypoint at(x + offset_x, y)onto the 350×21 map — i.e. treating the cube’s route as a polyline draw call — produces 23 distinct stroke glyphs laid out left to right. Read together, those glyphs spell the flag. The “maze” was never meant to be solved by walking it; the cube’s movement itself is the message. -
Verified the flag via HTB’s submission API against challenge ID 452, which returned
{'message': 'Congratulations!'}.
Key Steps
Recover the real artifacts (fix the silently-failed extraction):
# The staged binary was 0 bytes; the source zip had the real sizes.unzip -P hackthebox a12c736e-853d-4a25-a233-f1106ea2f90b.zip# -> noradar (36KB ELF), assets.dmp (3.8MB)Parse the TLV asset dump to rule out texture-based flags:
import struct
d = open('assets.dmp', 'rb').read()off = 0while off < len(d): tag = struct.unpack_from('<I', d, off)[0] # tag 1 = player pos (2 doubles) # tag 2 = 350x21 map, 3 bytes/cell # tag 3 = single texture # tag 4 = count + N textures: 64B name, u32 w, u32 h, RGBA pixels ... # extracted & rendered every texture -> no flag presentFind the one “real” cube among the decoy swarm:
# From objdump -d --no-show-raw-insn -M intel noradar:## main:# for each maze cell (x, y):# if (x+y) % 15 == 0 and y % 8 == 0: green_cube1_new(x, y) # decoy swarm# if (x+y) % 5 == 0 and y % 3 == 0: green_cube2_new(x, y) # decoy swarm# green_cube3_new(...) <-- called exactly once# register_update(green_cube3_update) <-- only this one patrolsExtract and rasterize the patrol path from .rodata:
import struct
d = open('noradar', 'rb').read()base = 0x5060n = 374 # 187 (x, y) waypoint pairsvals = struct.unpack_from(f'<{n}d', d, base)points = list(zip(vals[0::2], vals[1::2]))
offset_x = 0glyph_map = {} # (x, y) -> stroked pixel
for x, y in points: if (x, y) == (0.0, 0.0): offset_x += 14 # pen-lift: start next letter continue glyph_map[(int(x) + offset_x, int(y))] = True
# Rendering glyph_map onto the 350x21 grid draws 23 letterforms:# the flag, spelled out by the cube's own movement.Verify against the HTB API:
import syssys.path.insert(0, '/app/lib')from htb_api import HTBClient
c = HTBClient()print(c.submit_challenge(452, "HTB{REDACTED}"))# -> {'message': 'Congratulations!'}Tools Used
objdump(disassembly ofmain,green_cube3_new,green_cube3_update)nm(symbol enumeration)stringsunzip(password-protected source archive)Python 3(structfor binary parsing,PILfor texture rendering/verification)- HTB submission API (
htb_api.HTBClient)
Key Learnings
- Not every visual element in a challenge is the puzzle. The maze, the textures, and dozens of decoy cubes were all red herrings by design — “NoRadar” refers to burying the signal in noise, not to any in-game mechanic.
- Static analysis beats dynamic play when the “gameplay” is scripted. The cube’s path was a hardcoded waypoint table, not something reactive to player input, so there was no need to run the binary or solve the maze at all — just find and decode the table.
- Watch for sentinel values in coordinate data. The
(0, 0)waypoint wasn’t a real position — recognizing it as a pen-lift/letter-break marker was the key that turned a jumble of points into readable glyphs. - TLV/asset dump formats are worth reverse-engineering fully, even when a lead turns out to be a dead end (the textures here), because ruling out the “obvious” location narrows down where the real answer must be.
- Verify infrastructure failures before assuming a challenge is broken. A 0-byte staged binary looked like a broken challenge but was just a failed extraction step — re-extracting from source with the known password resolved it immediately.