HTB: Uplink Artifact Challenge

Uplink Artifact - HackTheBox Challenge Writeup

Challenge Information

PropertyValue
NameUplink Artifact
CategoryMisc / Forensics
DifficultyVery Easy
Authord3vn0mi

Description

During an analysis of a compromised satellite uplink, a suspicious dataset was recovered. Intelligence indicates it may encode physical access credentials hidden within the spatial structure of Volnaya’s covert data infrastructure.

The challenge hands over a CSV file describing a 3D point cloud (x, y, z, label), framed as telemetry recovered from a compromised satellite uplink. The task is to figure out what’s actually encoded in the “spatial structure” of the data.

Solution

The handed-out CSV turned out to be a 0-byte stub — the real artifact was a 97 KB CSV bundled inside a password-protected zip (hackthebox) in the output directory.

The dataset contained points split across 4 labels. Profiling each label’s coordinate distribution revealed that three of them (0, 2, 3) were just random floating-point noise scattered in space — a red herring. Label 1, however, stood out immediately:

  • All 322 of its points sat on integer (x, y) coordinates
  • Those coordinates fit neatly inside a 25×25 grid
  • The z value was clamped tight to ~0.5 for every point (i.e., flat/planar — the third dimension carried no information)

A 25×25 grid of “on/off” pixels is a strong signal for a QR code (Version 2 QR codes are exactly 25×25 modules). Rendering the label-1 points as a black/white bitmap confirmed it — the image showed the three classic corner finder patterns of a QR code. Adding a quiet-zone border and feeding the bitmap to cv2.QRCodeDetector decoded it cleanly, revealing the flag.

Key Steps

1. Extract the real artifact from the password-protected zip:

Terminal window
mkdir -p /out/solve_.../extract && cd /out/solve_.../extract
unzip -o -P hackthebox /out/<challenge_id>.zip

2. Profile the point cloud by label to find the anomaly:

import csv
from collections import Counter
rows = list(csv.reader(open('uplink_spatial_auth.csv')))[1:]
labels = Counter(r[3] for r in rows)
print('label counts:', labels)
# Check which label(s) sit on an integer coordinate grid
pts = set()
for r in rows:
if r[3] == '1':
pts.add((int(float(r[0])), int(float(r[1]))))
max_x = max(p[0] for p in pts)
max_y = max(p[1] for p in pts)
print(f"label 1: {len(pts)} points, bounding box {max_x+1}x{max_y+1}")
# -> 322 points, 25x25 grid, z pinned near 0.5 for all of them

3. Render the label-1 points as a bitmap (QR bit-grid):

import csv, numpy as np, cv2
rows = list(csv.reader(open('uplink_spatial_auth.csv')))[1:]
pts = [(int(float(r[0])), int(float(r[1])))
for r in rows if r[3] == '1']
grid = np.zeros((25, 25), dtype=np.uint8)
for x, y in pts:
grid[y, x] = 255 # "on" module -> white pixel
# Scale up and add a quiet zone border (required for QR detection)
img = cv2.copyMakeBorder(grid, 4, 4, 4, 4,
cv2.BORDER_CONSTANT, value=0)
img = cv2.resize(img, (img.shape[1] * 10, img.shape[0] * 10),
interpolation=cv2.INTER_NEAREST)
cv2.imwrite('qr.png', img)

4. Decode the QR code:

import cv2
img = cv2.imread('qr.png', cv2.IMREAD_GRAYSCALE)
detector = cv2.QRCodeDetector()
data, points, _ = detector.detectAndDecode(img)
print(data) # -> HTB{REDACTED}

Tools Used

  • unzip — extract the password-protected challenge artifact
  • Python csv — parse the raw point-cloud data
  • collections.Counter — profile point distribution per label
  • numpy — build the pixel grid from point coordinates
  • opencv-python-headless (cv2) — render, border/scale, and decode the QR bitmap via QRCodeDetector

Key Learnings

  • Always verify handed artifacts before trusting them. The provided CSV was a 0-byte stub; the real data lived in a password-protected zip in the output directory. A quick ls -la / size check saves time later.
  • Profile structured data by label/class before diving into content. Splitting the point cloud by its label column immediately isolated the signal (label 1) from the noise (labels 0, 2, 3) — no need to eyeball all 4 classes’ raw values first.
  • Integer-grid coordinates in a spatial dataset are a strong steganography tell. Random noise coordinates are floats; a cluster of points snapped to a clean integer lattice (especially one matching a known encoding’s dimensions, like 25×25 for QR v2) is a signal that the positions themselves are the payload, not the values.
  • Collapse unused dimensions. With z clamped to a near-constant value across all label-1 points, the real information was entirely in the (x, y) plane — a hint to project down to 2D and render as an image.
  • QR codes need a quiet zone. Even a perfectly correct 25×25 module grid can fail to decode without a blank border around it — cv2.copyMakeBorder before detection avoids a false negative.

Flag: HTB{REDACTED}