Skip to content

Lab Fabricator: decoding the telemetry bench

4 min read

Step-by-step solution for the Monaco playground’s `lab-fabricator` challenge—inspect the trace, reverse the spiral salt, undo the XOR, rotate the bytes, and submit the recovered flag.

The Monaco playground doubles as a telemetry lab. Clicking the JSON button loads a mocked firmware capture named lab-fabricator. Every two entries in the frames array correspond to one byte of the hidden flag. The entire challenge is self-contained: the dataset, the execution sandbox, and the SHA-256 flag verifier all sit on the same page. This walkthrough matches the playground’s behavior exactly and walks through every transformation until the final flag, flag{monaco_fullstack_kernel_ctf}, appears.


Tools and fixtures in the playground

Once you open /playground, click Load dataset. The JSON panel should show:

json
{
  "challenge": "lab-fabricator",
  "frames": [39, 52, 103, 185, 236, 255, "..."],
  "key": [19, 55, 192, 222, 66, 153],
  "spiral": "n^2 + 11n + 7 (mod 256)"
}

Important observations:

  • Frames arrive as [A0, B0, A1, B1, ...]. Pair index idx = i / 2.
  • The six-byte key repeats frequently and is intentionally short so that three different key schedules overlap.
  • The spiral function is deterministic per index: spiral(idx) = (idx² + 11·idx + 7) & 0xff.
  • The playground ships with FLAG_HASH_HEX = 7f2d7991bf72a3640057e7cf08875921d4b5779e2e3a66396f146d7e31a9f994. Your recovered string must hash to that value.

Step 1 – Inspecting the capture

Before reversing anything, print the first few pairs and confirm the structure:

ts
const ctx = typeof input === "object" ? input : {};
const frames = ctx.frames ?? [];
const key = ctx.key ?? [];

for (let i = 0; i < 6; i += 2) {
  const idx = i / 2;
  console.log(idx, "A:", frames[i], "B:", frames[i + 1]);
}

console.log("pairs:", frames.length / 2, "key length:", key.length);

The log confirms there are 34 pairs (68 numbers) and the key length is 6. From here the decoding pipeline is the reverse of the encoder’s order.


Step 2 – Frame integrity check

Each pair is encoded with a “sanity guard”. If the capture is untrusted, verify that guard before doing more work. The condition enforced during encoding is:

text
B ^ key[(idx * 3) % key.length] === A

Add this check to your loop and throw if any pair fails. It protects against bitrot or tampering and mirrors the Frame integrity scan example bundled with the playground.


Step 3 – Remove the spiral salt

After the guard, the encoder added an index-specific bias. Undo it by subtracting the spiral value and wrapping inside an 8-bit lane:

text
salted = (A - spiral(idx) + 256) & 0xff

The + 256 keeps the subtraction positive before masking. Implement spiral exactly as declared in the dataset string:

ts
const spiral = (i: number) => (i * i + 11 * i + 7) & 0xff;

Step 4 – Undo the XOR mask

The salted value was masked again with the primary key schedule. Strip it by XORing with key[idx % key.length]:

text
masked = salted ^ key[idx % key.length]

At this point each masked byte is still rotated.


Step 5 – Rotate the byte back into place

The encoder rotated the byte three positions to the left. Undoing that is a right rotation by three. A small helper keeps the math readable:

ts
const rotRight8 = (value: number, count: number) =>
  ((value >>> count) | (value << (8 - count))) & 0xff;

Applying rotRight8(masked, 3) yields the true ASCII byte.


Putting it together

Drop the following decoder into the Monaco editor (or load it from the Examples dropdown if you saved a share link). It mirrors the steps above and prints the final flag:

js
const ctx = typeof input === "object" && input !== null ? input : {};
const frames = Array.isArray(ctx.frames) ? ctx.frames : [];
const key = Array.isArray(ctx.key) ? ctx.key : [];

if (!frames.length || !key.length) {
  throw new Error("Load the lab-fabricator dataset first.");
}

const spiral = (i) => (i * i + 11 * i + 7) & 0xff;
const rotRight8 = (value, count) =>
  ((value >>> count) | (value << (8 - count))) & 0xff;

const bytes = [];

for (let i = 0; i < frames.length; i += 2) {
  const idx = i / 2;
  const A = frames[i];
  const B = frames[i + 1];
  const guardKey = key[(idx * 3) % key.length];
  if ((B ^ guardKey) !== A) {
    throw new Error(\`Frame pair \${idx} failed integrity check\`);
  }

  const salted = (A - spiral(idx) + 256) & 0xff;
  const masked = salted ^ key[idx % key.length];
  bytes.push(rotRight8(masked, 3));
}

const flag = String.fromCharCode(...bytes);
console.log("Recovered flag:", flag);
return flag;

Running it prints:

text
Recovered flag: flag{monaco_fullstack_kernel_ctf}

Copy the string into the verifier underneath the console. The verifier hashes the candidate with crypto.subtle.digest("SHA-256", value) and compares it to FLAG_HASH_HEX. If everything matches, the status pill turns green and the hash is echoed back for sanity.


Troubleshooting

  • Integrity check fails immediately – make sure you are reading frames[i] as A and frames[i + 1] as B. Off-by-one errors or iterating by 1 instead of 2 are the usual culprits.
  • Verifier rejects the flag – verify the rotation direction. Rotating left instead of right yields printable text but hashes to something else.
  • Nothing prints – the Monaco worker aborts executions that exceed the time limit. Keep the loop synchronous and avoid accidentally awaiting console.

Going beyond the base puzzle

  • Swap in your own dataset by editing the JSON panel. As long as you follow the same shape—frames, key, spiral—the decoder above can process it.
  • Modify the polynomial, the guard, or the rotation count to create harder variants. Share them via the Share button; the link encodes the current code, input, and time limit.
  • Extend the verifier. Because the worker exposes the Web Crypto API you can build MACs, signature checks, or even embed a second stage challenge.

If you create a nastier telemetry log or expand the tooling, send it my way. The Monaco playground is meant to evolve with community-built traces, and I’ll keep linking the best ones from this article.