🟦Bi0sCTF 2024

Writeup for A Block and a Hard Place

Only one challenge writeup this time, really tough multi-part questions and only had time to solve this one all the way through.

A Block and a Hard Place (28 Solves)

Description:

Are you the Far Lands because you're a Maze? Or are you a Maze because you're the Far Lands?

Solution:

We're given only a server to netcat to and nothing else. Once connected, we can move with either wasd or WASD to jump over walls.

─$ nc 13.201.224.182 30961                                                                                    130 β¨―
Welcome to The Far Lands! You're free to explore. 
You can move around using wasd (lowercase). 
If you hit a wall, you can jump over them using WASD (uppercase). 
You can't jump if there's no wall in front of you. 
Lastly, you're placed in a random position in the maze. 
Do your best to figure out it's secrets!

2000> w
Moved!
1999> w
You can't move there!
1998> W
Jumped over a wall!
1997> w
Moved!
...

Once we get to the boundary (at the top when trying w/W), neither option will work. To map out the entire maze, I wrote the following to go to the upper left corner, and then zig-zag right and left through the maze.

import socket
import time

HOST = '13.201.224.182'
PORT = 32127

# Create a socket, connect to the server, and return the socket
def create_socket_and_connect(host, port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    return s

# Send a command to the server and return the response
def send_command(sock, command):
    ret = sock.recv(4096).decode('utf-8')
    sock.sendall(f"{command}\n".encode('utf-8'))
    ret = sock.recv(4096).decode('utf-8')
    #print(command, ret) # print out command outputs for debugging
    return ret

# Navigate to the upper-left corner of the maze
def navigate_to_upper_left_corner(sock):
    while True:
        response = send_command(sock, 'w')
        if "can't move there" in response:
            response = send_command(sock, 'W')
            if "can't move there" in response:
                break

    while True:
        response = send_command(sock, 'a')
        if "can't move there" in response:
            response = send_command(sock, 'A')
            if "can't move there" in response:
                break

# Move to the right until the boundary is hit
def explore_right(sock):
    row = []
    while True:
        response = send_command(sock, 'd')
        if "can't move there" in response:
            response = send_command(sock, 'D')
            if "can't move there" in response:
                break
            else:
                row.append("#")
        else:
            row.append(".")
    print(row)
    
# Move to the left until the boundary is hit
def explore_left(sock):
    row = []
    while True:
        response = send_command(sock, 'a')
        if "can't move there" in response:
            response = send_command(sock, 'A')
            if "can't move there" in response:
                break
            else:
                row.append("*")
        else:
            row.append(".")
    print(row)

def main():
    with create_socket_and_connect(HOST, PORT) as sock:
        # Receive and print the welcome message
        print(sock.recv(4096).decode('utf-8'))
        time.sleep(1)

        # Navigate to the upper-left corner of the maze
        navigate_to_upper_left_corner(sock)
        
        # Skip a few rows that had no walls, found earlier
        response = send_command(sock, 's')
        response = send_command(sock, 's')
        response = send_command(sock, 's')
        
        # Go down one, move to the right, go down one, move to the left
        while True:
            response = send_command(sock, 's')
            if "can't move there" in response:
                break
            explore_right(sock)
            response = send_command(sock, 's')
            if "can't move there" in response:
                break
            explore_left(sock)

if __name__ == "__main__":
    main()

This will print out the rows, with a # for if there was a wall. Since we're going backwards every other row, I have * in the output to make a note to flip it. I did that by copy pasting the output to a text file, and in vim selecting the row (V) and then reversing it (:%!rev). Then find/replace * with # and deleting all characters other than # and space. See the final output below.

. . . # . . . . . . # # . # # # # # . . # . . . # # . . . . . . # . . .
. . . # # . . . . # # # . # . # # . . . # . # . . # # . . . . # # . . .
. . . # # # . . # # # . # . # . # # . . . # # # # # # # . . # # # . . .
. . . # # # . . # # # . # # # . # . # # # . # . . # # # . . # # # . . .
. . . # # # . . # # # . . . . . . . . # # # . # . # # # . . # # # . . .
. . . # # . . . . # # # . . # # . # . # . # . . . # # . . . . # # . . .
. . . # . . . . . . # # # # # # # # # # # # # # # # . . . . . . # . . .
. . . . . . . . . . . # # . # # . . # . # . # . # . . . . . . . . . . .
. . . . . . # . # # # . # . . . # # . # # . # # . . . # . . # . # . . .
. . . # # # # . # # # . # # # # . # . . . . # . # . # . . # # . # . . .
. . . # # # . . . . # # . # # . . # # . . . . # # # . # # . . # # . . .
. . . # . . . # . . . . . # . # . . . . . . # . # . . # # # . . # . . .
. . . . # # . # # # . . # # # . # # # . # # # . # # # . # # # . . . . .
. . . # # . . # . # # # . . . # . . . # . # # . # . . . # . . . . . . .
. . . # # # . . . . # # . # # . . . . . # # . # . # # . . . . . . . . .
. . . # . # # . # . . # # . # # . # # . # . # . # . # . # . . . # . . .
. . . . . # # . # . # . . . # . # # # # . # # . . . . # . # # # # . . .
. . . # . # . # # . . # # . . # # . . . # . # . # . # # # # # # # . . .
. . . # . # . # . . # # # . # # . . # # . # . # # # . . # . . # . . . .
. . . # # . # . # . . . # # # # # # . # . . # # # # . . # # . # . . . .
. . . . # . . . . . . # . # # # . # # . # # # # . . . . # . . . . . . .
. . . . . . . . . . . . # . # # . # # . . # . # # . . # . # . # # . . .
. . . # . . . . . . # . # # . # . # . . # . . . # # # # # # . # . . . .
. . . # # . . . . # # . # . . # . . . . # # . # # . . # # # # # # . . .
. . . # # # . . # # # # . . . # # . # . # # . # . . . . # # # # # . . .
. . . # # # . . # # # # # # . . # # . . . . . # # # # # . # . . # . . .
. . . # # # . . # # # . # . # . . # . . # # # # . . # . # . . # . . . .
. . . # # . . . . # # . # # . # . . . . # . . # # . # . # # # . . . . .
. . . # . . . . . . # . . . . # . # # . . . . # . . . # . # # # . . . .

This looks like a QR code but not quite, and this is because this is just a representation of where the horizontal walls are. Picturing a black and white QR code, each horizontal wall should signify a switch from white to black, and vice versa. Therefore, every time a # is encountered, it's actually a toggle. The below code fixes this.

def toggle_square_array(input_lines):
    # Initialize the output lines list
    transformed_lines = []

    # Process each input line
    for line in input_lines:
        current_char = '.'  # Start with '.' as the initial output character
        transformed_line = ""
        for char in line.split():  # Assuming characters are separated by spaces
            if char == '#':
                # Toggle the current character when a '#' is encountered
                current_char = '.' if current_char == '#' else '#'
            transformed_line += current_char + " "
        transformed_lines.append(transformed_line.strip())

    return transformed_lines

# Input lines
input_lines = [
    ". . . # . . . . . . # # . # # # # # . . # . . . # # . . . . . . # . . .",
    ". . . # # . . . . # # # . # . # # . . . # . # . . # # . . . . # # . . .",
    ". . . # # # . . # # # . # . # . # # . . . # # # # # # # . . # # # . . .",
    ". . . # # # . . # # # . # # # . # . # # # . # . . # # # . . # # # . . .",
    ". . . # # # . . # # # . . . . . . . . # # # . # . # # # . . # # # . . .",
    ". . . # # . . . . # # # . . # # . # . # . # . . . # # . . . . # # . . .",
    ". . . # . . . . . . # # # # # # # # # # # # # # # # . . . . . . # . . .",
    ". . . . . . . . . . . # # . # # . . # . # . # . # . . . . . . . . . . .",
    ". . . . . . # . # # # . # . . . # # . # # . # # . . . # . . # . # . . .",
    ". . . # # # # . # # # . # # # # . # . . . . # . # . # . . # # . # . . .",
    ". . . # # # . . . . # # . # # . . # # . . . . # # # . # # . . # # . . .",
    ". . . # . . . # . . . . . # . # . . . . . . # . # . . # # # . . # . . .",
    ". . . . # # . # # # . . # # # . # # # . # # # . # # # . # # # . . . . .",
    ". . . # # . . # . # # # . . . # . . . # . # # . # . . . # . . . . . . .",
    ". . . # # # . . . . # # . # # . . . . . # # . # . # # . . . . . . . . .",
    ". . . # . # # . # . . # # . # # . # # . # . # . # . # . # . . . # . . .",
    ". . . . . # # . # . # . . . # . # # # # . # # . . . . # . # # # # . . .",
    ". . . # . # . # # . . # # . . # # . . . # . # . # . # # # # # # # . . .",
    ". . . # . # . # . . # # # . # # . . # # . # . # # # . . # . . # . . . .",
    ". . . # # . # . # . . . # # # # # # . # . . # # # # . . # # . # . . . .",
    ". . . . # . . . . . . # . # # # . # # . # # # # . . . . # . . . . . . .",
    ". . . . . . . . . . . . # . # # . # # . . # . # # . . # . # . # # . . .",
    ". . . # . . . . . . # . # # . # . # . . # . . . # # # # # # . # . . . .",
    ". . . # # . . . . # # . # . . # . . . . # # . # # . . # # # # # # . . .",
    ". . . # # # . . # # # # . . . # # . # . # # . # . . . . # # # # # . . .",
    ". . . # # # . . # # # # # # . . # # . . . . . # # # # # . # . . # . . .",
    ". . . # # # . . # # # . # . # . . # . . # # # # . . # . # . . # . . . .",
    ". . . # # . . . . # # . # # . # . . . . # . . # # . # . # # # . . . . .",
    ". . . # . . . . . . # . . . . # . # # . . . . # . . . # . # # # . . . ."
]

# Transform the input
transformed_lines = toggle_square_array(input_lines)

# Print the transformed lines
for line in transformed_lines:
    print(line)

This gives us the fixed map, but it's in ASCII so one more program to convert it to a QR image.

import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

# QR pattern from the previous code
qr_code_pattern = """
. . . # # # # # # # . # # . # . # . . . # # # # . # # # # # # # . . . .
. . . # . . . . . # . # # . . # . . . . # # . . . # . . . . . # . . . .
. . . # . # # # . # . . # # . . # . . . . # . # . # . # # # . # . . . .
. . . # . # # # . # . . # . # # . . # . # # . . . # . # # # . # . . . .
. . . # . # # # . # . . . . . . . . . # . # # . . # . # # # . # . . . .
. . . # . . . . . # . # # # . # # . . # # . . . . # . . . . . # . . . .
. . . # # # # # # # . # . # . # . # . # . # . # . # # # # # # # . . . .
. . . . . . . . . . . # . . # . . . # # . . # # . . . . . . . . . . . .
. . . . . . # # . # . . # # # # . # # . # # . # # # # . . . # # . . . .
. . . # . # . . # . # # . # . # # . . . . . # # . . # # # . # # . . . .
. . . # . # # # # # . # # . # # # . # # # # # . # . . # . . . # . . . .
. . . # # # # . . . . . . # # . . . . . . . # # . . . # . # # # . . . .
. . . . # . . # . # # # . # . . # . # # . # . . # . # # . # . . . . . .
. . . # . . . # # . # . . . . # # # # . . # . . # # # # . . . . . . . .
. . . # . # # # # # . # # . # # # # # # . # # . . # . . . . . . . . . .
. . . # # . # # . . . # . . # . . # . . # # . . # # . . # # # # . . . .
. . . . . # . . # # . . . . # # . # . # # . # # # # # . . # . # . . . .
. . . # # . . # . . . # . . . # . . . . # # . . # # . # . # . # . . . .
. . . # # . . # # # . # . . # . . . # . . # # . # . . . # # # . . . . .
. . . # . . # # . . . . # . # . # . . # # # . # . # # # . # # . . . . .
. . . . # # # # # # # . . # . # # . # # . # . # # # # # . . . . . . . .
. . . . . . . . . . . . # # . # # . # # # . . # . . . # # . . # . . . .
. . . # # # # # # # . . # . . # # . . . # # # # . # . # . # # . . . . .
. . . # . . . . . # . . # # # . . . . . # . . # . . . # . # . # . . . .
. . . # . # # # . # . # # # # . # # . . # . . # # # # # . # . # . . . .
. . . # . # # # . # . # . # # # . # # # # # # . # . # . . # # # . . . .
. . . # . # # # . # . . # # . . . # # # . # . # # # . . # # # . . . . .
. . . # . . . . . # . . # . . # # # # # . . . # . . # # . # . . . . . .
. . . # # # # # # # . . . . . # # . # # # # # . . . . # # . # . . . . .
"""

# Converting the QR code pattern into a binary matrix
pattern_lines = qr_code_pattern.strip().split("\n")
binary_matrix = [[0 if char == '#' else 1 for char in line.split()] for line in pattern_lines]

# Converting the binary matrix into an image
binary_matrix = np.array(binary_matrix, dtype=np.uint8) * 255  # Convert to 0 and 255
qr_image = Image.fromarray(binary_matrix, mode='L')  # 'L' mode for grayscale

# Display the generated QR code image
plt.imshow(qr_image, cmap='gray')
plt.axis('off')  # Hide axes
plt.show()

Using a QR reader, we get the flag!

bi0sctf{Vkh5P76p4h8kemCI4TXOBw==}

Last updated