udev and USB Device History on Linux
Reconstruct USB device connections on Linux from kernel, udisks and USBGuard logs, and check udev rules used for RUN+= persistence.
- Location
- /etc/udev/rules.d/, /var/log/kern.log
- Proves
- Which USB devices (vendor, product, serial) were attached and when, where storage was mounted, and whether udev rules run code
- Timestamps
- Journal: UTC microseconds; syslog: host local time; dmesg: seconds since boot
- Access
- root (or adm/systemd-journal group) for logs; rules readable by any user
- Retention
- Follows journal and kern.log/messages rotation; rules until deleted
- Collection
- UAC, Velociraptor, journalctl, cp -a
Tools
Compare all tools- journalctlCLI · built into Linux
- grep / zgrepCLI · built into Linux
- udevadmCLI · built into Linux
- PlasoCLI · open source
What it is
Linux has no central USB registry like Windows. Device history comes from log messages written when a device is enumerated: the kernel USB core announces vendor and product IDs and descriptor strings, storage drivers name the block device, and desktop services such as udisks log the mount. systemd-udevd then applies rules from several directories to name devices, set permissions and optionally run programs.
Those same rule files are a persistence location. A rule with RUN+= executes a program as root whenever a matching device event occurs, and a rule matching a device that always exists at boot runs at every start (the sedexp malware used a rule on /dev/random).
Where it lives
| Item | Location | Notes |
|---|---|---|
| Kernel USB messages | journal (_TRANSPORT=kernel); /var/log/kern.log and syslog (Debian/Ubuntu with rsyslog); /var/log/messages (RHEL/Fedora with rsyslog) | Also in dmesg on a live host |
| Local admin rules | /etc/udev/rules.d/*.rules | Highest priority; same name overrides vendor rules |
| Runtime rules | /run/udev/rules.d/ | Volatile |
| Vendor rules | /usr/lib/udev/rules.d/, /usr/local/lib/udev/rules.d/, /lib/udev/rules.d/ (non-merged-usr Debian/Ubuntu) | Owned by packages |
| udev database | /run/udev/data/ | Live only, current devices |
| Live device tree | /sys/bus/usb/devices/*/ (idVendor, idProduct, serial, manufacturer, product) | Live only |
| udisks mount points | /media/<user>/<label> (Debian/Ubuntu), /run/media/<user>/<label> (Fedora/RHEL) | Usually removed after unmount |
| USBGuard (if installed) | /etc/usbguard/rules.conf, /var/log/usbguard/usbguard-audit.log | Allow/block decisions per device |
What it proves
- A USB device was connected: time, bus port, vendor and product ID, and (when the device reports one) manufacturer, product and serial number strings.
- Mass storage: the device was bound to
usb-storageoruasand becamesdX, with capacity and removable flag. - Mounting and by whom: udisks logs the device, mount point and UID for desktop auto-mounts.
- Disconnect time (
USB disconnect, device number N). - Persistence or tampering via custom udev rules.
- It does not prove which files were copied. Combine with recent files, shell history and file timestamps on both sides. Device strings and serials are chosen by the device and can be spoofed.
Key fields
Typical kernel sequence for a flash drive:
usb 1-2: new high-speed USB device number 7 using xhci_hcd
usb 1-2: New USB device found, idVendor=0781, idProduct=5583, bcdDevice= 1.00
usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
usb 1-2: Product: Ultra Fit
usb 1-2: Manufacturer: SanDisk
usb 1-2: SerialNumber: 4C530001230704116195
usb-storage 1-2:1.0: USB Mass Storage device detected
sd 6:0:0:0: [sdb] Attached SCSI removable disk
udisksd[1432]: Mounted /dev/sdb1 at /media/alice/ULTRAFIT on behalf of uid 1000
usb 1-2: USB disconnect, device number 7
| Element | Meaning |
|---|---|
usb 1-2 | Bus 1, port 2 (port path, stable per physical port) |
device number 7 | Address on the bus, reused over time |
idVendor / idProduct | USB IDs; look up in the usb.ids database |
Product, Manufacturer, SerialNumber | Descriptor strings; printed only when the kernel has CONFIG_USB_ANNOUNCE_NEW_DEVICES and the device supplies them |
[sdb] | Block device assigned; ties later filesystem and mount messages to the device |
on behalf of uid | Account that requested the udisks mount |
The descriptor lines, the storage line and the udisks line are separate messages, so correlate them by port path, device name and time.
A udev rule is a list of match keys (ACTION, SUBSYSTEM, KERNEL, ATTR{...}, ATTRS{...}, ENV{...}) followed by assignments. Watch for RUN+= and PROGRAM=. The sedexp rule looked like ACTION=="add", ENV{MAJOR}=="1", ENV{MINOR}=="8", RUN+="..." (the /dev/random device).
Timestamps
- Journal entries carry
__REALTIME_TIMESTAMPin microseconds since the Unix epoch (UTC);journalctldisplays local time unless you pass--utc. - Text logs use the syslog daemon's format: local time, with or without offset and year (see auth.log and syslog).
dmesgoutput is seconds since boot. Converting withdmesg -Tuses the current boot time and drifts after suspend, so prefer the journal.- Rule files: inode mtime and ctime show when a rule was written.
Retention
Nothing is retained beyond the logs. Kernel messages survive only as long as the journal (size-based limits) and rotated kern.log/messages/syslog generations. With a volatile journal (/run/log/journal) and no rsyslog, USB history is lost at reboot. Rules persist until deleted; the /run copies vanish at shutdown.
Collection
E=/mnt/evidence
journalctl -D "$E/var/log/journal" -k --utc -o short-iso-precise > kernel.txt
journalctl -D "$E/var/log/journal" --utc -o json _COMM=udisksd > udisks.json
tar -C "$E" -cpf /cases/2026-017/udev.tar etc/udev usr/lib/udev/rules.d lib/udev/rules.d \
etc/usbguard var/log/usbguard var/log/kern.log* var/log/messages* var/log/syslog* 2>/dev/null
UAC collects the three rule directories, /var/log and runs lsusb and lsusb -vv in live response. On a live host, udevadm info --export-db dumps the current udev database.
Parsing
# Connection history from the journal
journalctl -D journal/ -k --utc -o short-iso-precise \
| grep -E 'New USB device found|Product:|Manufacturer:|SerialNumber:|USB disconnect|Attached SCSI'
# Same from text logs, including rotated copies
zgrep -hE 'usb [0-9-]+(\.[0-9]+)*: (New USB device|Product|Manufacturer|SerialNumber)' kern.log* messages*
# Rules that run programs, non-package locations first
grep -rnE 'RUN\+?=|PROGRAM=' etc/udev/rules.d run/udev/rules.d 2>/dev/null
Check whether a rule in /usr/lib/udev/rules.d belongs to a package (dpkg -S or rpm -qf against the image, see package manager logs). Plaso picks up kernel lines through its syslog and journal parsers.
Investigator tips
- The same serial number across several hosts links one physical device to multiple systems; an absent serial is common on cheap devices and some card readers.
RUN+=is meant for short foreground tasks: the udevd sandbox blocks network access and mounts, and any forked or detached child is killed when event handling ends. A rule that needs a long-running payload therefore has to hand off outside udev, for example to a systemd unit pulled in withENV{SYSTEMD_WANTS}. Follow the chain.- A rule named like a vendor rule (
99-...rules,70-persistent-...) in/etc/udev/rules.dwith no package owner deserves reading line by line. - Keyboard-class devices (HID) appearing unexpectedly on a server can indicate a malicious USB device; look for
input:lines next to the USB enumeration. - USBGuard block events prove an attempt even when the device never became usable.
- New modules loaded right after a device appears are worth checking against kernel modules.