Skip to main content

Command Palette

Search for a command to run...

Weird Movements - dalCTF

Updated
•4 min read•View as Markdown
Weird Movements - dalCTF
Y
I write detailed writeups on HackTheBox, PicoCTF and other CTF challenges. Passionate about web exploitation, Active Directory attacks and ethical hacking

Category: Forensics Flag: dalctf{h3h3_i_s2_p41nt}


Overview

We're given a packet capture. The capture turns out to be a Linux USB HID (mouse) trace — the user held left-click and physically drew the flag on screen. The drawn text is ROT13-encoded, giving one extra layer of obfuscation.


Reconnaissance

file capture.pcap
# pcap capture file, microsecond ts (little-endian) - version 2.4
# (Memory-mapped Linux USB, capture length 134217728)

The link type is 220 (LINKTYPE_USB_LINUX_MMAPPED) — Linux USB captured via usbmon. Standard tools like Wireshark will decode this, but scapy treats it as raw since it doesn't recognise link type 220 natively.


Packet Structure

The capture contains 8618 packets. Parsing with scapy using the raw usbmon header layout:

from scapy.all import rdpcap
import struct

packets = rdpcap("capture.pcap")

Each raw packet is a usbmon_packet struct (48 bytes) followed by the HID data payload. Key fields in the header:

Offset Field Value
8 type S=submit, C=complete
9 transfer type 1 = interrupt
10 endpoint 0x81 = EP1 IN
36 data length 4 bytes LE

Filtering for interrupt IN packets with data gives 4254 HID reports — all mouse events.


HID Payload Layout

Each report is 20 bytes. The first 16 bytes are usbmon internal metadata (constant across packets). The actual HID data occupies bytes 16–19:

Byte Field
16 buttons (0 = none, 1 = left click)
17 X delta (signed 8-bit)
18 Y delta (signed 8-bit)
19 scroll wheel

Extracting the Mouse Path

events = []
for pkt in packets:
    raw = pkt.load
    if len(raw) < 48:
        continue
    if raw[9] == 1 and (raw[10] & 0x80):          # interrupt IN
        data_len = struct.unpack_from("<I", raw, 36)[0]
        if data_len > 0 and len(raw) > 48:
            hid = raw[48:]
            if len(hid) >= 20:
                btn = hid[16]
                dx  = struct.unpack_from("b", hid, 17)[0]
                dy  = struct.unpack_from("b", hid, 18)[0]
                events.append((dx, dy, btn))

Reconstructing absolute position by accumulating deltas:

x, y = 0, 0
path = []
for dx, dy, btn in events:
    x += dx
    y += dy
    path.append((x, y, btn))
  • Total points: 4254

  • Clicked points (btn=1): 2783

  • X range: −1021 to 340

  • Y range: −197 to 99


Rendering the Drawing

Only points where btn == 1 (left button held) are drawn. Connecting consecutive clicked points as line segments produces clean cursive-style strokes:

from PIL import Image, ImageDraw

scale = 4
margin = 40
xmin, ymin = min(xs), min(ys)
w = (max(xs) - xmin + 1) * scale + 2*margin
h = (max(ys) - ymin + 1) * scale + 2*margin

img = Image.new("RGB", (w, h), "white")
draw = ImageDraw.Draw(img)

prev = None
for px, py, btn in path:
    rx = (px - xmin) * scale + margin
    ry = (py - ymin) * scale + margin
    if btn == 1:
        if prev:
            draw.line([prev, (rx, ry)], fill="black", width=4)
        prev = (rx, ry)
    else:
        prev = None

The rendered image reveals handwritten text: QNYPGS{U3U3_V_F2_C41AG}


ROT13 Decode

The drawn text is ROT13-encoded:

QNYPGS{U3U3_V_F2_C41AG}
      ↓ ROT13
dalctf{h3h3_i_s2_p41nt}

Only alphabetic characters are shifted; digits and symbols pass through unchanged.


Flag

dalctf{h3h3_i_s2_p41nt}

Key Takeaways

  • USB pcap with link type 220 is Linux usbmon mmapped format — file identifies it correctly but scapy needs manual parsing.

  • The usbmon header is 48 bytes; HID data follows immediately. For this mouse, the actual report is at bytes 16–19 of the data section (not byte 0), due to usbmon internal padding.

  • Mouse drawing challenges require reconstructing absolute position from cumulative relative deltas, then rendering clicked-only segments as strokes.

  • The extra ROT13 layer is a lightweight encode-on-top that's easy to miss if you're only looking for standard flag format wrappers.


Tools Used

Tool Purpose
file Identify pcap link type (USB Linux mmapped)
scapy Parse raw usbmon packets
struct Unpack signed 8-bit deltas and little-endian fields
Pillow Render mouse path as PNG
ROT13 Decode drawn ciphertext to flag