Tags: s1c88 rev

Rating: 5.0

In short the check_flag function does the following:

1. XOR user input with a magic string
2. Convert the result to decimal and produce a large digit array that is interpreted as pairs of coordinates: [x0,y0,x1,y1,...,xn,yn]
3. Neighboring coordinates can’t share values x[i] != x[i+2] and y[i] != y[i+2]
4. Each consecutive pair of coordinates x1,y1 and x2,y2 specifies a line from x1,y1 to x2,y1 to x2,y2. Horizontal edges in the line are stored as value 5 in a path array while vertical edges are stored as 6. Corners in this path are stored as 1-4 depending on the orientation.
5. If any component of the path is self-intersecting, the check fails.
6. The path is validated using Masyu rules. Specifically there is a specified set of corner points (black dots) and edge points (white dots). Black dots must lie on a corner, white dots must lie on an edge and neighbor a corner:

We encode the solution digit array into our flag input:
py
magic = bytes([
15,57,42,9,53,58,
0,59,51,10,33,3,
58,58,17,12,60,60,
13,60,13,0,9,54])

charset = b'\
ABCDEFGHIJKLMNOPQRSTUVWXYZ\
abcdefghijklmnopqrstuvwxyz\
0123456789{}'

def decode(arr):
d_str = ''.join([str(x) for x in arr])
d = int(d_str[::-1])

parts = [0] * 24
for i in range(24):
parts[i] = d & 0x3f
d >>= 6

parts = parts[::-1]

out = bytes([a^b for a,b in zip(parts, magic)])
a_out = bytes([charset[x] for x in out])
return a_out

decode([
1,4, 0,0, 2,2, 3,6, 4,2, 5,6,
6,2, 8,1, 3,0, 9,5, 8,3, 7,6,
9,9, 8,7, 6,8, 7,9, 2,8, 5,7,
1,9, 0,6, 2,3])

PCTF{5EEth4tFURR3Tw4lcc}

Original writeup (https://ctf.harrisongreen.me/2021/plaidctf/pokemini/).