This challenge involved solving an ASCII maze in under 10 seconds. I can’t include a demo because I’m writing this after the CTF ended.

My script uses the astar package, because I was too lazy to re-implement A* manually. It isn’t particularly pretty, but at least it is functionnal.

Here it is:

``````import math
from pwn import *
from astar import AStar

def parse_maze(source):
source = source.strip()
source = source.split('\n')
source = source[:-1]  # Remove last line (dots)
source = [list(l[1:-1]) for l in source]  # Remove left and right dots
return source

# Mostly copied from the astar package example.
class MazeSolver(AStar):

def __init__(self, maze):
self.maze = maze
self.height = len(maze)
self.width = len(maze)

def heuristic_cost_estimate(self, n1, n2):
(x1, y1) = n1
(x2, y2) = n2
return math.hypot(x2 - x1, y2 - y1)

def distance_between(self, n1, n2):
return 1

def neighbors(self, node):
x, y = node
return [
(nx, ny)
for nx, ny
in [
(x, y - 1),
(x, y + 1),
(x - 1, y),
(x + 1, y)
]
if 0 <= nx < self.width and 0 <= ny < self.height and self.maze[ny][nx] == ' '
]

def sol_to_dir(i):
last = None
steps = []

for element in i:
if last is None:
last = element
continue

old_x, old_y = last
x, y = element

if old_x < x:
steps.append('O')
elif old_x > x:
steps.append('E')
elif old_y < y:
steps.append('S')
elif old_y > y:
steps.append('N')
else:
raise RuntimeError('should not happen')

last = element

return steps

r = remote('XXX.XXX.XXX.XXX', 00000)

maze = parse_maze(r.recvuntil(b'.' * 92).decode())

width = len(maze)
height = len(maze)

# We want the path from the bottom right corner, to the top left one.
start = (width - 1, height - 1)
goal = (0, 0)

solution = list(MazeSolver(maze).astar(start, goal))
solution = ''.join(sol_to_dir(solution))

r.send(f'{solution}\n')

print('---------------')
print(r.recvall().decode())

r.close()
``````

The flag was `CYBERTF{M@ze_Mast3r}`.