Know the attack, stop the attack

Three ways in through peripherals

With virtual USB devices on dummy_hcd, you see unknown USB drives held and scanned with ClamAV, a changed fingerprint held, a fake keyboard grabbed, and then audit the Bluetooth settings.

  • Level: Basics
  • About 25 min
  • 2026-11-12

#Three ways in through peripherals

The devices on your desk can be a way in. This module triggers three well-known routes with practice devices.

RouteExample
USB driveA USB drive someone found carries malware
A device posing as a keyboardSomething that looks like a USB drive types commands as a keyboard (BadUSB)
Bluetooth keyboardSomeone nearby poses as a keyboard that was never paired

The USB guards are features of the client edition (the roamswitch package). USB storage control and BadUSB prevention are checked here with virtual USB devices. The Bluetooth config check, Bluetooth control and check, also runs on Server Edition.

Practise on a throwaway VM

The client edition cannot sit on the same machine as Server Edition: installing it removes Server Edition. On a network it does not know, it switches to Away Protection (Lockdown) and stops sharing services such as SSH. If you reached the VM over SSH, the connection drops. Work from the VM's console. Times in the output are UTC.

#Make practice USB devices

You cannot plug real USB devices into a VM. Linux's dummy_hcd module helps here: it creates a virtual USB port inside the same machine. Devices that behave like a USB drive or a keyboard (gadgets) are attached to it. To the kernel they look the same as real USB devices.

Set up the virtual port and the practice devices

This loads a kernel module and installs packages (this changes the system). Use a throwaway Ubuntu 24.04 VM.

Ubuntu's kernel does not ship dummy_hcd, so build just that one module from the kernel source. Install ClamAV and the client edition too (the repository is registered as in Installing Linux Server).

sudo apt install build-essential linux-headers-$(uname -r) dosfstools roamswitch clamav clamav-daemon
mkdir -p ~/dhcd && cd ~/dhcd
curl -fsSL -o dummy_hcd.c https://raw.githubusercontent.com/torvalds/linux/v$(uname -r | cut -d. -f1,2)/drivers/usb/gadget/udc/dummy_hcd.c
echo "obj-m := dummy_hcd.o" > Makefile
make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo mkdir -p /opt/fakeusb && sudo cp dummy_hcd.ko /opt/fakeusb/
sudo systemctl stop clamav-freshclam && sudo freshclam && sudo systemctl restart clamav-daemon

Make the contents of the practice USB drive. EICAR is a public, harmless string used to test antivirus software.

sudo truncate -s 64M /opt/fakeusb/stick.img
sudo mkfs.vfat -n PRACTICE /opt/fakeusb/stick.img
sudo mkdir -p /mnt/img /mnt/stick && sudo mount -o loop /opt/fakeusb/stick.img /mnt/img
printf '%s' 'X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*' | sudo tee /mnt/img/eicar.com >/dev/null
echo hello | sudo tee /mnt/img/readme.txt >/dev/null
sudo umount /mnt/img

This script plugs and unplugs the devices. Put it at /usr/local/bin/fakeusb and make it executable with sudo chmod 755. The USB IDs are the practice IDs 1209:0001 (USB drive) and 1209:0002 (keyboard).

/usr/local/bin/fakeusb
#!/bin/sh
# fakeusb storage|keyboard plug|unplug : a virtual USB device on dummy_hcd (practice only)
set -e
G=/sys/kernel/config/usb_gadget
kind=$1; act=$2
modprobe libcomposite
mountpoint -q /sys/kernel/config || mount -t configfs none /sys/kernel/config
lsmod | grep -q '^dummy_hcd' || insmod /opt/fakeusb/dummy_hcd.ko
case "$kind" in
  storage) d=$G/practice_stick; pid=0x0001; prod="Practice Stick"; modprobe usb_f_mass_storage;;
  keyboard) d=$G/practice_kbd; pid=0x0002; prod="Practice Keyboard"; modprobe usb_f_hid;;
  *) echo "usage: fakeusb storage|keyboard plug|unplug"; exit 2;;
esac
if [ "$act" = unplug ]; then
  [ -e $d/UDC ] && echo "" > $d/UDC 2>/dev/null || true
  echo unplugged; exit 0
