In this write-up
These notes document the October 2026 CTF challenge. Commands, addresses and outputs refer to the supplied challenge environment.
1. Overview#
We are given:
check.pydials.jsonbundle.zip(packaging; the solution uses the supplied checker and JSON)
The checker reads dials.json, asks for a combination, and verifies it against a dense system of linear equations modulo 257.
The goal is to recover the input bytes that satisfy the system.
2. Inspecting the files#
First, look at the checker:
cat -n check.py
Output:
import json
from pathlib import Path
lock = json.loads(Path(__file__).with_name("dials.json").read_text())
candidate = input("Combination: ").encode()
ok = len(candidate) == len(lock["target"])
if ok:
ok = all(sum(a*b for a,b in zip(row,candidate)) % 257 == expected
for row,expected in zip(lock["matrix"],lock["target"]))
print("Unlocked" if ok else "Locked")
So the checker:
- Reads
dials.json. - Takes the user input as bytes.
- Requires the input length to equal
len(lock["target"]). - For each row in
lock["matrix"], computes:and compares it tosum_j matrix[i][j] · candidate[j] (mod 257)lock["target"][i].
This is exactly a system of linear equations over the finite field GF(257).
3. Structure of dials.json#
A quick Python inspection shows:
{
"modulus": 257,
"matrix": [ ... 37 rows ... ],
"target": [ ... 37 values ... ]
}
The matrix has 37 rows, and target has 37 entries.
Therefore, the candidate must be 37 bytes long.
So we have:
- 37 unknowns
x_0, x_1, ..., x_{36} - 37 equations
- All arithmetic modulo
257
The modulus 257 is prime, so every non-zero element has a modular inverse. This means we can solve the system with Gaussian elimination.
4. Mathematical formulation#
Let:
Abe the37 × 37matrix fromdials.json["matrix"]bbe the target vector fromdials.json["target"]xbe the unknown candidate bytes
We need:
A x ≡ b (mod 257)
Since 257 is prime, we can perform standard Gaussian elimination over GF(257).
5. Solver script#
Create a file called solve.py:
import json
with open('dials.json') as f:
data = json.load(f)
mod = data['modulus']
A = data['matrix']
b = data['target']
n = len(A) # 37
# Build augmented matrix [A | b]
M = [row[:] + [b[i]] for i, row in enumerate(A)]
# Gaussian elimination over GF(mod)
for i in range(n):
# Find pivot
pivot = None
for r in range(i, n):
if M[r][i] % mod != 0:
pivot = r
break
if pivot is None:
print("No unique solution")
exit(1)
# Swap rows
M[i], M[pivot] = M[pivot], M[i]
# Normalize pivot row
inv = pow(M[i][i], -1, mod)
for c in range(i, n + 1):
M[i][c] = (M[i][c] * inv) % mod
# Eliminate other rows
for r in range(n):
if r != i:
factor = M[r][i]
if factor != 0:
for c in range(i, n + 1):
M[r][c] = (M[r][c] - factor * M[i][c]) % mod
# Extract solution
x = [M[i][n] for i in range(n)]
print("Solution ints:", x)
print("Solution bytes:", bytes(x))
try:
print("String:", bytes(x).decode('ascii'))
except Exception as e:
print("Decode error:", e)
6. Running the solver#
python3 solve.py
Output:
Solution ints: [116, 105, 98, 101, 98, 123, 101, 113, 117, 97, 116, 105, 111, 110, 115, 95, 98, 100, 53, 101, 53, 99, 56, 100, 50, 102, 52, 101, 48, 48, 97, 97, 101, 48, 52, 99, 125]
Solution bytes: b'tibeb{equations_bd5e5c8d2f4e00aae04c}'
String: tibeb{equations_bd5e5c8d2f4e00aae04c}
7. Verification#
Check the candidate with the original checker:
echo -n 'tibeb{equations_bd5e5c8d2f4e00aae04c}' | python3 check.py
It should print:
Unlocked
8. Flag#
tibeb{equations_bd5e5c8d2f4e00aae04c}
Interesting / Tricky Points#
-
Modulus 257 is prime
This is important because Gaussian elimination requires division. Since257is prime, every non-zero pivot has an inverse modulo257. -
257 vs 256
The modulus is one more than256. This is common in CTFs because bytes range from0to255, but modular arithmetic mod257allows values up to256. In this challenge, the recovered bytes are all valid ASCII, so no issue arises. -
Dense system
The matrix is fully dense, so simple substitution or guessing would not work. Gaussian elimination is the intended solution. -
Length check
The checker enforceslen(candidate) == len(lock["target"]), which is37. If your solver returns a different length, something is wrong. -
bundle.zip
The shown solution usescheck.pyanddials.jsondirectly. No additional archive layer is required by the recorded method.
Final Answer#
tibeb{equations_bd5e5c8d2f4e00aae04c}