Alan WestLearn 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.
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:
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.
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
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})")
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.
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()
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.
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"
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.
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.
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.
Because of course there are gotchas:
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
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.
Honestly? You probably wouldn't in production. But there are some genuinely interesting angles:
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.
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:
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.