fi
if [ ! -d $d ]; then
  mkdir -p $d && cd $d
  echo 0x1209 > idVendor; echo $pid > idProduct; echo 0x0100 > bcdDevice; echo 0x0200 > bcdUSB
  mkdir -p strings/0x409
  echo "Practice" > strings/0x409/manufacturer; echo "$prod" > strings/0x409/product; echo "PRACTICE0001" > strings/0x409/serialnumber
  mkdir -p configs/c.1/strings/0x409; echo "practice" > configs/c.1/strings/0x409/configuration; echo 100 > configs/c.1/MaxPower
  if [ "$kind" = storage ]; then
    mkdir -p functions/mass_storage.0
    echo 1 > functions/mass_storage.0/lun.0/removable
    echo /opt/fakeusb/stick.img > functions/mass_storage.0/lun.0/file
    ln -s functions/mass_storage.0 configs/c.1/
  else
    mkdir -p functions/hid.usb0; cd functions/hid.usb0
    echo 1 > protocol; echo 1 > subclass; echo 8 > report_length
    python3 -c 'import sys; sys.stdout.buffer.write(bytes.fromhex("05010906a101050719e029e71500250175019508810295017508810395057501050819012905910295017503910395067508150025650507190029658100c0"))' > report_desc
    cd $d; ln -s functions/hid.usb0 configs/c.1/
  fi
