← Back to Writeups
CUSTOMMediumForensics

1043 грамма PLA

XESXOR9/2/20264 min read
#forensics#custom#medium#gcode_extrusion_parsing#layer_anomaly_detection#midpoint_layer_localization#toolpath_rendering

1043 грамма PLA

Platform: bug-makers | Category: Forensics | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-29 | Status: Solved Techniques: gcode_extrusion_parsing, layer_anomaly_detection, midpoint_layer_localization, toolpath_rendering

Summary

Task: a ~10MB Prusa i3 G-code file; a flag appears ~15h into a ~30.8h print, stays visible 30 min, then is overprinted. Solution: parse per-layer E-positive extrusion segments, detect the mid-print layer with an anomalous segment-count spike (the embedded text), and render those toolpaths top-down to read the flag.

1043 грамма PLA — bug-makers

Description

1043 грамма PLA. Примерно через 15 часов работы Prusa i3 появится флаг, и будет виден пол часа, потом исчезнет.

English: "1043 grams of PLA. After approximately 15 hours of Prusa i3 operation, a flag will appear, be visible for half an hour, then disappear."

Provided artifact: 1043.gcode.zip containing a single ~10 MB 1043.gcode file. The goal is to recover the flag that is only momentarily visible during the physical print.

Analysis

The G-code header from Cura tells us everything we need: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

;FLAVOR:Marlin
;TIME:111086
;Filament used: 349.606m
;Layer height: 0.4
;MINX:12.2 ;MINY:12.2 ;MINZ:0.3 ;MAXX:187.8 ;MAXY:187.8 ;MAXZ:159.9
;TARGET_MACHINE.NAME:Prusa i3
;Generated with Cura_SteamEngine 5.11.0
;LAYER_COUNT:400

Semantic clue mapping. ;TIME:111086 seconds ≈ 30.86 hours is the full print time. The hint says the flag appears at ~15 hours — almost exactly half the print. With 400 layers, halfway corresponds to layer ~200. The phrasing "visible for half an hour, then disappears" is the key insight: the flag is printed as a thin text band at a mid-height layer, then buried (overprinted) by the subsequent solid layers. It is therefore invisible in the finished model and visible only mid-print. This is geometry steganography — the message lives in the toolpaths, not in any file metadata. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

So the flag is not in the file's text/comments; it is in the extrusion geometry of a few layers around layer 200.

Solution

  1. Parse extrusion segments per layer. In Marlin/Cura G-code, material is deposited only when the extruder axis E advances. Track absolute X/Y/E; a G0/G1 move that increases E is a printed segment (x0,y0,x1,y1). Layers are delimited by ;LAYER: comments.

  2. Detect the anomalous layer via segment count. A normal layer of this model deposits ~382 segments (median). Detailed text glyphs require far more, short, segments, so the text layers stand out as a sharp spike: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

    LayerSegments
    median~382
    2002129
    2012815
    2022579
    2032578
    2042582
    2052808
    2061250

    Minor spikes at layers 1, 7–8, 394–395 are skirt and top/bottom solid fill — not text. The cluster at 200–206, exactly at the predicted midpoint, is the embedded text.

  3. Render the anomalous layers top-down. Plot every extrusion segment of layers 200–206 in the XY plane with matplotlib. The first render (with an inverted Y axis, as many printers/plotters default to) showed the text mirrored. Removing the Y inversion and cropping to the text band (raw model coords Y ≈ 90–115, X ≈ 15–185) made the flag cleanly readable. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The toolpaths spell out the flag.

Reusable snippet — parse G-code extrusion + find anomalous layers

python
#!/usr/bin/env python3
# Parse Cura/Marlin G-code into per-layer extrusion segments,
# flag layers whose segment count is an outlier (hidden geometry),
# and render the suspect layers top-down to read embedded text.
import re, statistics
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

def parse_layers(path):
    layers = []          # list of list-of-segments; segment = (x0,y0,x1,y1)
    cur = []
    x = y = e = 0.0
    abs_e = True         # M82 = absolute extrusion (Cura default)
    for line in open(path, errors="ignore"):
        s = line.strip()
        if s.startswith(";LAYER:"):
            if cur:
                layers.append(cur)
            cur = []
            continue
        if s.startswith("M83"): abs_e = False
        if s.startswith("M82"): abs_e = True
        if not (s.startswith("G0") or s.startswith("G1")):
            continue
        nx, ny, ne = x, y, None
        for tok in s.split():
            if   tok[0] == "X": nx = float(tok[1:])
            elif tok[0] == "Y": ny = float(tok[1:])
            elif tok[0] == "E": ne = float(tok[1:])
        extruding = False
        if ne is not None:
            de = (ne - e) if abs_e else ne
            extruding = de > 1e-6
            e = ne if abs_e else e + ne
        if extruding and (nx != x or ny != y):
            cur.append((x, y, nx, ny))
        x, y = nx, ny
    if cur:
        layers.append(cur)
    return layers
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

def anomalous_layers(layers, factor=2.5):
    counts = [len(l) for l in layers]
    med = statistics.median(counts)
    return [(i, c) for i, c in enumerate(counts) if c > factor * med], med

def render(layers, idxs, out="text.png"):
    fig, ax = plt.subplots(figsize=(20, 6))
    for i in idxs:
        for (x0, y0, x1, y1) in layers[i]:
            ax.plot([x0, x1], [y0, y1], color="black", linewidth=0.5)
    ax.set_aspect("equal")          # DO NOT invert Y — keep model orientation
    fig.savefig(out, dpi=150)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

layers = parse_layers("1043.gcode")
anoms, med = anomalous_layers(layers)
print("median segs/layer:", med)
print("anomalous:", anoms)         # -> layers ~200-206 spike to 2000-2800
render(layers, range(200, 207))    # crop Y~90-115, X~15-185 to read flag

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