Лабиринт (Labyrinth)
Лабиринт (Labyrinth)
Platform: duckerz | Category: Misc | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-01-09 | Status: Solved Techniques: bfs_pathfinding, index_mapping_discovery, protocol_analysis
Summary
Task: Navigate through 3 mazes (100x100, 200x200, 300x300) via netcat. Solution: BFS pathfinding with discovered wall index mapping bug (documented [N,E,S,W] vs actual [S,E,N,W]).
Лабиринт (Labyrinth) — duckerz CTF
Description
"Я гулял по парку, и задумавшись, не заметил как заблудился, помоги отыскать дорогу"
(Translation: "I was walking in the park, and lost in thought, I didn't notice how I got lost, help me find the way")
Connection: nc tasks.duckerz.ru 30003
Analysis
Service Interface
Upon connection, the service provides an interactive menu:
- Show labyrinth — visual representation of the maze
- Output wall matrix —
[[[N, E, S, W], ...], ...](documented format: up, right, down, left) - Make a move — execute a move (N/E/S/W)
- Show completed count — number of completed mazes
- Show player position — player position
[X, Y] - Exit — exit
Visual Elements
#(red) — exit point at position (0, 0)@(green) — current player position
Goal
Complete 3 mazes of increasing complexity:
- Maze 1: 100×100
- Maze 2: 200×200
- Maze 3: 300×300
Key Discovery — Wall Index Bug
Server documentation claims the wall format is: [N, E, S, W]
However, testing revealed the actual format is: [S, E, N, W]!
| Index | Documented | Actual |
|---|---|---|
| 0 | North | South |
| 1 | East | East |
| 2 | South | North |
| 3 | West | West |
| |
Wall values: 0 = passage open, 1 = wall
This was discovered empirically — by testing each direction and comparing with the visual representation of the maze.
Solution
Algorithm
- Get the wall matrix from server (option 2)
- Get current player position (option 5)
- Use BFS to find path from current position to (0, 0)
- Execute the path by sending movement commands (option 3)
- Repeat for all 3 mazes
Solution Code
python read-only • copy allowed#!/usr/bin/env python3 """ Duckerz CTF - Labyrinth Solver Solves maze challenges using BFS pathfinding Key insight: Server documentation says [N, E, S, W] but actual format is [S, E, N, W] """ import socket import json from collections import deque import re def connect(): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(30) s.connect(('tasks.duckerz.ru', 30003)) return s def recv(s): """Receive data until prompt""" data = b'' s.settimeout(5) try: while b'>> ' not in data: chunk = s.recv(4096) if not chunk: break data += chunk except: pass return data.decode('utf-8', errors='ignore') def get_matrix(s): """Get wall matrix from server""" s.send(b'2\n') data = recv(s) for line in data.split('\n'): if line.startswith('[[['): return json.loads(line) return None def get_position(s): """Get current player position""" s.send(b'5\n') data = recv(s) for line in data.split('\n'): if line.startswith('[') and ',' in line and not line.startswith('[[['): return json.loads(line) return None def bfs_solve(matrix, start_x, start_y): """ BFS pathfinding from (start_x, start_y) to (0, 0) CRITICAL: Wall indices are NOT as documented! Documentation says: [N, E, S, W] Actual format: [S, E, N, W] Index mapping: 0 = South (down, dy=+1) 1 = East (right, dx=+1) 2 = North (up, dy=-1) 3 = West (left, dx=-1) """ h, w = len(matrix), len(matrix[0]) # Direction: (name, wall_index, dx, dy) directions = [ ('N', 2, 0, -1), # North: index 2, move up ('E', 1, 1, 0), # East: index 1, move right ('S', 0, 0, 1), # South: index 0, move down ('W', 3, -1, 0), # West: index 3, move left ] queue = deque([(start_x, start_y, [])]) visited = {(start_x, start_y)} while queue: cx, cy, path = queue.popleft() # Reached exit if cx == 0 and cy == 0: return path cell = matrix[cy][cx] for direction, wall_idx, dx, dy in directions: # Check if passage is open (0 = open, 1 = wall) if cell[wall_idx] == 0: nx, ny = cx + dx, cy + dy if 0 <= nx < w and 0 <= ny < h and (nx, ny) not in visited: visited.add((nx, ny)) queue.append((nx, ny, path + [direction])) return None # No path found def execute_path(s, path): """Execute movement path and check for flag""" for move in path: s.send(b'3\n') recv(s) s.send(f'{move}\n'.encode()) response = recv(s) if 'DUCKERZ' in response: match = re.search(r'DUCKERZ\{[^}]+\}', response) if match: return match.group() return None def solve_maze(s): """Solve single maze""" matrix = get_matrix(s) if not matrix: return None pos = get_position(s) if not pos: return None x, y = pos h, w = len(matrix), len(matrix[0]) print(f" Size: {w}x{h}, Start: ({x}, {y})") path = bfs_solve(matrix, x, y) if not path: print(" ERROR: No path found!") return None print(f" Path length: {len(path)} steps") return execute_path(s, path) def main(): s = connect() recv(s) # Initial prompt for i in range(5): # Max attempts # Check progress s.send(b'4\n') data = recv(s) match = re.search(r'(\d)/(\d)', data) if match: current, total = match.groups() print(f"\nMaze {int(current)+1}: Progress {current}/{total}") if current == total: # All mazes completed, get flag s.send(b'1\n') response = recv(s) flag_match = re.search(r'DUCKERZ\{[^}]+\}', response) if flag_match: print(f"\nFLAG: {flag_match.group()}") break flag = solve_maze(s) if flag: print(f"\nFLAG: {flag}") break s.close() if __name__ == '__main__': main()
Execution Results
Maze 1: Progress 0/3
Size: 100x100, Start: (14, 68)
Path length: 226 steps
Maze 2: Progress 1/3
Size: 200x200, Start: (28, 157)
Path length: 499 steps
Maze 3: Progress 2/3
Size: 300x300, Start: (60, 276)
Path length: 814 steps
FLAG: DUCKERZ{REDACTED}
Lessons
- Always verify documentation — the documented format
[N, E, S, W]was incorrect; actual format is[S, E, N, W] - Test assumptions empirically — visual comparison with the matrix helped find the bug
- BFS is ideal for mazes — guarantees shortest path in unweighted graphs
- Automation is mandatory — sizes 100×100, 200×200, 300×300 are impossible to solve manually