fi
cd $d
# one UDC: unbind the other gadget first
for o in $G/*; do [ "$o" != "$d" ] && [ -n "$(cat $o/UDC 2>/dev/null)" ] && echo "" > $o/UDC; done
echo dummy_udc.0 > UDC
echo plugged

Plug one in.

sudo fakeusb storage plug
sleep 3
lsusb | grep 1209
Output
plugged
Bus 003 Device 002: ID 1209:0001 Generic pid.codes Test PID

The practice USB drive shows up in lsusb. Right after plugging in, it may not be listed yet, so wait 3 seconds first. To unplug it, run sudo fakeusb storage unplug.

#Hold an unknown USB drive, then allow and scan it

On a network it has not registered, the client edition switches to Away Protection (Lockdown). In Lockdown, unapproved USB drives and keyboards are held even when the USB guard settings are off. It also sets the kernel's authorized_default to 0, so new USB devices come up unusable until they are allowed.

Approved devices go into allowed_usb_devices in ~/.config/roamswitch/config.json. Normally you add them from the app. This exercise does not use the desktop app, so the file is written directly.

Plug in the practice USB drive in Lockdown

This writes the config file and restarts the service (this changes the system).

First, put the keyboard you use (05ac:8105 on this VM) on the allow-list. A keyboard that is not on the list is held even if it was already attached: it could have been swapped in before a reboot (a note in the code).

mkdir -p ~/.config/roamswitch
cat > ~/.config/roamswitch/config.json <<'J'
{
  "language": "en",
  "allowed_usb_devices": [
    {"deviceIdentifier": "05ac:8105", "displayName": "Virtual USB Keyboard", "dateAdded": "2026-11-05 09:00"}
  ]
}
J
chmod 600 ~/.config/roamswitch/config.json
sudo systemctl restart roamswitch

Plug in the practice USB drive and look.

sudo fakeusb storage plug
sleep 3
sudo dmesg | tail -1
cat /run/roamswitch/state.json
cat /sys/bus/usb/devices/usb3/authorized_default
cat /sys/bus/usb/devices/3-1/authorized
lsblk -S
roamswitch notifications | head -8
Output excerpt
plugged
[  168.746689] usb 3-1: Device is not authorized for usage
{"active_level":"lockdown","air_gap":false,"fanotify_ready":true,"network_trusted":false,"usb_zero_trust":true}
0
0
=====================================================
  🔔 Notification History (past 7 days)
=====================================================

  2026-11-12T02:25:03.019626434+00:00
  ⏸ Unauthorized USB Storage Held
    'Practice Practice Stick (VID:1209 PID:0001)' was held due to Away Protection (Lockdown). Allow it in the approval dialog to use it.

How to read it:

  • active_level is lockdown, because the network is not registered
  • authorized is 0: the kernel found the device but has not made it usable
  • lsblk -S shows nothing. No disk appears, so nothing can be mounted

The pending request is in /run/roamswitch/approvals.json. The app reads it and shows Allow and Deny.

python3 -c "
import json
for a in json.load(open('/run/roamswitch/approvals.json')): print(a['kind'], a['device_id']); print(a['title']); print(a['body'])"
Output
usb_storage 1209:0001
Allow this unregistered USB drive?
"Practice Practice Stick" (VID:1209 PID:0001) was connected. Details: Practice
It isn't on the allow-list, so it's held un-mounted for now. "Allow" makes it usable and adds it to the allow-list. If you don't recognize it, choose Deny.

Putting the drive on the allow-list releases it. When a USB drive is mounted, ClamAV reads it in a read-only scan (usb_mount_scan_enabled, on by default). A match raises a critical notification. Nothing is deleted or ejected.

Allow it, mount it, read the scan result

This edits the config file (this changes the system).

Make a small function that adds to the allow-list, and add the practice USB drive. Config changes take effect in a few seconds without a restart.

allow_usb() { python3 - "$1" "$2" <<'P'
import json,os,sys
p=os.path.expanduser('~/.config/roamswitch/config.json')
c=json.load(open(p))
c['allowed_usb_devices'].append({"deviceIdentifier":sys.argv[1],"displayName":sys.argv[2],"dateAdded":"2026-11-05 09:40"})
json.dump(c,open(p,'w'),ensure_ascii=False,indent=2)
P
}
allow_usb 1209:0001 "Practice Stick"
sleep 6
cat /sys/bus/usb/devices/3-1/authorized
lsblk -S
sudo mount -o ro /dev/sda /mnt/stick && ls /mnt/stick
sleep 15
roamswitch notifications | sed -n 4,9p
Output excerpt
1
NAME HCTL       TYPE VENDOR   MODEL             REV SERIAL       TRAN
sda  0:0:0:0    disk Linux    File-Stor Gadget 0700 PRACTICE0001 usb
eicar.com
readme.txt

  2026-11-12T02:25:19.295712309+00:00
  🦠 Malware found on a USB drive
    ClamAV found 1 infected file on /mnt/stick (/dev/sda).
      eicar.com (Eicar-Test-Signature)
    Do not open it; unplug the drive (notification only; nothing was deleted or ejected).

authorized turned 1 and sda appeared. Within a second of the mount, ClamAV found EICAR. eicar.com is still there. The allow-list identifies a device by its IDs (VID:PID), such as 1209:0001. Any device can claim those IDs. So the device's fingerprint at approval time (built from the manufacturer, product name, serial number and more) is remembered too. The fingerprints are in /var/lib/roamswitch/usb_fingerprints.json.

Change only the serial number of the practice USB drive and plug it in again.

sudo umount /mnt/stick
sudo fakeusb storage unplug
sleep 5
echo PRACTICE9999 | sudo tee /sys/kernel/config/usb_gadget/practice_stick/strings/0x409/serialnumber
sudo fakeusb storage plug
sleep 6
cat /sys/bus/usb/devices/3-1/authorized
sudo journalctl -u roamswitch --since -15s -o cat | grep fingerprint
roamswitch notifications | sed -n 4,7p
python3 -c "
import json
for a in json.load(open('/run/roamswitch/approvals.json')): print(a['kind'], a['device_id']); print(a['title']); print(a['body'])"
Output excerpt
PRACTICE9999
plugged
0
2026-11-12T02:25:38.996822Z  WARN roamswitch_daemon::sentinel: ⚠️ [BadUSB] 1209:0001 is allow-listed but a device with a different descriptor fingerprint is attached — treating it as unapproved

  2026-11-12T02:25:39.021386116+00:00
  A registered USB drive's identity has changed
    'Practice Practice Stick (VID:1209 PID:0001)' has the VID:PID of a USB drive on the allow-list, but its internal identity (descriptors) differs from the one registered, so it was held. Allow it in the approval dialog only if you can confirm it is the same device.
usb_storage 1209:0001
A registered USB drive's identity has changed
"Practice Practice Stick" (VID:1209 PID:0001) has the VID:PID of a USB drive on the allow-list, but its internal identity (descriptors) differs from the one registered. Details: Practice
It may be a different device from the one you registered, so it is held un-mounted. Choose Allow only if you can confirm it is the same device. If you don't recognize it, choose Deny.

The IDs are on the allow-list, but the fingerprint differs, so the drive is held as unapproved. Even when it is plugged back in about 5 seconds after unplugging, it gets a notification. Both the notification and the approval request say that the identity differs from the registered one, and ask you to allow it only if you can confirm it is the same device. The journal line is in English whatever the display language. The fingerprint does not tell apart a device that copies every descriptor (a note in the code).

#Stop a device that poses as a keyboard

In BadUSB, a device that looks like a USB drive or similar announces itself as a keyboard and types commands. It types right after it is plugged in, faster than a person can.

When an unapproved keyboard is attached, RoamSwitch takes its input for itself (an exclusive evdev grab). Its keystrokes reach no other program. The device keeps its power, so a mouse on the same receiver keeps working. If no other usable keyboard is left, it only notifies and does not grab, so you are not locked out (a note in the code).

Type from the practice keyboard with a script

/usr/local/bin/typekeys.py sends echo practice and Enter from the practice keyboard, one key every 5 ms. /usr/local/bin/readkeys.py reads the keyboard's input alongside and counts the keys that reach it. Make both executable with sudo chmod 755.

/usr/local/bin/typekeys.py
#!/usr/bin/env python3
# typekeys.py: send "echo practice" + Enter through /dev/hidg0, one key every 5 ms (a script, not a person)
import sys, time
KEYS = {c: 0x04 + i for i, c in enumerate("abcdefghijklmnopqrstuvwxyz")}
KEYS[" "] = 0x2c; KEYS["\n"] = 0x28
gap = float(sys.argv[1]) if len(sys.argv) > 1 else 0.005
with open("/dev/hidg0", "wb", buffering=0) as f:
    for ch in "echo practice\n":
        f.write(bytes([0, 0, KEYS[ch], 0, 0, 0, 0, 0]))
        f.write(bytes(8))
        time.sleep(gap)
print("sent", len("echo practice\n"), "keys")
/usr/local/bin/readkeys.py
#!/usr/bin/env python3
# readkeys.py EVENT SECONDS: count key presses that reach this reader (no grab of its own)
import os, select, struct, sys, time
fd = os.open(sys.argv[1], os.O_RDONLY | os.O_NONBLOCK)
end = time.time() + float(sys.argv[2]); n = 0
while time.time() < end:
    r, _, _ = select.select([fd], [], [], 0.2)
    if r:
        data = os.read(fd, 24 * 64)
        for i in range(0, len(data), 24):
            _, _, typ, code, val = struct.unpack("llHHi", data[i:i + 24])
            if typ == 1 and val == 1:
                n += 1
print("key presses seen:", n)

Plug in the practice keyboard and type.

sudo fakeusb keyboard plug
sleep 6
EV=$(grep -A6 'Name="Practice Practice Keyboard"' /proc/bus/input/devices | sed -n 's/.*Handlers=.*\(event[0-9]*\).*/\1/p')
(sudo readkeys.py /dev/input/$EV 5 &) ; sleep 1; sudo timeout 10 typekeys.py
sleep 6
roamswitch notifications | sed -n 4,13p
Output excerpt
plugged
sent 14 keys
key presses seen: 0

  2026-11-12T02:25:51.241973616+00:00
  🚨 This keyboard shows signs of automated (scripted) typing
    The keyboard awaiting approval has already sent 13 keystrokes at an unnaturally uniform ~6ms interval since connecting — consistent with a scripted injection attack (BadUSB), not a person typing. Its keystrokes are already blocked, but if you don't recognize this device, be sure to choose Deny and unplug it.
    VID:1209 PID:0002

  2026-11-12T02:25:45.131931377+00:00
  🛑 Unauthorized USB Keyboard (BadUSB) Blocked
    Input from 'Practice Practice Keyboard (VID:1209 PID:0002)' was blocked and isolated. Authorize it in the RoamSwitch "USB Guard" tab to use it.

