$ cat writeup.md…
$ cat writeup.md…
FortID 2026
Task: two Factorio 2.0 blueprint strings hide a multi-stage forensics/stego payload. Solution: decode blueprint, read QR from tile bitmap, recover second QR from entity-occupancy XOR tiles, then render 29 disjoint circuit-wire networks as ordered leet glyphs.
Two attachment files are provided. They look like tangled, abandoned Factorio machinery. Untangle the construction.
English summary: the two handouts are standard Factorio 2.0 (Space Age) blueprint
strings — a leading format byte 0 followed by base64(zlib(JSON)). The blueprint
version field decodes to hex 0x20000004d0000 = Factorio 2.0.77. Inside the JSON
there are three independent covert layers. Each layer yields one fragment of the flag,
and the fragments concatenate into a leet rendering of a well-known Factorio meme phrase.
The recovered flag is of the form FortID{REDACTED}.
Two files, three layers:
entities (290, a 25×25 footprint) and tiles (841, a 29×29 square).tiles only (a 25×25 square of landfill presence).import base64, zlib, json raw = open("handout1.txt").read().strip() bp = json.loads(zlib.decompress(base64.b64decode(raw[1:]))) # strip the leading '0' blueprint = bp["blueprint"]
The leading character is the legitimate Factorio format-version prefix (0), not
tampering. blueprint["version"] = 0x20000004d0000.
tiles (29×29)handout1's 841 tiles are a 29×29 grid with exactly two types. Mapping one type to a
black module and the other to white produces a valid QR code, version 3, ECC level
M, mask 5. Its payload is the first flag fragment (a leet phrase). It decodes
identically with zxingcpp and pyzbar (cross-check both to rule out transcription
errors).
The entity cluster in handout1 lies in the 25×25 interior of the 29×29 tile square. Rasterize entity occupancy — one occupied cell per entity at its rounded position. XOR that 25×25 occupancy bitmap against handout2's 25×25 landfill presence. The result is a second valid QR, version 2, ECC level M, mask 6, yielding the second flag fragment.
Rasterization caveat. Four entities sit at half-integer coordinates. Using
nearest-even rounding reproduces the canonical QR bitmap exactly; floor rounding
flips 8 modules, all mechanically explained by those same four entities (each moved one
cell ⇒ two module flips). Reed–Solomon corrects the floor version without changing the
payload, and both decoders agree on the text. Do not read meaning into those 8 modules.
handout1's wires (104 red/green connections between 125 entities) form exactly 29
disconnected networks. Every member of a given network shares one
control_behavior.circuit_condition.constant, and the set of constants is exactly
1..29, once each. Render each network as its own small glyph by drawing the wire
edges between member entity positions, then lay the 29 panels out in ascending
constant order. The glyphs spell the third flag fragment as a leet phrase, and the
set of 29 glyphs completes the meme sentence.
The semantic clue "tangled machinery" points directly at this: the wires are only "tangled" because all 29 separate glyphs are drawn on top of one another. Isolating each network by its index untangles them.
decode.py:
#!/usr/bin/env python3 """Reconstruct all three layers of the FortID 2026 'Tangled' challenge.""" import base64, zlib, json import numpy as np from PIL import Image, ImageDraw import networkx as nx def load(path): raw = open(path).read().strip() return json.loads(zlib.decompress(base64.b64decode(raw[1:])))["blueprint"] def bitmap_to_qr(grid, size, out): """grid: 2-D bool array (True = black).""" a = np.array([[0 if v else 255 for v in row] for row in grid], dtype=np.uint8) Image.fromarray(a).resize((size * 8, size * 8), Image.NEAREST).save(out) def decode_qr(path): import zxingcpp res = zxingcpp.read_barcode(Image.open(path)) return res.text if res else None bp1 = load("handout1.txt") bp2 = load("handout2.txt") # --- Layer 1: handout1 tiles -> 29x29 QR ------------------------------- t1 = [[False] * 29 for _ in range(29)] for tile in bp1["tiles"]: x, y = int(tile["position"]["x"]), int(tile["position"]["y"]) t1[y + 5][x + 69] = (tile["name"] == "landfill") # one type == black bitmap_to_qr(t1, 29, "qr1.png") frag1 = decode_qr("qr1.png") # --- Layer 2: entity occupancy XOR handout2 tiles -> 25x25 QR ---------- ent = [[False] * 25 for _ in range(25)] for e in bp1["entities"]: c = round(e["position"]["x"] + 66.5) # nearest-even reproduces canonical QR r = round(e["position"]["y"] + 2.5) if 0 <= r < 25 and 0 <= c < 25: ent[r][c] = True t2 = [[False] * 25 for _ in range(25)] for tile in bp2["tiles"]: x, y = int(tile["position"]["x"]), int(tile["position"]["y"]) t2[y + 12][x + 12] = True xor = [[ent[r][c] ^ t2[r][c] for c in range(25)] for r in range(25)] bitmap_to_qr(xor, 25, "qr2.png") frag2 = decode_qr("qr2.png") # --- Layer 3: 29 wire networks -> ordered leet glyphs ------------------ entities = {e["entity_number"]: e for e in bp1["entities"]} g = nx.Graph() for a, _ca, b, _cb in bp1["wires"]: g.add_edge(a, b) indexed = {} for comp in nx.connected_components(g): labels = {entities[i].get("control_behavior", {}) .get("circuit_condition", {}).get("constant") for i in comp} - {None} if len(labels) == 1 and 1 <= labels.copy().pop() <= 29: indexed[labels.copy().pop()] = comp assert sorted(indexed) == list(range(1, 30)) # 1..29 exactly once W, H = 220, 175 canvas = Image.new("RGB", (W * 5, H * 6), "white") pen = ImageDraw.Draw(canvas) for label in range(1, 30): comp = indexed[label] pts = [entities[i]["position"] for i in comp] minx = min(p["x"] for p in pts); miny = min(p["y"] for p in pts) maxx = max(p["x"] for p in pts); maxy = max(p["y"] for p in pts) col, row = (label - 1) % 5, (label - 1) // 5 left, top = col * W, row * H scale = min((W - 30) / (maxx - minx + 1), (H - 50) / (maxy - miny + 1), 24) def px(i): p = entities[i]["position"] return (left + 19 + (p["x"] - minx) * scale, top + 39 + (p["y"] - miny) * scale) for a, b in g.subgraph(comp).edges(): pen.line((px(a), px(b)), fill="black", width=3) for i in comp: x, y = px(i); pen.ellipse((x - 3, y - 3, x + 3, y + 3), fill="black") canvas.save("network_gallery.png") # Read the 29 panels left-to-right, top-to-bottom -> frag3 glyphs print("frag1 =", frag1) print("frag2 =", frag2)
flag = frag1 + frag2 + frag3 (frag3 read from network_gallery.png), where frag3 is the
final glyph phrase that closes the FortID{...} brace.
The leet in this challenge is deliberately inconsistent, which is the whole difficulty. The same English letter is sometimes drawn as its leet digit and sometimes drawn as a normal letter, decided purely by the drawn wire glyph shape. Critically, the words in the third fragment are not uniformly leet — within the word rendered like "reasonable", some letters are plain letters, not their digit look-alikes.
Because more than one glyph was misread simultaneously, every single-position
correction attempt returns badFlag: fixing only one ambiguous glyph still leaves the
other wrong. The successful procedure was therefore:
Two independent QR decoders (zxingcpp + pyzbar) were cross-checked to prove the two
QR fragments were not mis-transcribed — this isolates the error to the glyph layer.
Use this technique when:
0 followed by base64 — try
base64(zlib(...)) and expect a game blueprint (Factorio version field confirms it).1..N once each — the constants are a glyph index; render each
network separately and order by the constant.$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ cat pricing.md$ grep --similar