TunnelMadness
TunnelMadness
Platform: HackTheBox | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-01-29 | Status: Solved Techniques: binary_maze_extraction, dfs_backtracking, dynamic_maze_solving, server_interaction
Summary
Task: Navigate a 3D maze in an ELF binary with remote server interaction. Solution: Used DFS with backtracking to dynamically explore the unknown server-side maze, as the binary contained different test data.
Recon
Port scan
bash read-only • copy allowednmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
hackthebox| ID:20260129_hackthebox_tunnelmadness - Tags: maze, elf, 3d_maze, dynamic_analysis, dfs, backtracking
- Indicators: 3D maze navigation, L/R/F/B/U/D directions, embedded test data differs from server, remote server interaction required
- Source:
20260129_hackthebox_tunnelmadness.md
Foothold
Vulnerability / Misconfiguration
- binary_maze_extraction
- dfs_backtracking
- dynamic_maze_solving
- server_interaction
bash read-only • copy allowed<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
bash read-only • copy allowedsudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
bash read-only • copy allowed<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- binary_maze_extraction
- dfs_backtracking
- dynamic_maze_solving
- server_interaction
- Tags: maze, elf, 3d_maze, dynamic_analysis, dfs, backtracking
Original Writeup
<details><summary>Click to expand original content</summary>Description
"Within Vault 8707 are located master keys used to access any vault in the country. Unfortunately, the entrance was caved in long ago. There are decades old rumors that the few survivors managed to tunnel out deep underground and make their way to safety. Can you uncover their tunnel and break back into the vault?"
The challenge provided:
- A downloadable binary file
- A remote server:
nc 83.136.248.107 38062
Analysis
Initial Reconnaissance
Downloaded and extracted the challenge files. Found an ELF 64-bit binary called tunnel.
bash read-only • copy allowed$ file tunnel tunnel: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked...
String Analysis
Found interesting strings that reveal the challenge mechanics:
bash read-only • copy allowed$ strings tunnel | grep -E "(Direction|Cannot|flag|vault)" Direction (L/R/F/B/U/D/Q)? Cannot move that way /flag.txt HTB{fake_flag_for_testing} You break into the vault and read the secrets within...
Key findings:
- Navigation prompt: "Direction (L/R/F/B/U/D/Q)?" — 6 directions for 3D movement plus Quit
- Wall collision: "Cannot move that way" — indicates invalid moves
- Flag path: "/flag.txt" — server reads flag from file
- Test flag: "HTB{fake_flag_for_testing}" — embedded fake flag for local testing
- Success message: "You break into the vault..." — indicates reaching the goal
Function Analysis
Using objdump to identify key functions:
bash read-only • copy allowed$ objdump -t tunnel | grep -E "(main|get_cell|prompt|flag)"
Key functions identified:
main— Main game loopget_cell— Calculate cell position in 3D mazeprompt_and_update_pos— Handle movement inputget_flag— Read and print flag on success
Understanding the Maze Structure
From disassembly of get_cell:
c read-only • copy allowed// Pseudo-code reconstruction struct Cell { int x, y, z; // Coordinates int type; // 0=start, 1=path, 2=wall, 3=goal }; // Maze is 20x20x20 = 8000 cells // Each cell is 16 bytes (4 ints) Cell* get_cell(int x, int y, int z) { return &maze_data[z * 400 + y * 20 + x]; }
Maze parameters:
- Dimensions: 20x20x20 (indices 0-19)
- Cell size: 16 bytes (x, y, z, type)
- Cell types: 0=start, 1=path, 2=wall, 3=goal
- Start position: (0, 0, 0)
- Goal position: (19, 19, 19)
Direction mapping:
| Direction | Delta (x, y, z) |
|---|---|
| B (Back) | (-1, 0, 0) |
| F (Forward) | (+1, 0, 0) |
| L (Left) | (0, -1, 0) |
| R (Right) | (0, +1, 0) |
| D (Down) | (0, 0, -1) |
| U (Up) | (0, 0, +1) |
Main Loop Logic
The main loop:
- Prompts for direction input
- Calculates new position based on direction
- Checks if new cell is valid (not wall, within bounds)
- If cell type == 3 (goal), calls
get_flag()and exits - Otherwise, updates position and continues
Solution
First Attempt: Static Maze Extraction (Failed)
Initially tried to extract the maze from the binary and solve it offline:
python read-only • copy allowed#!/usr/bin/env python3 """ Attempt 1: Extract maze from binary and solve with BFS """ from collections import deque # Maze data found at offset 0x20e0 in binary # Extracted all cells and built adjacency graph # BFS found path from (0,0,0) to (19,19,19) path = "UUUFRUFUFFRFFRRUURUFFURURRFRURUUUURRFFUUURUFRDRRURRFFFFFRFF"
Problem: When sending this path to the server, got "Cannot move that way" errors. The server has a DIFFERENT maze than the binary!
The binary contains a fake/test maze for local development. The actual challenge maze is generated server-side.
Second Attempt: Dynamic Maze Solving (Success)
Since the maze is unknown, implemented DFS with backtracking to explore dynamically:
python read-only • copy allowed#!/usr/bin/env python3 """ TunnelMadness - Dynamic 3D Maze Solver Uses DFS with backtracking to explore unknown maze """ from pwn import * context.log_level = 'error' # Direction definitions directions = ['F', 'R', 'U', 'B', 'L', 'D'] reverse_dir = {'B': 'F', 'F': 'B', 'L': 'R', 'R': 'L', 'D': 'U', 'U': 'D'} dir_delta = { 'B': (-1, 0, 0), 'F': (1, 0, 0), 'L': (0, -1, 0), 'R': (0, 1, 0), 'D': (0, 0, -1), 'U': (0, 0, 1) } DIM = 20 # Maze dimension def solve_maze(): r = remote('83.136.248.107', 38062) visited = set() walls = set() current_pos = (0, 0, 0) visited.add(current_pos) path = [] # Stack of moves for backtracking def try_move(direction): """Attempt to move in given direction, return result""" r.recvuntil(b'?') r.sendline(direction.encode()) line = r.recvline() # Check for success (reached goal) if b'break into' in line.lower() or b'secrets' in line.lower(): print(f"[+] SUCCESS!") print(f"FLAG: {line.decode()}") rest = r.recvall(timeout=5) print(f"{rest.decode()}") return 'flag' # Check for wall collision if b'Cannot move' in line: return 'wall' return 'ok' move_count = 0 while True: moved = False # Try each direction (DFS exploration) for direction in directions: dx, dy, dz = dir_delta[direction] new_pos = (current_pos[0]+dx, current_pos[1]+dy, current_pos[2]+dz) # Skip if out of bounds if not all(0 <= c < DIM for c in new_pos): continue # Skip if already visited or known wall if new_pos in visited or new_pos in walls: continue # Try the move result = try_move(direction) move_count += 1 if result == 'flag': print(f"[+] Solved in {move_count} moves") print(f"[+] Path length: {len(path) + 1}") return if result == 'ok': # Move succeeded visited.add(new_pos) current_pos = new_pos path.append(direction) moved = True break else: # Hit a wall walls.add(new_pos) # If no valid move found, backtrack if not moved: if not path: print("[-] No solution found!") break # Backtrack: reverse last move last_move = path.pop() back_dir = reverse_dir[last_move] result = try_move(back_dir) move_count += 1 if result == 'flag': print(f"[+] Solved in {move_count} moves") return # Update position dx, dy, dz = dir_delta[back_dir] current_pos = (current_pos[0]+dx, current_pos[1]+dy, current_pos[2]+dz) if __name__ == '__main__': solve_maze()
Execution
bash read-only • copy allowed$ python3 solve.py [+] SUCCESS! FLAG: You break into the vault and read the secrets within... HTB{REDACTED} [+] Solved in 118 moves
The DFS algorithm explored the 3D maze, backtracking when hitting dead ends, until reaching the goal cell at (19, 19, 19).
Notes
Why Static Analysis Failed
Many CTF challenges with a remote component contain test data in the binary for local debugging. The actual data is generated or stored on the server. Always verify whether local and server data match!
DFS vs BFS Algorithm
- BFS (Breadth-First Search) — finds the shortest path but requires knowledge of the entire graph in advance
- DFS (Depth-First Search) with backtracking — works with an unknown graph, explores as it progresses
For dynamic maze exploration, DFS is ideal because:
- Does not require knowledge of the entire structure
- Backtracking allows returning from dead ends
- Memory O(depth) instead of O(all nodes)
3D Maze Navigation
6 directions in 3D:
- Horizontal plane: Forward/Back (X), Left/Right (Y)
- Vertical axis: Up/Down (Z)
This is a classic structure for 3D mazes, commonly found in CTF and game challenges.
</details>signed by XESXOR