None of the 14 keys got through. There are two notifications. One says the unapproved keyboard was grabbed. The other says the keystrokes that arrived while it was held came at a speed and evenness no hand produces. The timing is a supporting clue: the block was already in place, whatever the timing.

Next, allow this keyboard and type at a human-like pace (one key every 150 ms).

allow_usb 1209:0002 "Practice Keyboard"
sleep 6
(sudo readkeys.py /dev/input/$EV 5 &) ; sleep 1; sudo timeout 10 typekeys.py 0.15
cat /run/roamswitch/approvals.json
Output
sent 14 keys
key presses seen: 14
[]

Once allowed, all 14 keys got through, and the pending request is gone. The keys go to the VM's console (it just shows echo practice).

#Settings on a trusted network

On a registered network, or with the away level set to Standard (balanced), the USB guards follow their settings. They live in ~/.config/roamswitch/config.json. These are the defaults, checked in the code.

KeyDefaultWhat it does
usb_storage_guard_enabledfalseHold unapproved USB drives
usb_keyboard_guard_enabledfalseGrab the input of unapproved keyboards
usb_zero_trust_enabledfalseNew USB devices come up unusable until allowed
usb_mount_scan_enabledtrueScan a mounted USB drive with ClamAV (when clamav_enabled is on too)
usb_keyboard_timeout_releasefalseRelease an unanswered keyboard hold after 3 minutes
away_protection_levellockdownThe protection level on unregistered networks
Switch to Standard and turn the guards on one at a time

