
Configura il tuo Pi Zero 2W per trasformarlo in una BadUSB.
Una piattaforma di attacco BadUSB / HID programmabile basata su un Raspberry Pi Zero 2 W. Il Pi si presenta come un dispositivo USB composito (tastiera più un volume di archiviazione di massa di sola lettura opzionale) ed esegue payload in stile Ducky-Script sull'host a cui è collegato. Progettata per attività di red team autorizzate, test di penetrazione e CTF.
⚠️ Nota legale. Utilizzare solo su sistemi di propria proprietà o per i quali si dispone di esplicita autorizzazione scritta al test. L'accesso non autorizzato è illegale nella maggior parte delle giurisdizioni, e fingere di non saperlo non aiuta.
configured della UDC — nessun
payload spurio viene eseguito all'avvio.LAYOUT.IF / ELSE, WHILE, HOLD / RELEASE, INJECT_MOD,
generatori RANDOM_*, STRING_BLOCK / STRINGLN_BLOCK, DEFINE.ExecStop che smonta il gadget
in modo pulito. Nessuno stato configfs residuo, il riavvio del servizio funziona senza riavviare il sistema./dev/hidg0 — nessun nodo di dispositivo
scrivibile da tutti.+----------------+ USB cable +-------------+
| Raspberry Pi | ===================>>> | Host PC |
| Zero 2 W | (Pi emulates HID + | (target) |
| (this device) | optional drive) | |
+----------------+ +-------------+
|
| /home/pi/pi-badusb/
|
+-- badusb.service ----> monitor_and_run.py
|
| on `state == configured`:
v
run_payload.py
|
v
/dev/hidg0 (USB HID gadget)
badusb.service esegue gadget_setup.sh,
che usa configfs/libcomposite per comporre un gadget USB con una
funzione di tastiera HID e (opzionalmente) una LUN di archiviazione di massa basata su
un file immagine flat in /var/badusb/storage.img.monitor_and_run.py. Sonda
/sys/class/udc/<udc>/state e attende configured — lo
stato della specifica USB che indica che un host ha enumerato correttamente il
gadget. (Non usiamo l'esistenza di /dev/hidg0 come segnale:
tale condizione è vera nel momento in cui il gadget si lega alla UDC all'avvio,
indipendentemente dal fatto che qualcosa sia collegato.)run_payload.py,
che analizza payload.txt e scrive i report HID su /dev/hidg0.| Componente | Nota |
|---|---|
| Raspberry Pi Zero 2 W | Testato sulla revisione hardware del 2026-05. Anche il vecchio Pi Zero W con il driver BCM2835 dwc_otg funziona in linea di principio, ma questo README presuppone il 2 W con dwc2. |
| Cavo micro-USB verso USB-A | Oppure un adattatore con fattore di forma "chiavetta USB" che permette al Pi di collegarsi direttamente a una porta dell'host. |
| Opzionale: caricatore separato | Se si vuole alimentare il Pi da una sorgente non-host così che il listener possa avviarsi prima di essere collegato a un target (in caso contrario, la porta dell'host fornisce l'alimentazione). |
Il Pi Zero 2 W ha due porte micro-USB:
PWR IN — solo alimentazione, non espone le linee dati USB al blocco dwc2 OTG.USB — la porta dati OTG; è qui che ci si collega al target.Clona o copia il repository nel Pi, quindi esegui l'installer:
git clone http://your-gitea/admin/Pi-Zero-2W-Bad-USB.git /home/pi/pi-badusb
cd /home/pi/pi-badusb
sudo ./install.sh
sudo reboot
Dopo il riavvio, abilita e avvia il servizio:
sudo systemctl enable --now badusb.service
journalctl -u badusb -f
install.sh è idempotente — rieseguilo ogni volta che modifichi i
file del progetto. Esso:
/boot/firmware (Bookworm+) rispetto a /boot (più vecchio).dtoverlay=dwc2,dr_mode=otg sia attivo in un blocco [all]
in config.txt. Le impostazioni predefinite di Raspberry Pi Imager mettono questa riga
dentro un filtro [cm5] che non si applica al Pi Zero 2 W; l'installer
aggiunge un override contrassegnato da sentinella così che le riesecuzioni non lo
duplichino.modules-load=dwc2 sia presente in cmdline.txt.g_ether è ancora presente in cmdline.txt (ruba la
UDC a libcomposite e rompe la modalità gadget)./etc/systemd/system/badusb.service./etc/udev/rules.d/99-badusb-hidg.rules
così che sia scrivibile dal gruppo .# Start / stop / restart
sudo systemctl start badusb
sudo systemctl stop badusb
sudo systemctl restart badusb # safe to do while plugged in
# Watch live
journalctl -u badusb -f
# Disable autostart on boot
sudo systemctl disable badusb
# Tune timings (creates an override drop-in)
sudo systemctl edit badusb
# (paste an [Service] block with Environment="BADUSB_REARM_COOLDOWN_S=8" etc)
sudo systemctl restart badusb
Il servizio dipende da sys-kernel-config.mount e dalla presenza di una
UDC, quindi non può eseguire payload prima che il gadget sia effettivamente pronto.
Modificare il payload non richiede un riavvio — payload.txt viene
riletto a ogni collegamento.
payload.txt si trova nella directory di installazione. Il riferimento completo
ai comandi è in payload_commands.md.
REM Open Run dialog and type a greeting via Notepad
LAYOUT US
GUI r
DELAY 1500
STRING notepad
ENTER
DELAY 2500
STRINGLN Hello from the Pi Zero 2 W
VAR $USER="alice"
VAR $COUNT=0
WHILE $COUNT < 3
STRINGLN Hello $USER (iteration $COUNT)
VAR $COUNT = $COUNT + 1
END_WHILE
IF $USER == "alice"
STRINGLN matched
ELSE
STRINGLN missed
END_IF
Le espressioni matematiche in VAR passano attraverso un valutatore sicuro basato su un walker dell'ast —
niente nomi, niente chiamate, niente accesso agli attributi, solo letterali numerici e
+ - * / // % **.
HOLD SHIFT
STRINGLN this line is in capitals
RELEASE SHIFT
REM Hold Ctrl+Shift (0x01 + 0x02) and tap A
INJECT_MOD 0x03
STRING a
REM Release all modifiers
INJECT_MOD 0x00
RANDOM_LETTER 12 # 12 random mixed-case letters
RANDOM_NUMBER 6 # 6 random digits
RANDOM_SPECIAL 4 # 4 random ASCII symbols
LAYOUT UK # switch to UK ISO mappings for subsequent STRING/STRINGLN
STRING @ " # ~ £ \ | # types correctly on a UK-locale host
Inserisci un altro file in keymaps/ (accanto a us.py e uk.py) e
la direttiva LAYOUT <name> lo rileverà tramite importlib.
gadget_setup.sh)Questi possono essere sovrascritti per singola invocazione impostandoli
nell'ambiente quando si esegue gadget_setup.sh manualmente, oppure globalmente tramite
le direttive Environment= dell'unità.
monitor_and_run.py)Per sovrascrivere:
sudo systemctl edit badusb
# In the editor:
# [Service]
# Environment="BADUSB_REARM_COOLDOWN_S=8"
# Environment="BADUSB_MIN_INTER_FIRE_S=20"
sudo systemctl restart badusb
state della UDC, non /dev/hidg0La precedente implementazione verificava l'esistenza + la scrivibilità di /dev/hidg0
come segnale di "host collegato". Questo nodo di dispositivo viene creato nel momento in cui
il gadget si lega alla UDC all'avvio — molto prima che un host lo abbia effettivamente
enumerato. Quindi i payload venivano eseguiti immediatamente all'accensione, indipendentemente da
dove il Pi era collegato.
Il segnale affidabile è /sys/class/udc/<udc>/state, che riporta lo
stato del dispositivo secondo la specifica USB. Solo configured significa che l'host ha emesso
SetConfiguration(1) — il dispositivo ora è idoneo a inviare report HID.
Rilevare la disconnessione fisica sul Pi Zero 2 W è impossibile via software: la scheda non collega il rilevamento VBUS al blocco dwc2 OTG del SoC. Dopo uno scollegamento fisico:
/sys/class/udc/<udc>/state rimane su configured.current_speed rimane su high-speed.GOTGCTL rimane su 0x000d0000 (bit BSesVld impostato).Quindi, invece di attendere un segnale che non arriverà mai, il listener
provoca attivamente la disconnessione: dopo ogni payload, scrive "" nel
file configfs UDC del gadget (che il kernel interpreta come
slegamento), attende BADUSB_REARM_COOLDOWN_S, poi riscrive il nome della UDC
per rilegare. Il successivo collegamento dell'host produce una transizione pulita a configured
che il listener può rilevare.
Se l'operatore lascia il Pi collegato dopo un payload, il rilegamento fa sì che l'host ri-enumeri il gadget. Per prevenire un loop di esecuzioni incontrollato, entrano in gioco due protezioni:
BADUSB_MIN_INTER_FIRE_S — se una transizione a configured avviene
entro questa finestra dall'esecuzione precedente, sopprimila e slega
di nuovo. Il gadget cicla silenziosamente in background.BADUSB_MAX_FIRES_PER_MINUTE — limite massimo a finestra scorrevole. Oltre il
limite, il listener si mette in pausa per BADUSB_RATELIMIT_PAUSE_S e registra un
avviso.rm -rf su configfsI file di attributo di configfs gestiti dal kernel (bcdUSB, idVendor,
webusb/*, os_desc/*, …) non possono essere rimossi con rm(2) — il kernel
restituisce EPERM. Vengono rilasciati solo quando la loro directory padre
viene rimossa con rmdir. Sia gadget_setup.sh che teardown_gadget.sh percorrono
l'albero configfs nell'ordine canonico di libcomposite — solo rmdir,
mai rm sui file di attributo — e il kernel pulisce automaticamente il resto.
os.write e non file.writeScrivere una stringa vuota tramite open(path, "w").write("") non
invoca write(2) con zero byte — TextIOWrapper di CPython lo elide.
Per lo slegamento configfs (che il kernel interpreta da una scrittura di lunghezza zero
dopo la rimozione della nuova riga), usiamo direttamente os.write(fd, b"\n") così
che la syscall venga sempre emessa con almeno un byte.
Il parser Ducky ha una suite pytest di 34 test che viene eseguita contro un
MockHIDEngine (un sostituto in memoria del vero writer HID), quindi non
richiede né un Pi né hardware USB.
cd /home/pi/pi-badusb
python3 -m pytest tests/
La copertura include:
safe_eval_math che accetta operazioni aritmetiche e rifiuta nomi / chiamate /
accesso agli attributi / costanti stringa.evaluate_condition per confronti numerici e tra stringhe con distinzione tra maiuscole e minuscole.VAR con =, +=, -=, *=, /=.IF / ELSE / END_IF che seguono il ramo corretto.WHILE / END_WHILE per < e <=.cat /sys/class/udc/*/state — deve raggiungere configured quando
l'host enumera. Se rimane su not attached, l'host non sta
comunicando: prova un cavo diverso (alcuni sono solo per la ricarica) o una
porta host diversa.lsmod | grep dwc2 — deve essere caricato. Se c'è solo dwc_otg,
dtoverlay=dwc2,dr_mode=otg non si sta applicando; riesegui install.sh
e riavvia.journalctl -u badusb -f durante il collegamento — dovrebbe mostrare
Host attached. Running payload. entro ~2s dall'enumerazione dell'host.Questo era un bug reale, ora corretto. Se accade, sei tornato
a una build precedente a os.write. Assicurati che monitor_and_run.py corrisponda al
ramo main corrente (cerca os.write(fd, payload)).
Operation not permitted"Il gadget_setup.sh precedente alla correzione usava rm -rf su configfs. La versione
attuale usa teardown_gadget() — se vedi questi errori, hai
una copia vecchia. Ridistribuisci dal ramo main.
/dev/hidg0"La regola udev richiede un hot-plug per essere applicata, oppure sudo udevadm trigger
e un nuovo accesso così che l'utente pi acquisisca il gruppo plugdev.
g_ether durante l'installazione"Rimuovi g_ether da cmdline.txt; reclama la UDC prima che
libcomposite possa legarsi.
La dimensione dell'archiviazione di massa è impostata da BACKING_SIZE_MB e ha effetto solo al
primo avvio, quando viene creata l'immagine di supporto. Per ridimensionare:
sudo systemctl stop badusb
sudo rm /var/badusb/storage.img
sudo BACKING_SIZE_MB=256 /home/pi/pi-badusb/gadget_setup.sh
sudo systemctl start badusb
È a questo che servono BADUSB_MIN_INTER_FIRE_S e BADUSB_MAX_FIRES_PER_MINUTE.
Impostali più alti con systemctl edit badusb. Con le
impostazioni predefinite, un Pi lasciato collegato riesegue il payload ogni ~15s per il primo
minuto, poi si mette in pausa per 60s, poi riprende.
.
├── README.md This file
├── LICENSE MIT
├── install.sh Idempotent installer (firmware config, systemd, udev, plugdev)
├── gadget_setup.sh Composes the USB gadget via configfs/libcomposite
├── teardown_gadget.sh Canonical configfs teardown (wired as ExecStop)
├── reload_gadget.sh Manual UDC unbind/rebind helper
├── autorun.sh Legacy manual-launch wrapper (systemd is preferred)
├── monitor_and_run.py Listener: waits for host attach, runs payload, forces re-arm
├── run_payload.py Ducky-Script-style interpreter
├── payload.txt Your payload — edit freely; re-read on each plug-in
├── payload_commands.md Full command reference
├── etc/
│ ├── badusb.service systemd unit
│ └── 99-badusb-hidg.rules udev rule for /dev/hidg0 ownership
├── keymaps/
│ ├── __init__.py Dynamic layout loader
│ ├── us.py US ANSI (default)
│ └── uk.py UK ISO
└── tests/
├── __init__.py
├── conftest.py pytest path setup
└── test_parser.py 34 parser tests against a MockHIDEngine
libcomposite,
il driver del kernel dwc2.MIT — vedi LICENSE.
configured| Requisito | Motivo |
|---|
| Raspberry Pi OS (Debian Bookworm o Trixie, Lite 64-bit consigliato) | Lo script di installazione scrive in /boot/firmware/... su Bookworm+ e ripiega su /boot/... nelle immagini più vecchie. |
| Python 3 | Incluso con Pi OS. |
mkfs.vfat | Per formattare l'immagine di supporto dell'archiviazione di massa al primo avvio. Da saltare se si disabilita l'archiviazione di massa. |
| Accesso root per l'installazione | Modifica systemd, udev e /boot/firmware/config.txt. |
/dev/hidg0plugdevpi al gruppo plugdev./var/badusb/ per l'immagine di supporto dell'archiviazione di massa.| Variabile | Predefinito | Significato |
|---|
ENABLE_MASS_STORAGE | 1 | 0 per un gadget solo HID. |
BACKING_FILE | /var/badusb/storage.img | Immagine flat esposta all'host. |
BACKING_SIZE_MB | 64 | Creata al primo avvio se mancante. |
BACKING_LABEL | BADUSB | Etichetta del volume FAT. |
MASS_STORAGE_RO | 1 | Sola lettura per impostazione predefinita. |
| Variabile d'ambiente | Predefinito | Significato |
|---|
BADUSB_POST_PAYLOAD_FLUSH_S | 0.5 | Pausa dopo il payload prima dello slegamento per consentire lo scarico delle scritture HID. |
BADUSB_REARM_COOLDOWN_S | 5 | Per quanto tempo il gadget rimane invisibile all'host tra slegamento e rilegamento. |
BADUSB_MIN_INTER_FIRE_S | 10 | Secondi minimi tra due esecuzioni di payload; al di sotto di questo valore, l'esecuzione viene soppressa e il gadget viene nuovamente slegato. |
BADUSB_MAX_FIRES_PER_MINUTE | 6 | Limite massimo; oltre questo, pausa per BADUSB_RATELIMIT_PAUSE_S. |
BADUSB_RATELIMIT_PAUSE_S | 60 | Durata della pausa dopo l'attivazione del limite di frequenza. |
RANDOM_*; RANDOM_<unknown> no-op + avviso.INJECT_MOD attraverso le sequenze di tasti successive.HOLD SHIFT che capitalizza ogni carattere in STRINGLN abc.LAYOUT US rispetto a LAYOUT UK che producono report diversi per @ e
"; un layout sconosciuto ripiega su quello precedente.STRING_BLOCK che unisce le righe; STRINGLN_BLOCK che rispetta l'indentazione minima.