Are you the Far Lands because you're a Maze? Or are you a Maze because you're the Far Lands?
We're given a terminal prompt that looks like this:
It looks like we're blindly navigating some sort of "maze", with walls blocking regular movement between certain cells.
As a first order of business, we should probably map out what this maze looks like. We can represent each maze position as a 2x2 grid, where the right and bottom two cells represent whether there is a wall to the right or bottom of the cell, respectively:
.. .X .. .X
.. .X XX XX
(we don't need to check for left or top walls as we'll have already found them when iterating over other positions.)
Then, writing a python script to map out the maze's walls, printing one row of the maze at a time,1
import pwn
import numpy as np
conn = pwn.remote('13.201.224.182', 32545)
SIZE = 36 * 2
def print_buf(grid: np.ndarray):
for row in grid:
print(''.join('█' if e else ' ' for e in row))
def goto_top():
while True:
conn.sendlineafter(b'> ', b'w')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'W')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
break
def goto_left():
while True:
conn.sendlineafter(b'> ', b'a')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'A')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
return
def check_floor(x: int, grid: np.ndarray):
conn.sendlineafter(b'> ', b's')
if conn.recvline().decode().strip() != 'Moved!':
grid[1, x] = 1
grid[1, x + 1] = 1
else:
conn.sendlineafter(b'> ', b'w')
def map_line_right():
grid = np.zeros((2, SIZE), dtype=np.uint8)
x = 0
while True:
check_floor(x, grid)
conn.sendlineafter(b'> ', b'd')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'D')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
conn.sendlineafter(b'> ', b's')
print_buf(grid)
return
else:
grid[0, x + 1] = 1
grid[1, x + 1] = 1
x += 2
def map_line_left():
grid = np.zeros((2, SIZE), dtype=np.uint8)
x = SIZE
while True:
check_floor(x, grid)
conn.sendlineafter(b'> ', b'a')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'A')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
conn.sendlineafter(b'> ', b's')
print_buf(grid)
return
else:
grid[0, x - 1] = 1
grid[1, x - 1] = 1
x -= 2
goto_top()
goto_left()
# Skip top padding
for i in range(3):
conn.sendlineafter(b'> ', b's')
for i in range(0, SIZE, 2):
map_line_right()
map_line_left()
we get
██████████████ ████ ██ ████ ████████ ██████████████
█ █ █ █ █ █ █ █ █ █ █ █
█ ██████████ █ █ ██ █ █████ █ █ ██████ █ ██████████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ██████ █ █ █ █ ██ ██████████ █ █ █ ██████ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ █ ███████ ██ ███ ██ █ █ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ █ █████ ██ ███ ████ ███ █ █ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ █████ ███ █ ███ █ █ ███████ █ █
█ █ █ █ █ █ █ █ █ █
█ ███████████ █ ██ █ ██ ████ ██ ██ ██ █ ███████████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ █ ███ █████████ █ █ █ █████ ███████████████
█ █ █ █ █ █ █ █
██ ██ ████ █ ██ █ █ ██ █ █ ████████ ██████████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ███ █ █████ █ █████ █ █ ██ █ ███ ███ ██ ██
█ █ █ █ █ █ █ █ █ █
█████ ████████ █ ████ ██ █ █ ███ ███████ ██ ██████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ █████████ ███ ██ █████ █████ ████████ ███ █████ ███
█ █ █ █ █ █ █ █
█ ██████ ██████ █ ██████ ████ █ ██████ ████ █ ████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ █ █████ ██ ███ █ ███ ██ ███ █ ███ ██ ███
█ █ █ █ █ █ █ █
██ █ ███████ ██ █ ████ ██ █ ████ ██
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██ ███ █ ████ ███ ███ █ █████ ██ █ ███ █████ ██████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ██████ ███████ █ ██ ███ █████ ███ █ ███ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ ██ █████████ ██████ ██ █ ███████ █ ████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ █ █ ██ ███ ██████ ████ █ ███ █ █ █ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
██████ ███ ██ █ █ █ ███ ███ ████ ██ ██ ███ ██
█ █ █ █ █ █ █ █ █ █ █ █ █ █
██████████████ ███ ███ █ ██ ████ ███ ███████ █████ ████
█ █ █ █ █ █ █ █ █ █ █ █
█████ ███████ ██ ████ █ █ █ ██ ██ ██████ ███ █
█ █ █ █ █ █ █ █ █ █ █ █
██████████████ █████████ █████ █ █ █████ █ ██ █ ██ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ ██████████ █ ██████ █ ████████ ██ █ █ ███ █ ███ ██
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ██████ █ █ ███ █ █ █ █ █ ████ █ █ ███████ ████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ ██████ █ █████ ███ █ ████ ██ ██ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ ███ █ ████ █ ██ ██ ███████████ ███ ████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ ██ █████ █ █ █ █ ███ ██ █████ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ ███████████ █ █████ █████████ █ █ █ ██ █ ███ ████
█ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ ███ ███ █████ ███ ███ ███
Looks a bit like a QR code, doesn't it? 🤔
First, though, we need to fix up all of the top-left corners which got messed up by the traversal algorithm. Doing so yields
███████████████ █████ ███ █████ █████████ ███████████████
█ █ █ █ █ █ █ █ █ █ █ █
█ ███████████ █ █ ███ █ █████ █ █ ███████ █ ███████████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ █ █ ███ ███████████ █ █ █ ███████ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ █ ███████ ███ ███ ███ █ █ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ █ █████ ███ ███ █████ ███ █ █ █ █ █ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ █████ ███ █ ███ █ █ ███████ █ █
█ █ █ █ █ █ █ █ █ █
█ ███████████ █ ███ █ ███ █████ ███ ███ ███ █ ███████████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ █ ███ █████████ █ █ █ █████ ███████████████
█ █ █ █ █ █ █ █
███ ███ █████ █ ███ █ █ ███ █ █ █████████ ███████████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ ███ █ █████ █ █████ █ █ ███ █ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █
█████ █████████ █ █████ ███ █ █ ███ ███████ ███ ███████ █
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ █████████ ███ ███ █████ █████ █████████ ███ █████ ███
█ █ █ █ █ █ █ █
█ ███████ ███████ █ ███████ █████ █ ███████ █████ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ █ █████ ███ ███ █ ███ ███ ███ █ ███ ███ ███
█ █ █ █ █ █ █ █
███ █ ███████ ███ █ █████ ███ █ █████ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ ███ █ █████ ███ ███ █ █████ ███ █ ███ █████ ███████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ ███████ █ ███ ███ █████ ███ █ ███ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ ███ █████████ ███████ ███ █ ███████ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███ █ █ ███ ███ ███████ █████ █ ███ █ █ █ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
███████ ███ ███ █ █ █ ███ ███ █████ ███ ███ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ ███ ███ █ ███ █████ ███ ███████ █████ █████
█ █ █ █ █ █ █ █ █ █ █ █
█████ ███████ ███ █████ █ █ █ ███ ███ ███████ ███ █
█ █ █ █ █ █ █ █ █ █ █ █
███████████████ █████████ █████ █ █ █████ █ ███ █ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ ███████████ █ ███████ █ █████████ ███ █ █ ███ █ ███ ███
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ ███ █ █ █ █ █ █████ █ █ ███████ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ ███████ █ █████ ███ █ █████ ███ ███ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ █ █ █ █ ███ █ █████ █ ███ ███ ███████████ ███ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ █ ███████ █ █ ███ █████ █ █ █ █ ███ ███ █████ █ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █ █ █
█ ███████████ █ █████ █████████ █ █ █ ███ █ ███ █████
█ █ █ █ █ █ █ █ █ █ █ █ █ █
███████████████ ███ ███ █████ ███ ███ ███
Resizing the image in Photoshop to make the corner boxes more square-like, we get an interesting pattern that looks like this:
The key realization here is that the corners of a valid QR code should consist of a single ring around solid block of black:
Because the walls lie between cells, it's clear now that the walls of the maze separate regions of different color in the QR code. Then, this challenge turns into a sort of "A/B coloring" problem solvable with the following routine:
- Keep a state tracking the current "color": white/black (or filled/unfilled)
- Iterate over all cells, painting each cell the current color.
- When the program hits a wall in the maze while iterating, swap the current color.
Modifying our traversal script with the given algorithm,
import pwn
conn = pwn.remote('13.201.224.182', 32323)
fill = False
def goto_top():
while True:
conn.sendlineafter(b'> ', b'w')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'W')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
break
def goto_left():
while True:
conn.sendlineafter(b'> ', b'a')
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'A')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
return
def map_line_right() -> str:
global fill
ret = ''
while True:
conn.sendlineafter(b'> ', b'd')
ret += '█' if fill else ' '
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'D')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
conn.sendlineafter(b'> ', b's')
return ret
else:
fill = not fill
def map_line_left() -> str:
global fill
ret = ''
while True:
conn.sendlineafter(b'> ', b'a')
ret += '█' if fill else ' '
if conn.recvline().decode().strip() != 'Moved!':
conn.sendlineafter(b'> ', b'A')
if conn.recvline().decode().strip() != 'Jumped over a wall!':
conn.sendlineafter(b'> ', b's')
return ret[::-1]
else:
fill = not fill
goto_top()
goto_left()
# Skip top padding
for i in range(4):
conn.sendlineafter(b'> ', b's')
while True:
print(map_line_right())
print(map_line_left())
we get
███████ ██ █ ██ ████ ███████
█ █ █ █████ █ █ █
█ ███ █ █ ██ ██ █ ███ █
█ ███ █ ██ ████ █ █ ███ █
█ ███ █ ████ █ █ █ ███ █
█ █ ████ █ █
███████ █ █ █ █ █ █ █ ███████
████ █ █ ██
█ █ ██ ███ █ ██ █ ███ █████
████ ██████ █ ███████ ██
█ ███ █ █ █ ██ █ █ █ █
███ ███ ███ ███
█ █ ███ █ █ ██ █ █ ██ █
██ █ ███ ███
█ ███ █ █ ██ █ █ ██ █ █
█ ██ ███ ███ ██ ██ █ █
█ ██ ██ █ █ ███████ ███ ██
█ █ ███ ███ █ █ █ █
█ █ █ █ █ █ ███ █
██ ██ █ █ █ █ █
██ ███ ███ █ ███████████ █
██ █ █ █ ██ ████
███████ ██ ██ ██ █ █ █ ███
█ █ ██ █ █ ██ █ ████
█ ███ █ █ █ █ █ █ ███████ █
█ ███ █ █ ██ ████ █ █ ██ ██
█ ███ █ █ █ █ █ █ █ ███ █
█ █ █ ██ █ ██ ███ ████
███████ █ █ ██ █ █ █
which we can scan to get the flag.
bi0sctf{cZrfONl+bGtGiKWMnnR5Sg==}
Footnotes
-
A bit sloppy, but we're somewhat forced to print the buffer before reading the next line instead of printing it all at the end because traversing the maze takes so long the connection times out (crashing the program) before the entire process is done. ↩