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
nmap -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
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -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.
<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.
$ 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:
$ 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:
$ 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:
// 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:
#!/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:
#!/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
$ 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>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR