$ cat writeup.md…
$ cat writeup.md…
umasscybersec
Task: a file named `cake` looked like opaque binary data, but its structure matched a binary STL 3D mesh with hidden geometry. Solution: parse triangles, isolate disconnected mesh components, project the small hidden meshes with PCA, and render filled projected triangles to read `UMASS{REDACTED}`.
It's in the name!
The challenge provided a single file named cake. It was not immediately recognized as a common media format, so the goal was to identify the container first and then determine where the hidden data was actually stored.
Basic triage did not reveal anything obvious:
file cake # data
That ruled out easy wins like plain text, images, archives, or obvious appended content. A quick hex look was more useful: the file had a mostly zero 80-byte header followed by data that looked structured rather than random.
That pattern strongly suggested a binary STL file:
Parsing offset 80 as a little-endian uint32 gave a triangle count of 39210, which is exactly what a binary STL stores after the header. The geometry bounds were approximately:
x: [-1.347, 59.055]y: [-2.54, 43.105]z: [0, 25.4]So the mystery cake file was really a 3D model.
The binary STL record layout is:
12 bytes: normal vector (float32 x 3)36 bytes: 3 vertices (float32 x 9)2 bytes: attribute fieldNo useful strings or metadata were present. The flag was hidden in the mesh itself.
At this point, the main question was whether the model geometry itself encoded something visual. There were no relevant STL-specific hits in the existing knowledge base or HackTricks, so the solve path came from direct geometry analysis.
Plotting raw projections of all triangles from the top, front, and side views produced suspicious artifacts. They were not fully readable, but they looked too structured to be accidental. The helper renders in the task directory captured this stage:
tasks/umasscybersec/Take a Slice/top.pngtasks/umasscybersec/Take a Slice/front.pngtasks/umasscybersec/Take a Slice/side.pngThat suggested the text was not hidden in metadata or bit-level encoding, but as separate geometry embedded inside the STL.
The key insight was to treat the mesh as a graph:
Running connected-component analysis over shared vertices found:
After isolating the small components, the hidden geometry became much easier to inspect. Since the text was placed in 3D space at an angle, simple axis-aligned views were still suboptimal. PCA/SVD provided a better viewing plane.
The best readable projection was:
This corresponded to the helper image:
tasks/umasscybersec/Take a Slice/pc13.pngFinally, rendering the projected hidden triangles as filled polygons made the letters fully legible. That final step is visible in:
tasks/umasscybersec/Take a Slice/hidden_filled.pngThe rendered text was:
UMASS{REDACTED}
Binary STL is simple to parse. Read the header, triangle count, then unpack each 50-byte triangle record.
Collect each triangle's vertices, normalize them into hashable tuples, and build triangle adjacency by shared vertices. This separates the large visible model from disconnected hidden meshes.
The payload was split across 19 small components. Removing the main cake component eliminated most of the visual clutter.
The hidden geometry was not easiest to read from the standard XY/XZ/YZ views. PCA found a more natural plane aligned with the embedded text. The useful view was PC1 vs PC3.
A point cloud or wireframe view still leaves ambiguity. Filling the projected triangles produces solid glyphs, which makes the flag immediately readable.
Compact solve script:
#!/usr/bin/env python3 import struct from collections import defaultdict, deque import matplotlib.pyplot as plt import numpy as np PATH = "cake" def read_stl(path): tris = [] with open(path, "rb") as f: header = f.read(80) tri_count = struct.unpack("<I", f.read(4))[0] for _ in range(tri_count): rec = f.read(50) vals = struct.unpack("<12fH", rec) v1 = vals[3:6] v2 = vals[6:9] v3 = vals[9:12] tris.append(np.array([v1, v2, v3], dtype=np.float32)) return np.array(tris) def triangle_components(tris, decimals=5): vertex_to_tris = defaultdict(list) tri_vertices = [] for i, tri in enumerate(tris): keys = [] for v in tri: key = tuple(np.round(v, decimals)) keys.append(key) vertex_to_tris[key].append(i) tri_vertices.append(keys) adj = [[] for _ in range(len(tris))] for keys in tri_vertices: touched = set() for key in keys: touched.update(vertex_to_tris[key]) touched = list(touched) for a in touched: for b in touched: if a != b: adj[a].append(b) seen = set() comps = [] for start in range(len(tris)): if start in seen: continue q = deque([start]) seen.add(start) comp = [] while q: cur = q.popleft() comp.append(cur) for nxt in adj[cur]: if nxt not in seen: seen.add(nxt) q.append(nxt) comps.append(comp) return comps def pca_basis(points): centered = points - points.mean(axis=0) _, _, vt = np.linalg.svd(centered, full_matrices=False) return centered, vt def main(): tris = read_stl(PATH) comps = triangle_components(tris) comps.sort(key=len, reverse=True) # largest component is the cake; keep the hidden ones hidden_idx = [i for comp in comps[1:] for i in comp] hidden = tris[hidden_idx] pts = hidden.reshape(-1, 3) centered, basis = pca_basis(pts) projected = centered @ basis.T # best view for this challenge: PC1 vs PC3 x = projected[:, 0] y = projected[:, 2] fig, ax = plt.subplots(figsize=(12, 4)) for tri in hidden: tri_centered = tri - pts.mean(axis=0) tri_proj = tri_centered @ basis.T poly = np.c_[tri_proj[:, 0], tri_proj[:, 2]] ax.fill(poly[:, 0], poly[:, 1], color="black", linewidth=0) ax.set_aspect("equal") ax.axis("off") plt.tight_layout() plt.savefig("hidden_filled.png", bbox_inches="tight", pad_inches=0) plt.show() if __name__ == "__main__": main()
Running this against the isolated hidden geometry produced the readable filled rendering and revealed the flag.
$ cat /etc/motd
Liked this one?
Pro unlocks every writeup, every flag, and API access. $9/mo.
$ cat pricing.md$ grep --similar