Take a Slice
Take a Slice
Platform: Umasscybersec | Category: Steganography | Type: Challenge | Difficulty: Medium | OS: NA | Author: D3v0o0Nu11 | Date: 2026-04-11 | Status: Solved Techniques: binary_stl_parsing, connected_component_isolation, pca_projection, projected_triangle_rendering
Summary
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}.
Recon
Port scan
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
| Port | Service | Version | Notes |
|---|---|---|---|
| <PORT> | <SVC> | <VER> | <notes> |
Enumeration highlights
- Event:
umasscybersec| ID:20260411_umasscybersec_take_a_slice - Tags: pca, stl, binary_stl, mesh_steganography, hidden_geometry, 3d_model
- Indicators: A file identified only as generic data but matching the binary STL layout, Binary STL header with a plausible triangle count at offset 80, Raw top/front/side projections showing faint letter-like artifacts, Many disconnected mesh components where one large model coexists with several tiny ones, Hidden content becomes readable only after projecting isolated geometry and filling triangles
- Source:
20260411_umasscybersec_take_a_slice.md
Foothold
Vulnerability / Misconfiguration
- Binary_stl_parsing
- Connected_component_isolation
- Pca_projection
- Projected_triangle_rendering
<command>
Exploitation
- See original writeup content for detailed exploitation.
Privilege Escalation
Enumeration
sudo -l find / -perm -4000 2>/dev/null getcap -r / 2>/dev/null cat /etc/crontab ps aux
Exploitation
- N/A for challenge-type writeup; see exploitation above.
- Flag obtained via challenge solve.
<command>
Flags
| Flag | Location | Value |
|---|---|---|
| flag | REDACTED |
Key Takeaways / Lessons
- binary_stl_parsing
- connected_component_isolation
- pca_projection
- projected_triangle_rendering
- Tags: pca, stl, binary_stl, mesh_steganography, hidden_geometry, 3d_model
Original Writeup
<details><summary>Click to expand original content</summary>Challenge
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.
Recon
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:
- 80-byte header
- 4-byte little-endian triangle count
- then 50 bytes per triangle
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:
12bytes: normal vector (float32 x 3)36bytes: 3 vertices (float32 x 9)2bytes: attribute field
No useful strings or metadata were present. The flag was hidden in the mesh itself.
Analysis
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.png
That 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:
- each unique vertex is a node
- triangles connect their three vertices
- connected triangles belong to the same mesh component
Running connected-component analysis over shared vertices found:
- 20 total mesh components
- 1 large component for the visible cake model
- 19 smaller components that were likely the hidden payload
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:
- principal component 1 vs principal component 3
This corresponded to the helper image:
tasks/umasscybersec/Take a Slice/pc13.png
Finally, 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.png
The rendered text was:
UMASS{REDACTED}
Solution
1. Parse the binary STL
Binary STL is simple to parse. Read the header, triangle count, then unpack each 50-byte triangle record.
2. Build connected components
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.
3. Keep only the small hidden meshes
The payload was split across 19 small components. Removing the main cake component eliminated most of the visual clutter.
4. Project with PCA
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.
5. Render filled triangles
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.
</details>Auto-tracked: saved to WriteUps; run
/xesor-reviseto fold lessons into XESXor_Methodology.md.
signed by XESXOR