How to Store 2 Bytes of Data in Your USB Mouse (Yes, Really)

# usb# hid# python# hardwarehacking
How to Store 2 Bytes of Data in Your USB Mouse (Yes, Really)Alan West

Learn how USB HID feature reports let you write and persist 2 bytes of data in your mouse's onboard memory using Python and hidapi.

Sometimes the best weekend projects start with the dumbest questions. Mine was: "Can I use my mouse as a storage device?" Not like plugging in a flash drive — I mean actually writing arbitrary data into the mouse's onboard memory and reading it back later.

Turns out, you can. Sort of. Let me walk you through how HID feature reports work, why your mouse has writable memory you never knew about, and how I managed to stash a whopping 2 bytes of data in a peripheral.

The Problem: HID Devices Have Hidden Depth

Most developers interact with USB mice through high-level abstractions. The OS handles everything — cursor movement, button clicks, scroll events. But underneath, your mouse speaks a protocol called HID (Human Interface Device), and that protocol supports more than just input reports.

HID defines three types of reports:

  • Input Reports — device → host (mouse movements, clicks)
  • Output Reports — host → device (think keyboard LEDs)
  • Feature Reports — bidirectional, used for device configuration

Feature reports are the interesting ones. They let you read and write configuration data on the device itself. And some mice — particularly ones with onboard profiles or DPI settings — expose writable feature reports that persist across reboots.

Step 1: Finding Your Device

First, you need to talk to the mouse at a low level. On Linux and macOS, the hidapi library is your friend. Python's hid package wraps it nicely.

pip install hidapi
Enter fullscreen mode Exit fullscreen mode

Then enumerate your connected HID devices:

import hid

# List all connected HID devices
for device in hid.enumerate():
    print(f"0x{device['vendor_id']:04x}:0x{device['product_id']:04x} "
          f"{device['product_string']} "
          f"(usage_page: 0x{device['usage_page']:04x})")
Enter fullscreen mode Exit fullscreen mode

You'll see multiple entries for the same mouse — HID devices often expose several interfaces (one for the mouse itself, one for vendor-specific stuff, sometimes one for a receiver). Look for a vendor-specific usage page — for Logitech devices that's often usage_page: 0xff00 or similar.

Step 2: Understanding Feature Reports

Here's where it gets fun. Feature reports have a report ID (the first byte) and a payload. The trick is figuring out which report IDs are writable and what the device does with the data.

You can try reading various feature report IDs to see what comes back:

import hid

# Open the device — replace with your vendor/product IDs
device = hid.device()
device.open(0x046d, 0xc077)  # example: Logitech mouse

# Try reading feature reports with different IDs
for report_id in range(0x00, 0x20):
    try:
        # get_feature_report(report_id, max_length)
        data = device.get_feature_report(report_id, 64)
        if data:
            print(f"Report 0x{report_id:02x}: {data.hex()}") 
    except Exception:
        pass  # device will NAK unsupported report IDs

device.close()
Enter fullscreen mode Exit fullscreen mode

Most report IDs will return nothing or throw an error. But some will return actual data — firmware versions, current DPI settings, battery status, or configuration bytes.

Step 3: Writing Data and Reading It Back

Once you find a writable feature report, you can send data to it with send_feature_report(). The first byte is always the report ID, followed by your payload:

import hid

def write_to_mouse(vendor_id, product_id, report_id, data_bytes):
    """Write arbitrary bytes to a mouse feature report."""
    device = hid.device()
    device.open(vendor_id, product_id)

    # Feature report: [report_id, byte1, byte2, ...]
    # Pad to expected report length (device-specific)
    payload = [report_id] + list(data_bytes)
    payload += [0x00] * (8 - len(payload))  # pad to 8 bytes

    device.send_feature_report(payload)
    print(f"Wrote {data_bytes.hex()} to report 0x{report_id:02x}")

    # Read it back to verify
    result = device.get_feature_report(report_id, 8)
    print(f"Read back: {bytes(result).hex()}")

    device.close()

# Store 2 bytes — maybe your initials in ASCII?
write_to_mouse(0x046d, 0xc077, 0x06, b'\x41\x57')  # "AW"
Enter fullscreen mode Exit fullscreen mode

The critical thing I learned: not every feature report that returns data is writable. Some are read-only (firmware info, hardware IDs). You need to experiment. When a write succeeds, read the report back — if your bytes are there, you've found writable storage.

How Many Bytes Can You Actually Store?

This depends entirely on the device. In my testing, I found that most consumer mice expose very limited writable space through feature reports — we're talking single-digit bytes. The 2-byte sweet spot I landed on was a single feature report with a fixed-length payload where only 2 bytes of the payload were actually user-writable; the rest were either read-only fields or got overwritten by the firmware.

Don't expect to store your SSH keys in there.

Step 4: Does It Persist?

This is the actual interesting question. I unplugged my mouse, waited, plugged it back in, and read the feature report again.

The data was still there.

The mouse stores certain configuration in non-volatile memory (EEPROM or flash), and if you happen to write to a feature report backed by that storage, your data survives power cycles. This is by design — it's how onboard DPI profiles and polling rate settings persist without software.

The Gotchas

Because of course there are gotchas:

  • Permissions: On Linux, you'll need udev rules or root access to open HID devices directly. On macOS, the OS may grab exclusive access to the mouse — you might need to use the IOKit framework instead of hidapi for some devices.
  • Bricking risk is low but real: Writing garbage to the wrong feature report could theoretically mess up your device's calibration or profile data. I tested on a cheap mouse I was willing to sacrifice. You should too.
  • Device-specific behavior: Every mouse model has different report descriptors. What works on one model won't work on another. You need to parse the HID report descriptor to really understand what each report does.
  • EEPROM wear: Non-volatile memory has limited write cycles. Don't put this in a loop writing thousands of times.

Parsing the Report Descriptor

If you want to do this properly instead of brute-forcing report IDs, you can read the HID report descriptor:

# On Linux, the report descriptor is exposed in sysfs
sudo cat /sys/bus/hid/devices/*/report_descriptor | xxd

# Or use usbhid-dump
sudo usbhid-dump -d 046d:c077 -e descriptor
Enter fullscreen mode Exit fullscreen mode

The descriptor tells you exactly which reports exist, their sizes, and their usage types. Tools like the USB HID report descriptor parser can decode the binary format into something human-readable.

Why Would You Actually Do This?

Honestly? You probably wouldn't in production. But there are some genuinely interesting angles:

  • Hardware tokens: Store a 2-byte identifier that ties a specific mouse to a user or workstation
  • Easter eggs: Hide a secret message in a peripheral for someone to find
  • Understanding USB: This is one of the best ways to actually learn how HID works at a protocol level
  • CTF challenges: I've seen similar concepts in security capture-the-flag competitions

The real value is in the journey. Digging into HID descriptors, understanding how feature reports work, learning that your peripherals are tiny computers with their own writable memory — that's the kind of knowledge that makes you a better developer even if you never store another byte in a mouse.

Prevention Tips (For the Security-Minded)

If the idea of writable memory in peripherals makes your security brain tingle, you're right to be concerned. Here's what to be aware of:

  • USB devices can have writable storage you don't control — this is a known vector in USB security research
  • Consider USB device allowlisting in enterprise environments
  • Monitor for unexpected HID device behavior using tools like USBGuard on Linux
  • Remember that "air-gapped" doesn't mean much when someone can walk data out on a mouse

Two bytes isn't much. But the principle scales — some gaming mice with onboard macro storage can hold kilobytes of arbitrary data. Food for thought.