Passive USB keyboard sniffer for the Raspberry Pi Pico, with encrypted flash storage and multi-language CLI.
The entire USB capture — D+/D- signal sampling, packet decoding, and HID report filtering — is offloaded to the RP2040's PIO coprocessor. Keystrokes are encrypted with XTEA-CTR and persisted to a 1 MB circular flash log with CRC integrity protection. A serial command interface provides password-protected data access, real-time streaming, and CSV export.
⚠️ Legal DisclaimerThis project is provided strictly for educational purposes, authorized security research, and legitimate hardware auditing. It is intended to help security professionals, researchers, and enthusiasts understand USB HID protocol internals, PIO-based signal capture, and embedded cryptography.
You are solely responsible for ensuring that your use of this software complies with all applicable laws in your jurisdiction. Intercepting, recording, or monitoring keyboard input from devices you do not own — or without the explicit, informed consent of the device owner — is illegal in most jurisdictions and may violate statutes including, but not limited to:
- Brazil — Lei 12.737/2012, Art. 154-A (invasão de dispositivo informático)
- United States — Computer Fraud and Abuse Act (18 U.S.C. § 1030), Wiretap Act (18 U.S.C. § 2511)
- European Union — GDPR (Art. 5, 6), national implementations of the Computer Misuse Directive
- United Kingdom — Computer Misuse Act 1990
The author and contributors do not condone, encourage, or support the use of this tool for unauthorized surveillance, data theft, or any form of illegal activity. By using, downloading, or distributing this software, you acknowledge that:
- You will only use it on devices you own or have written authorization to test
- You assume all legal liability arising from your use
- The MIT License governs copyright and redistribution only — it does not grant permission to violate any law
If you are unsure whether your intended use is lawful, consult a qualified legal professional before proceeding.
- PIO-accelerated USB capture — passive high-impedance sampling of D+/D- via the PIO coprocessor; invisible to the host and device
- Dual-core architecture — Core 1 runs the sniffer at 120 MHz; Core 0 handles encryption, flash storage, and serial CLI
- XTEA-CTR encryption — every flash page is encrypted with a random 128-bit DEK wrapped by a PBKDF2-derived KEK
- Board-locked keys — the KDF mixes the RP2040's unique chip ID into the salt, binding decryption to the physical hardware
- Password protection — optional user password with brute-force lockout (progressive delay up to 30 s)
- Read PIN — optional 4-digit PIN (KDF-hashed) for casual access control without full password
- 1 MB circular log — ~254,000 events with automatic wraparound, CRC-16 per page, crash-safe metadata journal
- 4 bytes/keystroke — compact PackedEvent format (scancode + modifiers + delta timestamp)
- Multi-keyboard support — tracks up to 4 USB keyboards simultaneously with LRU eviction
- 10 keyboard layouts — US, UK, DE, FR, ES, IT, PT, ABNT2, Nordic, JP (runtime-switchable)
- 10 interface languages — EN, PT-BR, ES, FR, DE, IT, JA, ZH, KO, RU (runtime-switchable, persisted)
- Real-time streaming —
LIVEmode prints decoded keystrokes as they arrive;LIVE CLEANsuppresses modifier tags - CSV export — timestamped, filterable (
EXPORT last_N), with session markers and optional Unix epoch anchoring - Firmware integrity — CRC-32 of firmware region verified on every boot; mismatch warns of reflash/tampering
- Watchdog + heartbeat — 8 s watchdog on Core 0; Core 1 heartbeat monitored with 500 ms pause timeout
- Flash wear protection — metadata persist interval tuned for >2 year endurance under continuous typing
| Component | Description |
|---|---|
| Raspberry Pi Pico | RP2040-based board (2 MB flash) |
| USB keyboard | Any standard USB HID keyboard |
| 100 Ω resistors (×2) | Series protection on D+/D- lines |
Pico GP16 ── 100Ω ── USB D+ (green wire)
Pico GP17 ── 100Ω ── USB D- (white wire)
Pico GND ────────── USB GND (black wire)
Do NOT connect VBUS (5 V red wire) to the Pico.
The sniffer taps the USB bus passively — no signal injection, no enumeration, no VBUS power.
[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
framework = arduino
board_build.core = earlephilhower
board_build.f_cpu = 120000000L
monitor_speed = 115200
lib_deps =
USBSnifferPIO_RP2040
adafruit/Adafruit NeoPixel@^1.12.0
build_flags = -O2 -Wall -DPICO_FLASH_SIZE_BYTES=2097152
upload_protocol = picotoolThe Arduino IDE is a fully supported alternative to PlatformIO. Follow the steps below to set up your environment, configure the board, and flash the firmware.
Important: This project requires the Earle Philhower arduino-pico core — the official Arduino Mbed OS core for RP2040 does not support PIO (Programmable I/O) peripherals. Do not install the "Arduino Mbed OS RP2040 Boards" package.
Download and install Arduino IDE 2.x (recommended) from arduino.cc. Version 1.8.x also works, but the screenshots and menus reference the 2.x UI.
- Open Arduino IDE and go to File → Preferences (or Arduino IDE → Settings on macOS).
- In the Additional Boards Manager URLs field, paste the following URL:
If you already have other URLs in this field, click the icon on the right and add the URL on a new line.
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json - Click OK to close Preferences.
- Open Tools → Board → Boards Manager (or click the board icon in the left sidebar).
- Type "Raspberry Pi Pico" in the search box.
- Find "Raspberry Pi Pico/RP2040" by Earle F. Philhower, III — verify the author name carefully.
- Click Install (latest version recommended).
- Wait for the installation to complete — it downloads the compiler toolchain (~200 MB).
- Go to Tools → Board → Raspberry Pi Pico/RP2040 and select Raspberry Pi Pico.
- Now configure the following board settings in the Tools menu. Bold values are required; others can be left at default:
| Setting | Value | Notes |
|---|---|---|
| Board | Raspberry Pi Pico | |
| CPU Speed | 120 MHz | Required for stable PIO USB sniffing |
| Flash Size | 2MB | Must match -DPICO_FLASH_SIZE_BYTES=2097152 |
| USB Stack | Pico SDK | Default — works correctly |
| Debug Level | None | Use "USB (HWCDC)" for serial-only monitoring |
| Optimize | Optimize (-O2) | Important for PIO timing deadlines |
| C++ Exceptions | Disabled | Keeps binary size small |
| IP/Bluetooth Stack | IPv4 Only | Not used by the sniffer |
| Debug Port | Disabled |
⚠️ If CPU Speed is left at the default 133 MHz, the PIO state machine timing will be off and USB capture will produce malformed packets. 120 MHz is mandatory.
- Open Tools → Manage Libraries (or the library icon in the left sidebar).
- Install each of these libraries by searching and clicking Install:
| Library | Author | Version | Purpose |
|---|---|---|---|
| USBSnifferPIO_RP2040 | angeloINTJ | latest | PIO-based USB D+/D- signal capture engine |
| Adafruit NeoPixel | Adafruit | ≥ 1.12.0 | Status LED feedback on GP25 |
- Close the Library Manager when done.
- Clone or download this repository to your computer:
git clone https://github.com/angeloINTJ/KeyloggerSniffer_PIO.git
- In Arduino IDE, go to File → Open and select
KeyloggerSniffer_PIO.inofrom the cloned directory. - Arduino IDE will open the
.inofile and automatically pull in all companion files (.cpp,.h) from the same folder. You should see multiple tabs in the IDE — the main sketch plusCryptoEngine.cpp,SnifferWorker.cpp,KeylogConfig.h, and the keyboard layout headers.
Note: All project files must remain in the same directory as
KeyloggerSniffer_PIO.ino. Arduino IDE compiles everything from that folder — do not move individual files elsewhere.
Before the first build, you must generate a cryptographic seed file. This creates BuildSeed.h with a random 16-byte XOR-obfuscated seed used to derive the initial encryption password.
Open a terminal in the project directory and run:
python3 generate_build_seed.pyYou should see confirmation that BuildSeed.h was written. Run this command once per installation — if you re-run it, all previously encrypted flash data will become unreadable (a new key pair is generated).
If you don't have Python 3 installed, install it from python.org or your system package manager (
sudo apt install python3on Debian/Ubuntu).
- Disconnect the Pico from USB if already connected.
- Hold down the BOOTSEL button (the white button on the Pico board).
- While holding BOOTSEL, connect the Pico to your computer via USB.
- Release BOOTSEL after the USB cable is connected.
- The Pico should appear as a USB mass storage device called "RPI-RP2" (or a new COM port if using picotool).
If the Pico does not appear, try a different USB cable — some cables are power-only and do not carry data.
- In Arduino IDE, click the Verify button (✓ checkmark) first to compile without uploading. This catches errors before attempting to flash.
- Once compilation succeeds, click Upload (→ arrow).
- If the Pico is in BOOTSEL/mass-storage mode, the IDE will flash directly. If the Pico is already running, the IDE will attempt to auto-reset it via the USB serial port (1200 bps touch).
- Wait for "Done uploading" in the status bar. The Pico will reboot automatically and the NeoPixel LED should light up orange (booting).
Compilation time: The first build takes 2–4 minutes because the IDE compiles the entire arduino-pico core. Subsequent builds are incremental and much faster (~10–30 seconds).
- Go to Tools → Port and select the COM port that appeared after uploading (e.g.,
COM3on Windows,/dev/ttyACM0on Linux,/dev/cu.usbmodem*on macOS). - Open Tools → Serial Monitor (or press
Ctrl+Shift+M). - Set the baud rate to 115200 at the bottom of the Serial Monitor window.
- Press the Pico's RESET button (or disconnect/reconnect USB) to reboot. You should see:
==================================================== USB Keyboard Sniffer v1.0.0 (Encrypted, Passive) ==================================================== Flash log : 0 events Build password: XXXX-XXXX-XXXX Keyboard layout: US | Language: EN Type HELP for available commands.
Save the build password! It is derived from the random seed and the Pico's unique chip ID — it cannot be recovered from the firmware binary. Store it somewhere safe.
With the firmware running and serial monitor open:
- Disconnect power from the Pico before wiring.
- Connect the USB keyboard tap wires according to the Wiring Diagram.
- Reconnect power to the Pico.
- Type on the keyboard — you should see the NeoPixel glow green (capturing, unlocked).
- Use
DUMPin the Serial Monitor to decrypt and review captured keystrokes.
Compile error: "USBSnifferPIO_RP2040.h: No such file or directory"
The library was not installed or the IDE cannot find it. Re-install USBSnifferPIO_RP2040 via the Library Manager and restart the IDE. Verify it appears under Sketch → Include Library → Contributed Libraries.
Compile error: "PICO_FLASH_SIZE_BYTES" undeclared
The flash size define was not passed to the compiler. Go to Tools → Flash Size and ensure 2MB is selected (not "2MB (no FS)"). If the issue persists, the arduino-pico core version may be outdated — update it in the Boards Manager.
Upload fails: "No drive to deploy" or "Cannot find RPI-RP2"
The Pico is not in BOOTSEL mode. Disconnect the USB cable, hold the BOOTSEL button, reconnect USB, then release BOOTSEL. The Pico should appear as a USB drive. Try uploading again. If it still fails, try a different USB port or cable.
Serial Monitor shows garbage or nothing
Ensure the baud rate is set to 115200 in the Serial Monitor. If the port menu shows no ports, install the USB Serial driver for your OS (Windows users may need to install drivers via Zadig).
PIO capture produces malformed/missing packets
CPU speed is likely wrong. Go to Tools → CPU Speed and verify 120 MHz is selected. Also confirm Optimize is set to Optimize (-O2). Recompile and re-upload after changing these settings.
NeoPixel LED does not light up
Check the Adafruit NeoPixel library is installed (version ≥ 1.12.0). If installed but no light, the GP25 pin may be damaged or the LED may require a level shifter on some Pico clones. The sniffer works without the LED — it is purely cosmetic feedback.
On first power-up, the system auto-generates a random encryption key and prints a build password on Serial. This password is derived from the board's unique chip ID and cannot be recovered from the firmware binary alone.
| Command | Description |
|---|---|
DUMP |
Decrypt and display all events as text |
HEX |
Decrypt and display as hex |
EXPORT [N] |
CSV export (last N events optional) |
RAWDUMP |
Raw encrypted pages for offline decryption |
LOCK <pwd> |
Set password (enables locked mode) |
RPIN SET <pin> |
Set 4-digit read PIN |
RPIN CLEAR <pin> |
Remove read PIN |
LAYOUT [name] |
Show or change keyboard layout |
LANG [code] |
Show or change interface language |
LIVE [CLEAN] |
Toggle real-time keystroke streaming |
TIME <epoch> |
Set Unix timestamp for CSV anchoring |
DEL CONFIRM |
Erase all data and reset keys |
STAT |
System statistics and diagnostics |
HELP |
Show available commands |
STOP |
Cancel an active dump/export operation |
All data commands require the password as an argument (e.g. DUMP <pwd>). Additional commands:
| Command | Description |
|---|---|
LOGIN <pwd> |
Unlock the system for capture |
PASS <old> <new> |
Change password (instant key re-wrap) |
UNLOCK <pwd> |
Remove password and return to open mode |
build_seed + board_id ──► KDF ──► build_password
│
user_password + salt + board_id ──► PBKDF2 (12000 rounds)
│
┌────┴────┐
│ KEK │ (128-bit)
└────┬────┘
│
XTEA-ECB encrypt
│
┌────┴────┐
│ DEK │ (128-bit, random)
└────┬────┘
│
XTEA-CTR per page
│
Flash Log
- DEK is generated from hardware RNG (ROSC + von Neumann debiasing + timer jitter) and never touches flash in plaintext
- KEK is derived from the password via PBKDF2 with a dual CBC-MAC PRF, producing 128 bits per iteration
- Board ID (8 bytes, unique per chip) is mixed into the KDF salt, binding the key to the physical hardware
- Nonce floor advances with each metadata persist, detecting flash rollback attacks
- Login lockout enforces progressive delays after 3 failed attempts (2 s → 4 s → 8 s → ... → 30 s)
┌─────────────────────┐ ┌──────────────────────┐
│ Core 1 (Sniffer) │ │ Core 0 (Storage) │
│ │ │ │
│ PIO + DMA capture │ │ Ring buffer consumer │
│ HID report filter │ │ XTEA-CTR encryption │
│ Multi-keyboard diff │ │ Flash circular log │
│ LED status tracking │ │ Metadata journal │
│ Heartbeat timestamp │ │ Serial CLI + i18n │
│ │ │ Watchdog timer │
└────────┬────────────┘ └──────────┬───────────┘
│ Ring Buffer (512 slots) │
└────────────────────────────┘
┌──────────────────────┐ 0x10000000 (XIP_BASE)
│ Firmware (.text) │
├──────────────────────┤ FLASH_CRYPTO_START
│ Crypto Sector (4K) │ Key envelope + salt
├──────────────────────┤ FLASH_META_START
│ Metadata Sector (4K)│ Crash-safe state journal
├──────────────────────┤ FLASH_LOG_START
│ Event Log (1 MB) │ Encrypted circular pages
└──────────────────────┘ End of 2 MB flash
| Color | Meaning |
|---|---|
| Green | Capturing (unlocked) |
| Orange | Booting / awaiting LOGIN |
| Blue | Text dump in progress |
| Cyan | Hex dump in progress |
| Red | Flash write |
| Magenta | Flash erase |
| White | Crypto operation |
| Yellow | Core 1 handshake timeout |
Contributions are welcome! Please read CONTRIBUTING.md before submitting a pull request.
This project is licensed under the MIT License — see LICENSE for details.
- USBSnifferPIO_RP2040 for the PIO-based USB packet capture engine
- Adafruit NeoPixel for LED status feedback
- arduino-pico for the RP2040 Arduino core
- Raspberry Pi Foundation for the RP2040 PIO architecture
- DHT22PIO_RP2040 — PIO-accelerated DHT22 library by the same author
- OneWirePIO_RP2040 — PIO-accelerated DS18B20 library by the same author