This edits the config file and restarts the service (this changes the system).

Unplug the practice keyboard and take the practice USB drive off the allow-list (unallow_usb). Set the away level to Standard. A change of protection level takes effect on a service restart (or a network change).

setk() { python3 - "$1" "$2" <<'P'
import json,os,sys
p=os.path.expanduser('~/.config/roamswitch/config.json')
c=json.load(open(p))
k,v=sys.argv[1],sys.argv[2]
if v=='DEL': c.pop(k,None)
else: c[k]=json.loads(v)
json.dump(c,open(p,'w'),ensure_ascii=False,indent=2)
P
}
unallow_usb() { python3 - "$1" <<'P'
import json,os,sys
p=os.path.expanduser('~/.config/roamswitch/config.json')
c=json.load(open(p))
c['allowed_usb_devices']=[d for d in c['allowed_usb_devices'] if d['deviceIdentifier']!=sys.argv[1]]
json.dump(c,open(p,'w'),ensure_ascii=False,indent=2)
P
}
sudo fakeusb keyboard unplug
unallow_usb 1209:0001
setk away_protection_level '"balanced"'
sudo systemctl restart roamswitch; sleep 10
cat /run/roamswitch/state.json
roamswitch guards | grep -i "protection level"
Output
unplugged
{"active_level":"balanced","air_gap":false,"fanotify_ready":true,"network_trusted":false,"usb_zero_trust":false}
  Active protection level: Standard [BALANCED]

roamswitch guards also shows the level the daemon is using right now.

Plug in the USB drive you just took off the allow-list. Then turn on only the USB drive guard and plug it in again.

sudo fakeusb storage plug; sleep 6
cat /sys/bus/usb/devices/3-1/authorized
sudo fakeusb storage unplug; sleep 5
setk usb_storage_guard_enabled true; sleep 4
sudo fakeusb storage plug; sleep 6
cat /sys/bus/usb/devices/3-1/authorized
roamswitch notifications | sed -n 4,7p
Output excerpt
plugged
1
unplugged
plugged
0

  2026-11-12T02:26:39.408764995+00:00
  ⏸ Unauthorized USB Storage Held
    'Practice Practice Stick (VID:1209 PID:0001)' was held by the USB storage guard setting. Allow it in the approval dialog to use it.

At Standard with the guard off, the drive was usable (1). With usb_storage_guard_enabled on, it was held (0). This setting takes effect without a restart. The title is the same "Held" as in Lockdown, and the body gives the reason (Lockdown, or the USB storage guard setting). With usb_storage_guard_enabled on, a device whose fingerprint differs was also held, with the same "identity has changed" notification as in the previous exercise. At Standard with the guard off, it was usable even with a different fingerprint, with only a connection notification.

To undo, delete the added keys and restart. The level goes back to Lockdown. Finally, unplug the practice device and remove the module.

setk away_protection_level DEL
setk usb_storage_guard_enabled DEL
sudo systemctl restart roamswitch
sudo fakeusb storage unplug
sudo rmmod dummy_hcd
How much to block
  • A laptop you carry: keep Lockdown away from home. Unknown USB drives and keyboards are held whatever the settings say
  • A desk PC at home or at work: if people can plug USB devices into it, turn on usb_keyboard_guard_enabled. Put the keyboard you use on the allow-list first
  • A machine with no screen: no approval dialog appears, so a keyboard hold lasts until someone answers (the default, checked in the code). usb_keyboard_timeout_release releases it after 3 minutes; weigh the risk of being locked out against the weaker guard

#Check for Bluetooth keyboard spoofing

Bluetooth keyboards had a flaw that accepted keystrokes from devices that were never paired (CVE-2023-45866, known from BlueDucky). In Linux's BlueZ, ClassicBondedOnly in /etc/bluetooth/input.conf is the guard. roamswitch bluetooth-audit reads the BlueZ config files and looks for settings that remove it. It only reads files: nothing is run and nothing is sent.

Write a risky setting and check it

This edits the BlueZ config files (this changes the system). This exercise was run on a Server Edition VM.

