← Back to Writeups
HTBEasyMisc

Delta Doodle

D3v0o0Nu119/2/20264 min read
#misc#htb#easy#delta_to_absolute_coordinate_a#pen_state_stroke_segmentation#y_axis_flip_for_text_readabili

Delta Doodle

Platform: Tjctf | Category: Misc | Type: Challenge | Difficulty: Easy | OS: NA | Author: D3v0o0Nu11 | Date: 2026-05-15 | Status: Solved Techniques: delta_to_absolute_coordinate_accumulation, pen_state_stroke_segmentation, y_axis_flip_for_text_readability

Summary

Task: CSV file with trackpad delta values (dx, dy) and pen_down state — reconstruct the drawn message. Solution: accumulate deltas into absolute coordinates, draw line segments only where pen_down=1, flip Y-axis for correct text orientation.

Recon

Port scan

bash
nmap -p- -sV -sC <TARGET> --min-rate 1000 -Pn
PortServiceVersionNotes
<PORT><SVC><VER><notes>

Enumeration highlights

  • Event: tjctf | ID: 20260515_tjctf_delta_doodle
  • Tags: pillow, data_visualization, delta_accumulation, csv_parsing, coordinate_reconstruction, trackpad, pen_down
  • Indicators: CSV with dx/dy delta columns, pen_down or pen_up column indicating stroke breaks, challenge mentions accumulate or delta in title/description, incremental motion data that must be summed to reconstruct a path
  • Source: 20260515_tjctf_delta_doodle.md

Foothold

Vulnerability / Misconfiguration

  1. delta_to_absolute_coordinate_accumulation
  2. pen_state_stroke_segmentation
  3. y_axis_flip_for_text_readability
bash
<command>

Exploitation

  • See original writeup content for detailed exploitation.

Privilege Escalation

Enumeration

bash
sudo -l
find / -perm -4000 2>/dev/null
getcap -r / 2>/dev/null
cat /etc/crontab
ps aux

Exploitation

  1. N/A for challenge-type writeup; see exploitation above.
  2. Flag obtained via challenge solve.
bash
<command>

Flags

FlagLocationValue
flagREDACTED

Key Takeaways / Lessons

  • delta_to_absolute_coordinate_accumulation
  • pen_state_stroke_segmentation
  • y_axis_flip_for_text_readability
  • Tags: pillow, data_visualization, delta_accumulation, csv_parsing, coordinate_reconstruction, trackpad, pen_down

Original Writeup

<details><summary>Click to expand original content</summary>

Description

i was calibrating the new AR airbrush for our maker faire booth by doodling the flag in midair. the headset, unfortunately, only logged the raw data. can you accumulate the motion trail and figure out what message we were trying to spray-paint?

English summary: Given a CSV file (trackpad_deltas.csv) containing 782 rows of incremental trackpad motion data with columns time_ms, dx, dy, and pen_down. The goal is to reconstruct the handwritten flag from the raw delta values.

Analysis

The CSV has 4 columns:

  • time_ms — timestamp in milliseconds (16ms intervals, ~60 Hz sampling)
  • dx, dy — floating-point incremental movement values
  • pen_down — 0 or 1, indicating whether the pen is drawing (1) or lifted (0)
time_ms,dx,dy,pen_down
40,2.500,15.008,0
56,0.000,-2.485,1
72,2.960,0.000,1
88,0.000,-1.117,1
104,-2.960,0.000,1

The challenge title "delta doodle" and the description phrase "accumulate the motion trail" directly hint at the technique: these are delta (incremental) values that must be summed (accumulated) into absolute positions to reconstruct the drawn path.

The pen_down field acts like a plotter pen — when 0, the pen is lifted (moving between characters without drawing), and when 1, the pen is drawing a stroke. This naturally segments the path into individual character strokes.

Solution

Step 1: Parse CSV and accumulate deltas

Read each row, maintain a running sum of dx and dy to convert deltas into absolute (x, y) coordinates.

Step 2: Draw the path

For each consecutive pair of points where both have pen_down=1, draw a line segment. Skip segments where the pen is lifted (stroke breaks between characters).

Step 3: Flip Y-axis

Screen/trackpad coordinates have Y increasing downward, but for readable text we need Y increasing upward (cartesian convention). Flip with sy = height - sy.

python
#!/usr/bin/env python3
import csv
from PIL import Image, ImageDraw

# Read deltas
rows = []
with open('trackpad_deltas.csv', 'r') as f:
    reader = csv.DictReader(f)
    for row in reader:
        rows.append({
            'time_ms': int(row['time_ms']),
            'dx': float(row['dx']),
            'dy': float(row['dy']),
            'pen_down': int(row['pen_down'])
        })

# Accumulate positions
positions = []
x, y = 0.0, 0.0
for row in rows:
    x += row['dx']
    y += row['dy']
    positions.append((x, y, row['pen_down']))

# Find bounds
all_x = [p[0] for p in positions]
all_y = [p[1] for p in positions]
min_x, max_x = min(all_x), max(all_x)
min_y, max_y = min(all_y), max(all_y)

margin = 20
scale = 5
width = int((max_x - min_x) * scale) + 2 * margin
height = int((max_y - min_y) * scale) + 2 * margin

img = Image.new('RGB', (width, height), 'white')
draw = ImageDraw.Draw(img)

# Draw segments where pen_down=1
prev = None
for i, (px, py, pen) in enumerate(positions):
    sx = (px - min_x) * scale + margin
    sy = height - ((py - min_y) * scale + margin)  # flip Y
    if pen == 1 and prev is not None and prev[2] == 1:
        draw.line([(prev[0], prev[1]), (sx, sy)], fill='black', width=3)
    prev = (sx, sy, pen)

img.save('flag.png')

The resulting image clearly shows the handwritten text: tjctf{REDACTED}

</details>

signed by XESXOR