Shell Maze is a misc challenge that was part of the 2022 TUCTF. We had to solve a maze by sending the right commands to the server. After solving a maze, we got the next one that was a bit harder than the previous one.

Solution

First maze:

XOOOOOOOOOOOOO######
#############O######
#############O######
###OOOOOOOOOOO######
#OOO################
#OOOOOOOOOOOOOOOOO##
#################O##
##########OOOOOOOO##
##########OO########
###########O########
#OOOOOOOOOOOOOOOOOOO
Command Description
> Move right
< Move left
V Move down

Game logic

The game logic is pretty simple. We have to move the X to the bottom right corner. The X can move only right, left and down. If the X moves to the right or left, it can move only if there is an O in the next column. If the X moves down, it can move only if there is an O in the next row.

if x_row == len(maze) - 1: # if we are in the last row -> move right
    io.sendline(b'>')
else:
    if maze[x_row + 1][x_col] == "O":
        io.sendline(b"V")
    else:
        o_pos = find_o_in_row(x_row + 1)
        if o_pos < x_col:
            io.sendline(b"<")
        else:
            io.sendline(b">")

Final code

#!/usr/bin/env python
from pwn import remote

io = remote('chals.tuctf.com', 30204)
io.recvuntil(b'down.\n')

x_row, x_col = 0, 0
def get_pos_of_X():
    global x_row, x_col
    for row in range(len(maze)):
        for col in range(len(maze[row])):
            if maze[row][col] == 'X':
                x_row, x_col = row, col
                return

def find_o_in_row(row):
    for col in range(len(maze[row])):
        if maze[row][col] == 'O':
            return col
    return -1

run = 1
while True:
    try:
        maze = io.recvuntil(b'Move: ').decode().split("\nMove: ")[-2]
    except:
        print(io.recvall().decode())
        break

    print(maze)
    print(f"maze {run}")

    if "Loading next level..." in maze:
        run += 1
        for line in maze.splitlines():
            if line and line[0] == 'X':
                start_line = maze.splitlines().index(line)
                maze = [list(line) for line in maze \
                    .splitlines()[start_line:]]
                break
    else:
        maze = [list(line) for line in maze.splitlines()]

    get_pos_of_X()

    if x_row == len(maze) - 1:
        io.sendline(b'>')
    else:
        if maze[x_row + 1][x_col] == "O":
            io.sendline(b"V")
        else:
            o_pos = find_o_in_row(x_row + 1)
            if o_pos < x_col:
                io.sendline(b"<")
            else:
                io.sendline(b">")

Output:

$ ./solve-maze.py
[+] Opening connection to chals.tuctf.com on port 30204: Done
XOOOOOOOOOOOOO######
#############O######
#############O######
###OOOOOOOOOOO######
#OOO################
#OOOOOOOOOOOOOOOOO##
#################O##
##########OOOOOOOO##
##########OO########
###########O########
#OOOOOOOOOOOOOOOOOOO
maze 1
OXOOOOOOOOOOOO######
#############O######
#############O######
###OOOOOOOOOOO######
#OOO################
#OOOOOOOOOOOOOOOOO##
#################O##
##########OOOOOOOO##
##########OO########
###########O########
#OOOOOOOOOOOOOOOOOOO
maze 1
...
O###################
O###################
O###################
...
############O#######
############O#######
##OOOOOOOOOOOOOOOOXO
maze 50
[+] Receiving all data: Done (1.33KB)
[*] Closed connection to chals.tuctf.com port 30204

O###################
O###################
O###################
...
############O#######
############O#######
##OOOOOOOOOOOOOOOOOX


Loading next level...

Congrats! Here's your flag: TUCTF{1_4m_4_7ru3_n37_7r4v3l3r_357269}

Flag

The flag is “TUCTF{1_4m_4_7ru3_n37_7r4v3l3r_357269}”.