sudo apt install bluez
sudo cp /etc/bluetooth/input.conf /etc/bluetooth/input.conf.orig
sudo cp /etc/bluetooth/main.conf /etc/bluetooth/main.conf.orig
sudo sed -i 's/^#ClassicBondedOnly=true/ClassicBondedOnly=false/' /etc/bluetooth/input.conf
sudo sed -i 's/^#AlwaysPairable = false/AlwaysPairable = true/' /etc/bluetooth/main.conf
roamswitch bluetooth-audit
Output
=====================================================
  🔵 Bluetooth keyboard-injection check
=====================================================

  [high] input.conf: ClassicBondedOnly=false
    ClassicBondedOnly=false makes this machine accept keyboard and mouse connections from devices that were never paired. That is the way in for someone nearby who poses as a keyboard and types on it (BlueDucky, CVE-2023-45866). Delete the line or set it to true (the BlueZ default is true).
  
  [medium] main.conf: AlwaysPairable=true
    AlwaysPairable=true accepts pairing requests from nearby devices even when no pairing agent is running (the default is false). Enable it only when you need it.
  
  
  Keyboards and mice connected over Bluetooth right now:
    none
  
  This only reads config files and /proc: nothing is run and nothing is sent. If you see a device you do not recognise, remove its pairing in the Bluetooth settings.

Each of the two edited lines is reported with a severity. Settings that are not written are taken as the BlueZ defaults. With --json, the same result comes out as JSON.

roamswitch bluetooth-audit --json
Output
{
  "installed": true,
  "findings": [
    {
      "id": "classic_bonded_only_off",
      "severity": "high",
      "detail": "input.conf: ClassicBondedOnly=false"
    },
    {
      "id": "always_pairable",
      "severity": "medium",
      "detail": "main.conf: AlwaysPairable=true"
    }
  ],
  "input_devices": [],
  "unreadable": []
}

Undo the edit and check again.

sudo mv /etc/bluetooth/input.conf.orig /etc/bluetooth/input.conf
sudo mv /etc/bluetooth/main.conf.orig /etc/bluetooth/main.conf
roamswitch bluetooth-audit | sed -n 5p
Output
  No setting in the BlueZ config lets unpaired devices in (anything not set falls back to the BlueZ default).

#What this does not show

What was checked and what was not

This was checked on a throwaway Ubuntu 24.04 VM, with virtual USB devices on dummy_hcd. Some things differ from real hardware.

  • The practice devices go through the kernel's USB stack, but physical plugging and electrical attacks are not reproduced
  • ClamAV finds only what it knows. This module used only the public test string (EICAR). The scan only notifies; it deletes nothing
  • The fingerprint does not tell apart a device that copies every descriptor. It is there to catch a device that only copies the IDs
  • When no other usable keyboard is left, the keyboard is not grabbed, only notified, so you are not locked out (a note in the code)
  • The Bluetooth check is a check of config files. This VM has no Bluetooth hardware, so real pairing and the list of connected devices were not tried
Do not
  • Turn on the keyboard guard before the keyboard you use is on the allow-list
  • Open a USB drive you found before you see the scan result
  • Treat a device as safe because its IDs (VID:PID) are on the allow-list
Check yourself

Away from home (on an unregistered network), you plug in an unknown USB drive. The USB guard settings are off. What happens?

Show the explanation

In Lockdown, unapproved USB drives and keyboards are held whatever the settings say. authorized stays 0 and nothing shows in lsblk. Once on the allow-list, the drive becomes usable.

Check yourself

An unapproved keyboard sent keys 5 ms apart. Which is right?

Show the explanation

An unapproved keyboard's input is grabbed as soon as it is attached. The timing notification is extra information that points to automated typing.

#What to learn next

  • Switching and choosing protection levels is covered in "Setting and choosing protection levels" (coming soon)
  • Handling malware that was found is covered in "Suspicious execution and malware" (coming soon)
  • USB and Bluetooth in the desktop app are covered in the Linux manual
Features covered

Related techniques (ATT&CK): T1091 T1200

What was verified

Verified on 2026-11-12. Target: Linux 1.17.39. The USB labs ran in Japanese and English, each on a fresh disposable Ubuntu 24.04 VM with the 1.17.39 client from the public apt repository and virtual devices on dummy_hcd; the Bluetooth audit ran on a Server Edition 1.17.39 VM.

Command output varies with the product version and the environment. If yours differs, check the version first.