π¦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