The Shape of Silence
The archive the_shape_of_silence.zip contains:
Category: Machine Learning / Reverse Engineering
Flag: isfcr{577027585a0778773aa949d9}
Challenge description
A black-box model returns exact rational outputs. Submit its outputs for the 24 inputs in
data.jsonundertargets, in order, as a JSON list of reduced rational strings (for example"-87/64"or"26"). Then run:BASHpython3 unlock.py '["...", "...", ...]'Flag format:
isfcr{...}
Provided files
The archive the_shape_of_silence.zip contains:
data.json: model metadata, observations, anchors, and the 24 target inputs.unlock.py: validates an exact ordered list of outputs by attempting authenticated decryption.
Unpack the files:
unzip the_shape_of_silence.zip1. Inspecting the dataset
The JSON describes a six-dimensional model (dimension: 6) with the current version identifier v-8fb4ff. It includes:
captures: 22 capture records, consisting of 18 fromv-8fb4ffand 4 from the older versionv-41e59d;anchors: 16 input/output examples;targets: 24 six-dimensional inputs for which exact outputs are needed.
Each observation supplies its input coordinates as rational strings and its scalar output y as a rational string. The inputs and answers therefore need to be handled exactly, not with floating-point approximations.
A quick inspection script:
import json
from collections import Counter
with open("data.json") as f:
data = json.load(f)
print("dimension:", data["dimension"])
print("current version:", data["model_version"])
print("targets:", len(data["targets"]))
print("anchors:", len(data["anchors"]))
print("capture versions:", Counter(c["model_version"] for c in data["captures"]))2. Recovering the response function
The critical insight is to use the structure of the captures rather than fitting an arbitrary high-degree polynomial to the anchor points.
The captures contain groups of nearby inputs and their outputs. For each group, changes in output across small changes in the six coordinates reveal a local gradient. With exact rational arithmetic, these changes can be compared without rounding errors.
A useful starting point is to filter out the observations for the obsolete model version:
from fractions import Fraction
version = data["model_version"]
observations = [
(tuple(map(Fraction, point["x"])), Fraction(point["y"]))
for capture in data["captures"]
if capture["model_version"] == version
for cloud in capture["clouds"]
for point in cloud
]Within a region in which a piecewise-linear model has no change of activation pattern, differences satisfy
[ f(x+\Delta x)-f(x) = g\cdot\Delta x, ]
where g is a constant local gradient vector. Solving these linear equations using Fraction-based Gaussian elimination reveals the local gradients. Comparing gradients across capture regions exposes discrete changes consistent with nine piecewise-linear components.
The general reconstruction strategy is:
- Group observations by their capture clouds and retain only the current model version.
- Solve for local gradient vectors using exact rational differences.
- Compare the gradients to identify changes in the active piecewise-linear terms.
- Use the anchor inputs/outputs to constrain the overall offset and the remaining unknown parameters.
- Validate the resulting function on observations not used for fitting, then evaluate
targetsin their original order.
Note: This section documents the recovery strategy rather than claiming a complete parameter-recovery implementation; the independently executable part of this writeup is the final-output verification below.
3. Exact outputs for the 24 targets
The following list is in the same order as data.json["targets"]. Every value is a reduced rational string:
[
"5441/64",
"909/32",
"8939/64",
"1055/32",
"6041/64",
"1305/64",
"4173/32",
"4559/64",
"1597/16",
"1309/64",
"3015/32",
"423/4",
"2145/64",
"9475/64",
"26",
"2777/64",
"-1437/64",
"2969/64",
"983/16",
"5471/64",
"4163/64",
"5835/64",
"2971/64",
"2007/64"
]4. Unlocking and verifying the flag
The verifier serializes the ordered answer list as compact ASCII JSON, prefixes it with b'isfcr-ml\0', and hashes it using SHA-256 to obtain a 32-byte AES key:
enc = json.dumps(answer, ensure_ascii=True, separators=(',', ':')).encode('ascii')
key = hashlib.sha256(b'isfcr-ml\0' + enc).digest()It then performs AES-GCM decryption of the sealed ciphertext. The authenticated tag makes the check sensitive to every character and to the ordering of all 24 outputs. An incorrect answer produces Rejected.
To test the recovered values without manually typing a long shell argument, save this as solve.py alongside unlock.py:
from unlock import unlock
answers = [
"5441/64", "909/32", "8939/64", "1055/32",
"6041/64", "1305/64", "4173/32", "4559/64",
"1597/16", "1309/64", "3015/32", "423/4",
"2145/64", "9475/64", "26", "2777/64",
"-1437/64", "2969/64", "983/16", "5471/64",
"4163/64", "5835/64", "2971/64", "2007/64",
]
print(unlock(answers))Run:
python3 solve.pyVerified output:
isfcr{577027585a0778773aa949d9}Key takeaways
- The old model version is a distraction: use version-matched captures.
- Nearby samples expose local linear behavior that can be analyzed using finite differences.
- Exact rational arithmetic prevents numerical errors in gradient recovery and target evaluation.
- The flag is not stored in plaintext in the challenge files: the correct 24-element answer list is needed to derive the AES-GCM key.
Flag
isfcr{577027585a0778773aa949d9}